diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx
index 793f0e6c2a42..e0fb4e554ee3 100644
--- a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx
+++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx
@@ -80,7 +80,6 @@ export const ConfirmDisableUsers: FunctionComponent =
}
confirmButtonColor={isSystemUser ? 'danger' : undefined}
isLoading={state.loading}
- ownFocus
>
{isSystemUser ? (
diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx
index a1aac5bc0a8c..2cb4cf8b4a9e 100644
--- a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx
+++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx
@@ -67,7 +67,6 @@ export const ConfirmEnableUsers: FunctionComponent = ({
}
)}
isLoading={state.loading}
- ownFocus
>
diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
index ca4c869e0f2d..c001f1fc2bc4 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
@@ -99,7 +99,7 @@ export const goToQueryTab = () => {
export const addNotesToTimeline = (notes: string) => {
goToNotesTab();
cy.get(NOTES_TEXT_AREA).type(notes);
- cy.get(ADD_NOTE_BUTTON).click();
+ cy.get(ADD_NOTE_BUTTON).click({ force: true });
cy.get(QUERY_TAB_BUTTON).click();
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx
index 1b67aaeb795d..eb75d896ae77 100644
--- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import * as i18n from './translations';
interface ConfirmDeleteCaseModalProps {
@@ -28,20 +28,18 @@ const ConfirmDeleteCaseModalComp: React.FC = ({
return null;
}
return (
-
-
- {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION}
-
-
+
+ {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION}
+
);
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx
index 1dfabda8068f..eda8ed8cdfbc 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx
@@ -6,13 +6,7 @@
*/
import React, { memo } from 'react';
-import {
- EuiModal,
- EuiModalBody,
- EuiModalHeader,
- EuiModalHeaderTitle,
- EuiOverlayMask,
-} from '@elastic/eui';
+import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui';
import { useGetUserSavedObjectPermissions } from '../../../common/lib/kibana';
import { Case } from '../../containers/types';
@@ -34,16 +28,14 @@ const AllCasesModalComponent: React.FC = ({
const userCanCrud = userPermissions?.crud ?? false;
return isModalOpen ? (
-
-
-
- {i18n.SELECT_CASE_TITLE}
-
-
-
-
-
-
+
+
+ {i18n.SELECT_CASE_TITLE}
+
+
+
+
+
) : null;
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx
index 3595f2c916af..8dd5080666cb 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx
@@ -7,13 +7,7 @@
import React, { memo } from 'react';
import styled from 'styled-components';
-import {
- EuiModal,
- EuiModalBody,
- EuiModalHeader,
- EuiModalHeaderTitle,
- EuiOverlayMask,
-} from '@elastic/eui';
+import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui';
import { FormContext } from '../create/form_context';
import { CreateCaseForm } from '../create/form';
@@ -40,21 +34,19 @@ const CreateModalComponent: React.FC = ({
onSuccess,
}) => {
return isModalOpen ? (
-
-
-
- {i18n.CREATE_TITLE}
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {i18n.CREATE_TITLE}
+
+
+
+
+
+
+
+
+
+
) : null;
};
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
index dc7388438c01..5ea11f61f9a7 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
@@ -14,7 +14,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
- EuiOverlayMask,
EuiButton,
EuiButtonEmpty,
EuiHorizontalRule,
@@ -348,133 +347,129 @@ export const AddExceptionModal = memo(function AddExceptionModal({
}, [maybeRule]);
return (
-
-
-
- {addExceptionMessage}
-
- {ruleName}
-
-
-
- {fetchOrCreateListError != null && (
-
-
-
+
+
+ {addExceptionMessage}
+
+ {ruleName}
+
+
+
+ {fetchOrCreateListError != null && (
+
+
+
+ )}
+ {fetchOrCreateListError == null &&
+ (isLoadingExceptionList ||
+ isIndexPatternLoading ||
+ isSignalIndexLoading ||
+ isSignalIndexPatternLoading) && (
+
)}
- {fetchOrCreateListError == null &&
- (isLoadingExceptionList ||
- isIndexPatternLoading ||
- isSignalIndexLoading ||
- isSignalIndexPatternLoading) && (
-
- )}
- {fetchOrCreateListError == null &&
- !isSignalIndexLoading &&
- !isSignalIndexPatternLoading &&
- !isLoadingExceptionList &&
- !isIndexPatternLoading &&
- !isRuleLoading &&
- !mlJobLoading &&
- ruleExceptionList && (
- <>
-
- {isRuleEQLSequenceStatement && (
- <>
-
-
- >
- )}
- {i18n.EXCEPTION_BUILDER_INFO}
-
-
-
-
-
-
-
-
-
- {alertData !== undefined && alertStatus !== 'closed' && (
-
-
-
- )}
+ {fetchOrCreateListError == null &&
+ !isSignalIndexLoading &&
+ !isSignalIndexPatternLoading &&
+ !isLoadingExceptionList &&
+ !isIndexPatternLoading &&
+ !isRuleLoading &&
+ !mlJobLoading &&
+ ruleExceptionList && (
+ <>
+
+ {isRuleEQLSequenceStatement && (
+ <>
+
+
+ >
+ )}
+ {i18n.EXCEPTION_BUILDER_INFO}
+
+
+
+
+
+
+
+
+
+ {alertData !== undefined && alertStatus !== 'closed' && (
- {exceptionListType === 'endpoint' && (
- <>
-
-
- {i18n.ENDPOINT_QUARANTINE_TEXT}
-
- >
- )}
-
- >
- )}
- {fetchOrCreateListError == null && (
-
- {i18n.CANCEL}
-
-
- {addExceptionMessage}
-
-
+ )}
+
+
+
+ {exceptionListType === 'endpoint' && (
+ <>
+
+
+ {i18n.ENDPOINT_QUARANTINE_TEXT}
+
+ >
+ )}
+
+ >
)}
-
-
+ {fetchOrCreateListError == null && (
+
+ {i18n.CANCEL}
+
+
+ {addExceptionMessage}
+
+
+ )}
+
);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
index 75b7bf2aabd7..336732016e93 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
@@ -12,7 +12,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
- EuiOverlayMask,
EuiButton,
EuiButtonEmpty,
EuiHorizontalRule,
@@ -281,125 +280,121 @@ export const EditExceptionModal = memo(function EditExceptionModal({
}, [maybeRule]);
return (
-
-
-
-
- {exceptionListType === 'endpoint'
- ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE
- : i18n.EDIT_EXCEPTION_TITLE}
-
-
- {ruleName}
-
-
- {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && (
-
- )}
- {!isSignalIndexLoading &&
- !addExceptionIsLoading &&
- !isIndexPatternLoading &&
- !isRuleLoading &&
- !mlJobLoading && (
- <>
-
- {isRuleEQLSequenceStatement && (
- <>
-
-
- >
- )}
- {i18n.EXCEPTION_BUILDER_INFO}
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {exceptionListType === 'endpoint'
+ ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE
+ : i18n.EDIT_EXCEPTION_TITLE}
+
+
+ {ruleName}
+
+
+ {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && (
+
+ )}
+ {!isSignalIndexLoading &&
+ !addExceptionIsLoading &&
+ !isIndexPatternLoading &&
+ !isRuleLoading &&
+ !mlJobLoading && (
+ <>
+
+ {isRuleEQLSequenceStatement && (
+ <>
+
-
- {exceptionListType === 'endpoint' && (
- <>
-
-
- {i18n.ENDPOINT_QUARANTINE_TEXT}
-
- >
- )}
-
- >
- )}
- {updateError != null && (
-
-
-
- )}
- {hasVersionConflict && (
-
-
- {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}
-
-
- )}
- {updateError == null && (
-
- {i18n.CANCEL}
-
-
- {i18n.EDIT_EXCEPTION_SAVE_BUTTON}
-
-
+
+ >
+ )}
+ {i18n.EXCEPTION_BUILDER_INFO}
+
+
+
+
+
+
+
+
+
+
+
+
+ {exceptionListType === 'endpoint' && (
+ <>
+
+
+ {i18n.ENDPOINT_QUARANTINE_TEXT}
+
+ >
+ )}
+
+ >
)}
-
-
+ {updateError != null && (
+
+
+
+ )}
+ {hasVersionConflict && (
+
+
+ {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}
+
+
+ )}
+ {updateError == null && (
+
+ {i18n.CANCEL}
+
+
+ {i18n.EDIT_EXCEPTION_SAVE_BUTTON}
+
+
+ )}
+
);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap
index 6503dd8dfb50..d1a41b1c32c1 100644
--- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap
@@ -2,64 +2,62 @@
exports[`ImportDataModal renders correctly against snapshot 1`] = `
-
-
-
-
- title
-
-
-
-
-
- description
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- submitBtnText
-
-
-
-
+
+
+
+ title
+
+
+
+
+
+ description
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ submitBtnText
+
+
+
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx
index 8a29ce379932..4c3dc2a249b4 100644
--- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx
@@ -15,7 +15,6 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiOverlayMask,
EuiSpacer,
EuiText,
} from '@elastic/eui';
@@ -132,51 +131,49 @@ export const ImportDataModalComponent = ({
return (
<>
{showModal && (
-
-
-
- {title}
-
-
-
-
- {description}
-
-
-
- {
- setSelectedFiles(files && files.length > 0 ? files : null);
- }}
- display={'large'}
- fullWidth={true}
- isLoading={isImporting}
+
+
+ {title}
+
+
+
+
+ {description}
+
+
+
+ {
+ setSelectedFiles(files && files.length > 0 ? files : null);
+ }}
+ display={'large'}
+ fullWidth={true}
+ isLoading={isImporting}
+ />
+
+ {showCheckBox && (
+ setOverwrite(!overwrite)}
/>
-
- {showCheckBox && (
- setOverwrite(!overwrite)}
- />
- )}
-
-
-
- {i18n.CANCEL_BUTTON}
-
- {submitBtnText}
-
-
-
-
+ )}
+
+
+
+ {i18n.CANCEL_BUTTON}
+
+ {submitBtnText}
+
+
+
)}
>
);
diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx
index ece29cd360ce..a5c014453111 100644
--- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx
@@ -15,7 +15,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
- EuiOverlayMask,
EuiSpacer,
EuiTabbedContent,
} from '@elastic/eui';
@@ -211,24 +210,22 @@ export const ModalInspectQuery = ({
];
return (
-
-
-
-
- {i18n.INSPECT} {title}
-
-
-
-
-
-
-
-
-
- {i18n.CLOSE}
-
-
-
-
+
+
+
+ {i18n.INSPECT} {title}
+
+
+
+
+
+
+
+
+
+ {i18n.CLOSE}
+
+
+
);
};
diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
index 778916ad2d07..be5702550a44 100644
--- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
@@ -246,6 +246,12 @@ exports[`Paginated Table Component rendering it renders the default load more ta
},
"euiFilePickerTallHeight": "128px",
"euiFlyoutBorder": "1px solid #343741",
+ "euiFlyoutPaddingModifiers": Object {
+ "paddingLarge": "24px",
+ "paddingMedium": "16px",
+ "paddingNone": 0,
+ "paddingSmall": "8px",
+ },
"euiFocusBackgroundColor": "#08334a",
"euiFocusRingAnimStartColor": "rgba(27, 169, 245, 0)",
"euiFocusRingAnimStartSize": "6px",
@@ -357,6 +363,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
},
"euiMarkdownEditorMinHeight": "150px",
"euiPageBackgroundColor": "#1a1b20",
+ "euiPageDefaultMaxWidth": "1000px",
"euiPaletteColorBlind": Object {
"euiColorVis0": Object {
"behindText": "#6dccb1",
@@ -534,6 +541,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
"euiSwitchWidthCompressed": "28px",
"euiSwitchWidthMini": "22px",
"euiTabFontSize": "16px",
+ "euiTabFontSizeL": "18px",
"euiTabFontSizeS": "14px",
"euiTableActionsAreaWidth": "40px",
"euiTableActionsBorderColor": "rgba(83, 89, 102, 0.09999999999999998)",
diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap
index f7924f37d2c1..5e008e28073d 100644
--- a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap
@@ -1,50 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Modal all errors rendering it renders the default all errors modal when isShowing is positive 1`] = `
-
-
-
-
- Your visualization has error(s)
-
-
-
-
-
-
-
- Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
-
-
-
- Close
-
-
-
-
+
+
+
+ Your visualization has error(s)
+
+
+
+
+
+
+
+ Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+
+ Close
+
+
+
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx
index 873ebe97317f..0a78139f5fe3 100644
--- a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx
@@ -7,7 +7,6 @@
import {
EuiButton,
- EuiOverlayMask,
EuiModal,
EuiModalHeader,
EuiModalHeaderTitle,
@@ -36,36 +35,34 @@ const ModalAllErrorsComponent: React.FC = ({ isShowing, toast, t
if (!isShowing || toast == null) return null;
return (
-
-
-
- {i18n.TITLE_ERROR_MODAL}
-
+
+
+ {i18n.TITLE_ERROR_MODAL}
+
-
-
-
- {toast.errors != null &&
- toast.errors.map((error, index) => (
- 100 ? `${error.substring(0, 100)} ...` : error}
- data-test-subj="modal-all-errors-accordion"
- >
- {error}
-
- ))}
-
+
+
+
+ {toast.errors != null &&
+ toast.errors.map((error, index) => (
+ 100 ? `${error.substring(0, 100)} ...` : error}
+ data-test-subj="modal-all-errors-accordion"
+ >
+ {error}
+
+ ))}
+
-
-
- {i18n.CLOSE_ERROR_MODAL}
-
-
-
-
+
+
+ {i18n.CLOSE_ERROR_MODAL}
+
+
+
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx
index adc46f08272d..aefa447269f4 100644
--- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx
@@ -15,7 +15,6 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiOverlayMask,
EuiPanel,
EuiSpacer,
EuiText,
@@ -211,7 +210,7 @@ export const ValueListsModalComponent: React.FC = ({
const columns = buildColumns(handleExport, handleDelete);
return (
-
+ <>
{i18n.MODAL_TITLE}
@@ -255,7 +254,7 @@ export const ValueListsModalComponent: React.FC = ({
name={exportDownload.name}
onDownload={() => setExportDownload({})}
/>
-
+ >
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx
index 20744c3a2251..e4d8e2cee326 100644
--- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiConfirmModal, EuiListGroup, EuiListGroupItem, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal, EuiListGroup, EuiListGroupItem } from '@elastic/eui';
import styled from 'styled-components';
import { rgba } from 'polished';
@@ -59,28 +59,26 @@ export const ReferenceErrorModalComponent: React.FC =
}
return (
-
-
- {contentText}
-
-
- {references.map((r, index) => (
-
- ))}
-
-
-
-
+
+ {contentText}
+
+
+ {references.map((r, index) => (
+
+ ))}
+
+
+
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
index 89785efbb504..04bf3c544030 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
@@ -9,7 +9,6 @@ import {
EuiBasicTable,
EuiLoadingContent,
EuiProgress,
- EuiOverlayMask,
EuiConfirmModal,
EuiWindowEvent,
} from '@elastic/eui';
@@ -490,18 +489,16 @@ export const RulesTables = React.memo(
)}
{showIdleModal && (
-
-
- {i18n.REFRESH_PROMPT_BODY}
-
-
+
+ {i18n.REFRESH_PROMPT_BODY}
+
)}
{shouldShowRulesTable && (
<>
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
index ec0198de5855..e14f56881d67 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
@@ -12,7 +12,6 @@ import {
EuiButton,
EuiButtonEmpty,
EuiSpacer,
- EuiOverlayMask,
EuiConfirmModal,
EuiCallOut,
EuiLoadingSpinner,
@@ -234,59 +233,54 @@ const ConfirmUpdate = React.memo<{
onCancel: () => void;
}>(({ hostCount, onCancel, onConfirm }) => {
return (
-
-
- {hostCount > 0 && (
- <>
-
-
-
-
- >
- )}
-
-
-
-
-
+
+ {hostCount > 0 && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
index 4e3dc953b539..bffd98061037 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
@@ -19,7 +19,6 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiOverlayMask,
EuiText,
} from '@elastic/eui';
@@ -100,36 +99,34 @@ export const TrustedAppDeletionDialog = memo(() => {
if (useTrustedAppsSelector(isDeletionDialogOpen)) {
return (
-
-
-
- {translations.title}
-
+
+
+ {translations.title}
+
-
-
- {translations.mainMessage}
- {translations.subMessage}
-
-
+
+
+ {translations.mainMessage}
+ {translations.subMessage}
+
+
-
-
- {translations.cancelButton}
-
+
+
+ {translations.cancelButton}
+
-
- {translations.confirmButton}
-
-
-
-
+
+ {translations.confirmButton}
+
+
+
);
} else {
return <>>;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
index a87f486a9d5d..7dde3fbe4cd2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiModal } from '@elastic/eui';
import React, { useCallback } from 'react';
import { createGlobalStyle } from 'styled-components';
@@ -46,16 +46,14 @@ export const DeleteTimelineModalOverlay = React.memo(
<>
{isModalOpen && }
{isModalOpen ? (
-
-
-
-
-
+
+
+
) : null}
>
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx
index 5b7fbcffd14a..c23cffa85451 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiModal } from '@elastic/eui';
import React from 'react';
import { TimelineModel } from '../../../../timelines/store/timeline/model';
@@ -26,22 +26,20 @@ const OPEN_TIMELINE_MODAL_WIDTH = 1100; // px
export const OpenTimelineModal = React.memo(
({ hideActions = [], modalTitle, onClose, onOpen }) => (
-
-
-
-
-
+
+
+
)
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
index 124c8012fd53..aece377ee4f2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
@@ -287,7 +287,7 @@ Array [
data-eui="EuiFocusTrap"
>
-
- {isSaving && (
-
+
+ {isSaving && (
+
+ )}
+ {modalHeader}
+
+
+ {showWarning && (
+
+
+
+
)}
- {modalHeader}
-
-
- {showWarning && (
-
-
-
-
- )}
-
-
-
-
+
+
+
);
}
);
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx
index 0638d3349206..792538a730eb 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx
@@ -7,7 +7,7 @@
import React, { Fragment, useRef, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { useServices, useToastNotifications } from '../app_context';
import { deletePolicies } from '../services/http';
@@ -96,58 +96,56 @@ export const PolicyDeleteProvider: React.FunctionComponent
= ({ children
const isSingle = policyNames.length === 1;
return (
-
-
- ) : (
-
- )
- }
- onCancel={closeModal}
- onConfirm={deletePolicy}
- cancelButtonText={
+
- }
- confirmButtonText={
+ ) : (
- }
- buttonColor="danger"
- data-test-subj="srdeletePolicyConfirmationModal"
- >
- {!isSingle ? (
-
-
-
-
-
- {policyNames.map((name) => (
- - {name}
- ))}
-
-
- ) : null}
-
-
+ )
+ }
+ onCancel={closeModal}
+ onConfirm={deletePolicy}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ buttonColor="danger"
+ data-test-subj="srdeletePolicyConfirmationModal"
+ >
+ {!isSingle ? (
+
+
+
+
+
+ {policyNames.map((name) => (
+ - {name}
+ ))}
+
+
+ ) : null}
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx
index 3fcf5a35b345..5636ca651b62 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx
@@ -7,7 +7,7 @@
import React, { Fragment, useRef, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { useServices, useToastNotifications } from '../app_context';
import { executePolicy as executePolicyRequest } from '../services/http';
@@ -81,32 +81,30 @@ export const PolicyExecuteProvider: React.FunctionComponent = ({ children
}
return (
-
-
- }
- onCancel={closeModal}
- onConfirm={executePolicy}
- cancelButtonText={
-
- }
- confirmButtonText={
-
- }
- data-test-subj="srExecutePolicyConfirmationModal"
- />
-
+
+ }
+ onCancel={closeModal}
+ onConfirm={executePolicy}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ data-test-subj="srExecutePolicyConfirmationModal"
+ />
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx
index 300941354111..f02f160958a2 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx
@@ -7,7 +7,7 @@
import React, { Fragment, useRef, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { Repository } from '../../../common/types';
import { useServices, useToastNotifications } from '../app_context';
@@ -109,79 +109,77 @@ export const RepositoryDeleteProvider: React.FunctionComponent = ({ child
const isSingle = repositoryNames.length === 1;
return (
-
-
- ) : (
-
- )
- }
- onCancel={closeModal}
- onConfirm={deleteRepository}
- cancelButtonText={
+
- }
- confirmButtonText={
- isSingle ? (
-
- ) : (
+ ) : (
+
+ )
+ }
+ onCancel={closeModal}
+ onConfirm={deleteRepository}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+ isSingle ? (
+
+ ) : (
+
+ )
+ }
+ buttonColor="danger"
+ data-test-subj="deleteRepositoryConfirmation"
+ >
+ {isSingle ? (
+
+
+
+ ) : (
+
+
- )
- }
- buttonColor="danger"
- data-test-subj="deleteRepositoryConfirmation"
- >
- {isSingle ? (
+
+
+ {repositoryNames.map((name) => (
+ - {name}
+ ))}
+
- ) : (
-
-
-
-
-
- {repositoryNames.map((name) => (
- - {name}
- ))}
-
-
-
-
-
- )}
-
-
+
+ )}
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx
index 9366815a0256..4ce1d9395595 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx
@@ -7,7 +7,7 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { useServices, useToastNotifications } from '../app_context';
import { executeRetention as executeRetentionRequest } from '../services/http';
@@ -58,31 +58,29 @@ export const RetentionExecuteModalProvider: React.FunctionComponent = ({
}
return (
-
-
- }
- onCancel={closeModal}
- onConfirm={executeRetention}
- cancelButtonText={
-
- }
- confirmButtonText={
-
- }
- data-test-subj="executeRetentionModal"
- />
-
+
+ }
+ onCancel={closeModal}
+ onConfirm={executeRetention}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ data-test-subj="executeRetentionModal"
+ />
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx
index d8916ce9858f..73e19eee8bf7 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx
@@ -8,7 +8,6 @@
import React, { Fragment, useRef, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
- EuiOverlayMask,
EuiModal,
EuiModalHeader,
EuiModalHeaderTitle,
@@ -129,165 +128,161 @@ export const RetentionSettingsUpdateModalProvider: React.FunctionComponent
-
-
-
- {isEditing ? (
-
- ) : (
-
- )}
-
-
-
-
- {saveError && (
-
-
- }
- color="danger"
- iconType="alert"
- >
- {saveError.data && saveError.data.message ? (
- {saveError.data.message}
- ) : null}
-
-
-
+
+
+
+ {isEditing ? (
+
+ ) : (
+
)}
- {isAdvancedCronVisible ? (
-
-
- }
- isInvalid={isInvalid}
- error={i18n.translate(
- 'xpack.snapshotRestore.policyForm.stepRetention.policyUpdateRetentionScheduleFieldErrorMessage',
- {
- defaultMessage: 'Retention schedule is required.',
- }
- )}
- helpText={
-
-
-
- ),
- }}
- />
+
+
+
+
+ {saveError && (
+
+
+ }
+ color="danger"
+ iconType="alert"
+ >
+ {saveError.data && saveError.data.message ? {saveError.data.message}
: null}
+
+
+
+ )}
+ {isAdvancedCronVisible ? (
+
+
+ }
+ isInvalid={isInvalid}
+ error={i18n.translate(
+ 'xpack.snapshotRestore.policyForm.stepRetention.policyUpdateRetentionScheduleFieldErrorMessage',
+ {
+ defaultMessage: 'Retention schedule is required.',
}
- fullWidth
- >
- setRetentionSchedule(e.target.value)}
+ )}
+ helpText={
+
+
+
+ ),
+ }}
/>
-
+ }
+ fullWidth
+ >
+ setRetentionSchedule(e.target.value)}
+ />
+
-
+
-
- {
- setIsAdvancedCronVisible(false);
- setRetentionSchedule(simpleCron.expression);
- }}
- data-test-subj="showBasicCronLink"
- >
-
-
-
-
- ) : (
-
- {
- setSimpleCron({
- expression,
- frequency,
- });
- setFieldToPreferredValueMap(newFieldToPreferredValueMap);
- setRetentionSchedule(expression);
+
+ {
+ setIsAdvancedCronVisible(false);
+ setRetentionSchedule(simpleCron.expression);
}}
- />
-
-
+ data-test-subj="showBasicCronLink"
+ >
+
+
+
+
+ ) : (
+
+ {
+ setSimpleCron({
+ expression,
+ frequency,
+ });
+ setFieldToPreferredValueMap(newFieldToPreferredValueMap);
+ setRetentionSchedule(expression);
+ }}
+ />
-
- {
- setIsAdvancedCronVisible(true);
- }}
- data-test-subj="showAdvancedCronLink"
- >
-
-
-
-
- )}
-
+
-
-
+
+ {
+ setIsAdvancedCronVisible(true);
+ }}
+ data-test-subj="showAdvancedCronLink"
+ >
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ {isEditing ? (
-
-
-
- {isEditing ? (
-
- ) : (
-
- )}
-
-
-
-
+ ) : (
+
+ )}
+
+
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx
index 40af1b07a50b..74614efb314a 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx
@@ -9,7 +9,6 @@ import React, { Fragment, useRef, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiConfirmModal,
- EuiOverlayMask,
EuiCallOut,
EuiLoadingSpinner,
EuiFlexGroup,
@@ -118,95 +117,93 @@ export const SnapshotDeleteProvider: React.FunctionComponent = ({ childre
const isSingle = snapshotIds.length === 1;
return (
-
-
- ) : (
-
- )
- }
- onCancel={closeModal}
- onConfirm={deleteSnapshot}
- cancelButtonText={
+
- }
- confirmButtonText={
+ ) : (
- }
- confirmButtonDisabled={isDeleting}
- buttonColor="danger"
- data-test-subj="srdeleteSnapshotConfirmationModal"
- >
- {!isSingle ? (
-
+ )
+ }
+ onCancel={closeModal}
+ onConfirm={deleteSnapshot}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ confirmButtonDisabled={isDeleting}
+ buttonColor="danger"
+ data-test-subj="srdeleteSnapshotConfirmationModal"
+ >
+ {!isSingle ? (
+
+
+
+
+
+ {snapshotIds.map(({ snapshot, repository }) => (
+ - {snapshot}
+ ))}
+
+
+ ) : null}
+
+
+
+ {!isSingle && isDeleting ? (
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
-
- {snapshotIds.map(({ snapshot, repository }) => (
- - {snapshot}
- ))}
-
-
- ) : null}
-
-
-
- {!isSingle && isDeleting ? (
-
-
-
-
-
-
-
-
-
-
-
- }
- >
-
-
-
-
-
- ) : null}
-
-
+
+
+ ) : null}
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx
index e7bdde2984d6..823ce3a122ef 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx
@@ -281,7 +281,7 @@ export const RepositoryDetails: React.FunctionComponent = ({
{verification ? (
-
+
{JSON.stringify(
verification.valid ? verification.response : verification.error,
null,
@@ -350,7 +350,7 @@ export const RepositoryDetails: React.FunctionComponent = ({
/>
-
+
{JSON.stringify(cleanup.response, null, 2)}
diff --git a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap
index b0d0933614d1..5bf93a1021c0 100644
--- a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap
@@ -1,95 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfirmDeleteModal renders as expected 1`] = `
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ ,
}
}
/>
-
-
-
-
-
-
-
- ,
- }
- }
- />
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
`;
diff --git a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx
index c57bc1cef8fb..94a5c082834a 100644
--- a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx
+++ b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx
@@ -20,7 +20,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalProps,
- EuiOverlayMask,
EuiSpacer,
EuiText,
} from '@elastic/eui';
@@ -97,88 +96,86 @@ class ConfirmDeleteModalUI extends Component
{
};
return (
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ ),
}}
/>
-
-
-
-
-
-
-
-
- ),
- }}
- />
-
-
-
-
-
-
- {warning}
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+ {warning}
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap
index 750afcfc44e7..3eb92de01792 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap
@@ -1,28 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfirmAlterActiveSpaceModal renders as expected 1`] = `
-
-
- }
- >
-
-
-
-
-
+
+ }
+>
+
+
+
+
`;
diff --git a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx
index 1839fbdfdda7..c95bb7250a23 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
@@ -16,34 +16,32 @@ interface Props {
}
const ConfirmAlterActiveSpaceModalUI: React.FC = (props) => (
-
-
- }
- defaultFocusedButton={'confirm'}
- cancelButtonText={props.intl.formatMessage({
- id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton',
- defaultMessage: 'Cancel',
- })}
- confirmButtonText={props.intl.formatMessage({
- id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.updateSpaceButton',
- defaultMessage: 'Update space',
- })}
- >
-
-
-
-
-
+
+ }
+ defaultFocusedButton={'confirm'}
+ cancelButtonText={props.intl.formatMessage({
+ id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton',
+ defaultMessage: 'Cancel',
+ })}
+ confirmButtonText={props.intl.formatMessage({
+ id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.updateSpaceButton',
+ defaultMessage: 'Update space',
+ })}
+ >
+
+
+
+
);
export const ConfirmAlterActiveSpaceModal = injectI18n(ConfirmAlterActiveSpaceModalUI);
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx
index b215b2af56c3..2d2a5b1fcad9 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx
@@ -7,7 +7,7 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
interface Props {
onCancel: () => void;
@@ -59,19 +59,17 @@ const cancelButtonText = i18n.translate(
);
export const SwitchModal: FC = ({ onCancel, onConfirm, type }) => (
-
-
- {type === 'pivot' ? pivotModalMessage : sourceModalMessage}
-
-
+
+ {type === 'pivot' ? pivotModalMessage : sourceModalMessage}
+
);
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
index 148e6c1a3bac..d82f0769c8b7 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
@@ -12,7 +12,6 @@ import {
EuiConfirmModal,
EuiFlexGroup,
EuiFlexItem,
- EuiOverlayMask,
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
@@ -123,22 +122,20 @@ export const DeleteActionModal: FC = ({
);
return (
-
-
- {isBulkAction ? bulkDeleteModalContent : deleteModalContent}
-
-
+
+ {isBulkAction ? bulkDeleteModalContent : deleteModalContent}
+
);
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
index c3967dd687a6..bb01fe355a33 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
@@ -7,7 +7,7 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
+import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
import { StartAction } from './use_start_action';
@@ -24,27 +24,25 @@ export const StartActionModal: FC = ({ closeModal, items, startAndC
});
return (
-
-
+
+ {i18n.translate('xpack.transform.transformList.startModalBody', {
+ defaultMessage:
+ 'A transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.',
})}
- confirmButtonText={i18n.translate('xpack.transform.transformList.startModalStartButton', {
- defaultMessage: 'Start',
- })}
- defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
- buttonColor="primary"
- >
-
- {i18n.translate('xpack.transform.transformList.startModalBody', {
- defaultMessage:
- 'A transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.',
- })}
-
-
-
+
+
);
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
index b84d7fc433df..bcb07c8069ab 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
@@ -14,7 +14,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiModal,
- EuiOverlayMask,
EuiPageContent,
EuiPageContentBody,
EuiSpacer,
@@ -124,15 +123,13 @@ export const TransformManagement: FC = () => {
{isSearchSelectionVisible && (
-
-
-
-
-
+
+
+
)}
);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx
index 952ea07ba05c..b98db1178f46 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useState } from 'react';
import { HttpSetup } from 'kibana/public';
@@ -73,56 +73,54 @@ export const DeleteModalConfirmation = ({
}
);
return (
-
- {
- setDeleteModalVisibility(false);
- onCancel();
- }}
- onConfirm={async () => {
- setDeleteModalVisibility(false);
- setIsLoadingState(true);
- const { successes, errors } = await apiDeleteCall({ ids: idsToDelete, http });
- setIsLoadingState(false);
+ {
+ setDeleteModalVisibility(false);
+ onCancel();
+ }}
+ onConfirm={async () => {
+ setDeleteModalVisibility(false);
+ setIsLoadingState(true);
+ const { successes, errors } = await apiDeleteCall({ ids: idsToDelete, http });
+ setIsLoadingState(false);
- const numSuccesses = successes.length;
- const numErrors = errors.length;
- if (numSuccesses > 0) {
- toasts.addSuccess(
- i18n.translate(
- 'xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText',
- {
- defaultMessage:
- 'Deleted {numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}',
- values: { numSuccesses, singleTitle, multipleTitle },
- }
- )
- );
- }
+ const numSuccesses = successes.length;
+ const numErrors = errors.length;
+ if (numSuccesses > 0) {
+ toasts.addSuccess(
+ i18n.translate(
+ 'xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText',
+ {
+ defaultMessage:
+ 'Deleted {numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}',
+ values: { numSuccesses, singleTitle, multipleTitle },
+ }
+ )
+ );
+ }
- if (numErrors > 0) {
- toasts.addDanger(
- i18n.translate(
- 'xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText',
- {
- defaultMessage:
- 'Failed to delete {numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}',
- values: { numErrors, singleTitle, multipleTitle },
- }
- )
- );
- await onErrors();
- }
- await onDeleted(successes);
- }}
- cancelButtonText={cancelButtonText}
- confirmButtonText={confirmButtonText}
- >
- {confirmModalText}
-
-
+ if (numErrors > 0) {
+ toasts.addDanger(
+ i18n.translate(
+ 'xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText',
+ {
+ defaultMessage:
+ 'Failed to delete {numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}',
+ values: { numErrors, singleTitle, multipleTitle },
+ }
+ )
+ );
+ await onErrors();
+ }
+ await onDeleted(successes);
+ }}
+ cancelButtonText={cancelButtonText}
+ confirmButtonText={confirmButtonText}
+ >
+ {confirmModalText}
+
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx
index b7450d742bc4..8732727b9a77 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx
@@ -7,17 +7,19 @@
import React, { useCallback, useMemo, useReducer, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup } from '@elastic/eui';
import {
EuiModal,
EuiButton,
+ EuiButtonEmpty,
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalBody,
EuiModalFooter,
+ EuiTitle,
+ EuiFlexItem,
+ EuiIcon,
+ EuiFlexGroup,
} from '@elastic/eui';
-import { EuiButtonEmpty } from '@elastic/eui';
-import { EuiOverlayMask } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
import { createConnectorReducer, InitialConnector, ConnectorReducer } from './connector_reducer';
@@ -127,92 +129,90 @@ export const ConnectorAddModal = ({
});
return (
-
-
-
-
-
- {actionTypeModel && actionTypeModel.iconClass ? (
-
-
-
- ) : null}
-
-
-
-
-
-
+
+
+
+
+ {actionTypeModel && actionTypeModel.iconClass ? (
+
+
-
-
-
+ ) : null}
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- {i18n.translate(
- 'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel',
- {
- defaultMessage: 'Cancel',
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel',
+ {
+ defaultMessage: 'Cancel',
+ }
+ )}
+
+ {canSave ? (
+ {
+ if (hasErrors) {
+ setConnector(
+ getConnectorWithInvalidatedFields(
+ connector,
+ configErrors,
+ secretsErrors,
+ connectorBaseErrors
+ )
+ );
+ return;
}
- )}
-
- {canSave ? (
- {
- if (hasErrors) {
- setConnector(
- getConnectorWithInvalidatedFields(
- connector,
- configErrors,
- secretsErrors,
- connectorBaseErrors
- )
- );
- return;
- }
- setIsSaving(true);
- const savedAction = await onActionConnectorSave();
- setIsSaving(false);
- if (savedAction) {
- if (postSaveEventHandler) {
- postSaveEventHandler(savedAction);
- }
- closeModal();
+ setIsSaving(true);
+ const savedAction = await onActionConnectorSave();
+ setIsSaving(false);
+ if (savedAction) {
+ if (postSaveEventHandler) {
+ postSaveEventHandler(savedAction);
}
- }}
- >
-
-
- ) : null}
-
-
-
+ closeModal();
+ }
+ }}
+ >
+
+
+ ) : null}
+
+
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx
index 9ef7e414d505..6d71fe858f1c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@@ -17,38 +17,36 @@ interface Props {
export const ConfirmAlertClose: React.FC = ({ onConfirm, onCancel }) => {
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx
index 48d4229bb9b3..c406ec7c8028 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@@ -17,38 +17,36 @@ interface Props {
export const ConfirmAlertSave: React.FC = ({ onConfirm, onCancel }) => {
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx
index f13e5fd96d2a..4a5739c8b443 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { capitalize } from 'lodash';
interface Props {
@@ -26,37 +26,35 @@ export const ManageLicenseModal: React.FC = ({
}) => {
const licenseRequired = capitalize(licenseType);
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap
index 31d4322210e2..b689ca7ff56f 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap
@@ -342,6 +342,14 @@ exports[`DonutChart component passes correct props without errors for valid prop
"band": Object {
"fill": "rgba(245, 247, 250, 1)",
},
+ "crossLine": Object {
+ "dash": Array [
+ 4,
+ 4,
+ ],
+ "stroke": "rgba(105, 112, 125, 1)",
+ "strokeWidth": 1,
+ },
"line": Object {
"dash": Array [
4,
diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap
index d83e45fea1ae..9d670158bc53 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap
@@ -1,58 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ML Confirm Job Delete shallow renders without errors 1`] = `
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
`;
exports[`ML Confirm Job Delete shallow renders without errors while loading 1`] = `
-
-
-
-
- )
-
-
+
+
-
-
+ )
+
+
+
`;
diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap
index fd59b14520ce..23feec1e5181 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap
@@ -84,7 +84,7 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `
data-eui="EuiFocusTrap"
>
= ({ onConfirm, onCancel }) => {
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.tsx
index ef95f6a14ed9..96c67227caba 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiOverlayMask, EuiConfirmModal, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiConfirmModal, EuiLoadingSpinner } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import * as labels from './translations';
@@ -18,45 +18,43 @@ interface Props {
export const ConfirmJobDeletion: React.FC
= ({ loading, onConfirm, onCancel }) => {
return (
-
-
- {!loading ? (
-
-
-
- ) : (
-
-
- )
-
- )}
- {!loading ? (
-
-
+ {!loading ? (
+
+
+
+ ) : (
+
+
+ )
+
+ )}
+ {!loading ? (
+
+
-
- ) : (
-
- )}
-
-
+ />
+
+ ) : (
+
+ )}
+
);
};
diff --git a/x-pack/plugins/watcher/public/application/components/confirm_watches_modal.tsx b/x-pack/plugins/watcher/public/application/components/confirm_watches_modal.tsx
index 88cb04a03629..04114c87e61d 100644
--- a/x-pack/plugins/watcher/public/application/components/confirm_watches_modal.tsx
+++ b/x-pack/plugins/watcher/public/application/components/confirm_watches_modal.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
@@ -26,29 +26,27 @@ export const ConfirmWatchesModal = ({
}
const { title, message, buttonType, buttonLabel } = modalOptions;
return (
-
- callback()}
- onConfirm={() => {
- callback(true);
- }}
- cancelButtonText={i18n.translate(
- 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.cancelButtonLabel',
- { defaultMessage: 'Cancel' }
- )}
- confirmButtonText={
- buttonLabel
- ? buttonLabel
- : i18n.translate(
- 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.saveButtonLabel',
- { defaultMessage: 'Save watch' }
- )
- }
- >
- {message}
-
-
+ callback()}
+ onConfirm={() => {
+ callback(true);
+ }}
+ cancelButtonText={i18n.translate(
+ 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.cancelButtonLabel',
+ { defaultMessage: 'Cancel' }
+ )}
+ confirmButtonText={
+ buttonLabel
+ ? buttonLabel
+ : i18n.translate(
+ 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.saveButtonLabel',
+ { defaultMessage: 'Save watch' }
+ )
+ }
+ >
+ {message}
+
);
};
diff --git a/x-pack/plugins/watcher/public/application/components/delete_watches_modal.tsx b/x-pack/plugins/watcher/public/application/components/delete_watches_modal.tsx
index c9d8d3fb545c..844e210de26f 100644
--- a/x-pack/plugins/watcher/public/application/components/delete_watches_modal.tsx
+++ b/x-pack/plugins/watcher/public/application/components/delete_watches_modal.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { deleteWatches } from '../lib/api';
@@ -45,48 +45,46 @@ export const DeleteWatchesModal = ({
}
);
return (
-
- callback()}
- onConfirm={async () => {
- const { successes, errors } = await deleteWatches(watchesToDelete);
- const numSuccesses = successes.length;
- const numErrors = errors.length;
- callback(successes);
- if (numSuccesses > 0) {
- toasts.addSuccess(
- i18n.translate(
- 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText',
- {
- defaultMessage:
- 'Deleted {numSuccesses, number} {numSuccesses, plural, one {watch} other {watches}}',
- values: { numSuccesses },
- }
- )
- );
- }
+ callback()}
+ onConfirm={async () => {
+ const { successes, errors } = await deleteWatches(watchesToDelete);
+ const numSuccesses = successes.length;
+ const numErrors = errors.length;
+ callback(successes);
+ if (numSuccesses > 0) {
+ toasts.addSuccess(
+ i18n.translate(
+ 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText',
+ {
+ defaultMessage:
+ 'Deleted {numSuccesses, number} {numSuccesses, plural, one {watch} other {watches}}',
+ values: { numSuccesses },
+ }
+ )
+ );
+ }
- if (numErrors > 0) {
- toasts.addDanger(
- i18n.translate(
- 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText',
- {
- defaultMessage:
- 'Failed to delete {numErrors, number} {numErrors, plural, one {watch} other {watches}}',
- values: { numErrors },
- }
- )
- );
- }
- }}
- cancelButtonText={cancelButtonText}
- confirmButtonText={confirmButtonText}
- >
- {confirmModalText}
-
-
+ if (numErrors > 0) {
+ toasts.addDanger(
+ i18n.translate(
+ 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText',
+ {
+ defaultMessage:
+ 'Failed to delete {numErrors, number} {numErrors, plural, one {watch} other {watches}}',
+ values: { numErrors },
+ }
+ )
+ );
+ }
+ }}
+ cancelButtonText={cancelButtonText}
+ confirmButtonText={confirmButtonText}
+ >
+ {confirmModalText}
+
);
};
diff --git a/yarn.lock b/yarn.lock
index ff0cb49911ca..31fbf7d9037a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2203,10 +2203,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@31.4.0":
- version "31.4.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-31.4.0.tgz#d2c8cc91fc538f7b1c5e5229663e186fa0c9207c"
- integrity sha512-ADdUeNxj2uiN13U7AkF0ishLAN0xcqFWHC+xjEmx8Wedyaj5DFrmmJEuH9aXv+XSQG5l8ppMgZQb3pMDjR2mKw==
+"@elastic/eui@31.7.0":
+ version "31.7.0"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-31.7.0.tgz#091cad212da3d7bad8e1717398d9bf404257f835"
+ integrity sha512-Be4K/dizAsmOrRMAKZy6oW4VaEvQNB1LvbKxNWIa4yFeX7Wn6gw5ihttgbQ4vqWWIkhLPoadbXMebKF9+7VuhA==
dependencies:
"@types/chroma-js" "^2.0.0"
"@types/lodash" "^4.14.160"
From d9df612495ed14b067fa2a255d9667c5a36dac08 Mon Sep 17 00:00:00 2001
From: Melissa Alvarez
Date: Tue, 16 Feb 2021 15:12:56 -0500
Subject: [PATCH 03/23] ensure class_assignment_objective parameter is cloned
correctly (#91507)
---
.../ml/common/types/data_frame_analytics.ts | 3 ++-
.../hooks/use_create_analytics_form/state.ts | 18 +++++++++++-------
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts
index cacc5acb9768..95d82932a121 100644
--- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts
+++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts
@@ -34,9 +34,10 @@ interface Regression {
}
interface Classification {
+ class_assignment_objective?: string;
dependent_variable: string;
training_percent?: number;
- num_top_classes?: string;
+ num_top_classes?: number;
num_top_feature_importance_values?: number;
prediction_field_name?: string;
}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
index 131da93a2328..40e13ea0e686 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
@@ -16,6 +16,7 @@ import {
DataFrameAnalyticsId,
DataFrameAnalysisConfigType,
} from '../../../../../../../common/types/data_frame_analytics';
+import { isClassificationAnalysis } from '../../../../../../../common/util/analytics_utils';
import { ANALYSIS_CONFIG_TYPE } from '../../../../../../../common/constants/data_frame_analytics';
export enum DEFAULT_MODEL_MEMORY_LIMIT {
regression = '100mb',
@@ -50,6 +51,7 @@ export interface State {
alpha: undefined | number;
computeFeatureInfluence: string;
createIndexPattern: boolean;
+ classAssignmentObjective: undefined | string;
dependentVariable: DependentVariable;
description: string;
destinationIndex: EsIndexName;
@@ -126,6 +128,7 @@ export const getInitialState = (): State => ({
alpha: undefined,
computeFeatureInfluence: 'true',
createIndexPattern: true,
+ classAssignmentObjective: undefined,
dependentVariable: '',
description: '',
destinationIndex: '',
@@ -278,13 +281,14 @@ export const getJobConfigFromFormState = (
};
}
- if (
- formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION &&
- jobConfig?.analysis?.classification !== undefined &&
- formState.numTopClasses !== undefined
- ) {
- // @ts-ignore
- jobConfig.analysis.classification.num_top_classes = formState.numTopClasses;
+ if (jobConfig?.analysis !== undefined && isClassificationAnalysis(jobConfig?.analysis)) {
+ if (formState.numTopClasses !== undefined) {
+ jobConfig.analysis.classification.num_top_classes = formState.numTopClasses;
+ }
+ if (formState.classAssignmentObjective !== undefined) {
+ jobConfig.analysis.classification.class_assignment_objective =
+ formState.classAssignmentObjective;
+ }
}
if (formState.jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION) {
From 3fe52287310af6d56f162078fd278d966ff17285 Mon Sep 17 00:00:00 2001
From: spalger
Date: Tue, 16 Feb 2021 13:24:06 -0700
Subject: [PATCH 04/23] remove Operations from renovate labels
---
renovate.json5 | 2 --
1 file changed, 2 deletions(-)
diff --git a/renovate.json5 b/renovate.json5
index f1e773427a10..415aa71fc382 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -14,7 +14,6 @@
],
labels: [
'release_note:skip',
- 'Team:Operations',
'renovate',
'v8.0.0',
'v7.11.0',
@@ -22,7 +21,6 @@
major: {
labels: [
'release_note:skip',
- 'Team:Operations',
'renovate',
'v8.0.0',
'v7.11.0',
From 073cd4d5086f9a76845e5e9e68d1270291ff1e05 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Tue, 16 Feb 2021 21:29:56 +0100
Subject: [PATCH 05/23] [ML] Transforms: Adds retention policy options to
transform UI. (#91162)
Adds retention policy options to the transform UI.
---
.../common/api_schemas/transforms.ts | 8 +
.../common/api_schemas/update_transforms.ts | 3 +-
.../transform/common/types/transform_stats.ts | 2 +
.../public/app/common/request.test.ts | 5 +-
.../transform/public/app/common/request.ts | 13 +-
.../public/app/common/validators.test.ts | 74 +++++-
.../transform/public/app/common/validators.ts | 88 ++++++-
.../components/step_details/common.ts | 90 +++++++
.../components/step_details/index.ts | 5 +-
.../step_details/step_details_form.tsx | 232 ++++++++++++------
.../step_details/step_details_summary.tsx | 27 +-
.../edit_transform_flyout_form.tsx | 40 +++
.../use_edit_transform_flyout.test.ts | 40 ++-
.../use_edit_transform_flyout.ts | 115 ++++++---
14 files changed, 614 insertions(+), 128 deletions(-)
diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts
index face319f141d..f9dedf0acb56 100644
--- a/x-pack/plugins/transform/common/api_schemas/transforms.ts
+++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts
@@ -51,6 +51,13 @@ export type PivotConfig = TypeOf;
export type LatestFunctionConfig = TypeOf;
+export const retentionPolicySchema = schema.object({
+ time: schema.object({
+ field: schema.string(),
+ max_age: schema.string(),
+ }),
+});
+
export const settingsSchema = schema.object({
max_page_search_size: schema.maybe(schema.number()),
// The default value is null, which disables throttling.
@@ -94,6 +101,7 @@ export const putTransformsRequestSchema = schema.object(
* Latest and pivot are mutually exclusive, i.e. exactly one must be specified in the transform configuration
*/
latest: schema.maybe(latestFunctionSchema),
+ retention_policy: schema.maybe(retentionPolicySchema),
settings: schema.maybe(settingsSchema),
source: sourceSchema,
sync: schema.maybe(syncSchema),
diff --git a/x-pack/plugins/transform/common/api_schemas/update_transforms.ts b/x-pack/plugins/transform/common/api_schemas/update_transforms.ts
index 4ff9780be1f5..9bd4df510804 100644
--- a/x-pack/plugins/transform/common/api_schemas/update_transforms.ts
+++ b/x-pack/plugins/transform/common/api_schemas/update_transforms.ts
@@ -9,7 +9,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { TransformPivotConfig } from '../types/transform';
-import { settingsSchema, sourceSchema, syncSchema } from './transforms';
+import { retentionPolicySchema, settingsSchema, sourceSchema, syncSchema } from './transforms';
// POST _transform/{transform_id}/_update
export const postTransformsUpdateRequestSchema = schema.object({
@@ -22,6 +22,7 @@ export const postTransformsUpdateRequestSchema = schema.object({
})
),
frequency: schema.maybe(schema.string()),
+ retention_policy: schema.maybe(retentionPolicySchema),
settings: schema.maybe(settingsSchema),
source: schema.maybe(sourceSchema),
sync: schema.maybe(syncSchema),
diff --git a/x-pack/plugins/transform/common/types/transform_stats.ts b/x-pack/plugins/transform/common/types/transform_stats.ts
index d280f4ce3505..f3b7000a424d 100644
--- a/x-pack/plugins/transform/common/types/transform_stats.ts
+++ b/x-pack/plugins/transform/common/types/transform_stats.ts
@@ -33,6 +33,8 @@ export interface TransformStats {
attributes: Record;
};
stats: {
+ delete_time_in_ms: number;
+ documents_deleted: number;
documents_indexed: number;
documents_processed: number;
index_failures: number;
diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts
index 778b2c24325f..fa39419c254b 100644
--- a/x-pack/plugins/transform/public/app/common/request.test.ts
+++ b/x-pack/plugins/transform/public/app/common/request.test.ts
@@ -10,7 +10,7 @@ import { PIVOT_SUPPORTED_AGGS } from '../../../common/types/pivot_aggs';
import { PivotGroupByConfig } from '../common';
import { StepDefineExposedState } from '../sections/create_transform/components/step_define';
-import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form';
+import { StepDetailsExposedState } from '../sections/create_transform/components/step_details';
import { PIVOT_SUPPORTED_GROUP_BY_AGGS } from './pivot_group_by';
import { PivotAggsConfig } from './pivot_aggs';
@@ -174,6 +174,9 @@ describe('Transform: Common', () => {
continuousModeDelay: 'the-continuous-mode-delay',
createIndexPattern: false,
isContinuousModeEnabled: false,
+ isRetentionPolicyEnabled: false,
+ retentionPolicyDateField: '',
+ retentionPolicyMaxAge: '',
transformId: 'the-transform-id',
transformDescription: 'the-transform-description',
transformFrequency: '1m',
diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts
index 8e535e653a38..82faa8021381 100644
--- a/x-pack/plugins/transform/public/app/common/request.ts
+++ b/x-pack/plugins/transform/public/app/common/request.ts
@@ -19,7 +19,7 @@ import type {
import type { SavedSearchQuery } from '../hooks/use_search_items';
import type { StepDefineExposedState } from '../sections/create_transform/components/step_define';
-import type { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form';
+import type { StepDetailsExposedState } from '../sections/create_transform/components/step_details';
export interface SimpleQuery {
query_string: {
@@ -119,6 +119,17 @@ export const getCreateTransformRequestBody = (
},
}
: {}),
+ // conditionally add retention policy settings
+ ...(transformDetailsState.isRetentionPolicyEnabled
+ ? {
+ retention_policy: {
+ time: {
+ field: transformDetailsState.retentionPolicyDateField,
+ max_age: transformDetailsState.retentionPolicyMaxAge,
+ },
+ },
+ }
+ : {}),
// conditionally add additional settings
...getCreateTransformSettingsRequestBody(transformDetailsState),
});
diff --git a/x-pack/plugins/transform/public/app/common/validators.test.ts b/x-pack/plugins/transform/public/app/common/validators.test.ts
index 44126b8f3fa2..f48039052d20 100644
--- a/x-pack/plugins/transform/public/app/common/validators.test.ts
+++ b/x-pack/plugins/transform/public/app/common/validators.test.ts
@@ -5,7 +5,12 @@
* 2.0.
*/
-import { continuousModeDelayValidator, transformFrequencyValidator } from './validators';
+import {
+ continuousModeDelayValidator,
+ parseDuration,
+ retentionPolicyMaxAgeValidator,
+ transformFrequencyValidator,
+} from './validators';
describe('continuousModeDelayValidator', () => {
it('should allow 0 input without unit', () => {
@@ -29,6 +34,73 @@ describe('continuousModeDelayValidator', () => {
});
});
+describe('parseDuration', () => {
+ it('should return undefined when the input is not an integer and valid time unit.', () => {
+ expect(parseDuration('0')).toBe(undefined);
+ expect(parseDuration('0.1s')).toBe(undefined);
+ expect(parseDuration('1.1m')).toBe(undefined);
+ expect(parseDuration('10.1asdf')).toBe(undefined);
+ });
+
+ it('should return parsed data for valid time units nanos|micros|ms|s|m|h|d.', () => {
+ expect(parseDuration('1a')).toEqual(undefined);
+ expect(parseDuration('1nanos')).toEqual({
+ number: 1,
+ timeUnit: 'nanos',
+ });
+ expect(parseDuration('1micros')).toEqual({
+ number: 1,
+ timeUnit: 'micros',
+ });
+ expect(parseDuration('1ms')).toEqual({ number: 1, timeUnit: 'ms' });
+ expect(parseDuration('1s')).toEqual({ number: 1, timeUnit: 's' });
+ expect(parseDuration('1m')).toEqual({ number: 1, timeUnit: 'm' });
+ expect(parseDuration('1h')).toEqual({ number: 1, timeUnit: 'h' });
+ expect(parseDuration('1d')).toEqual({ number: 1, timeUnit: 'd' });
+ });
+});
+
+describe('retentionPolicyMaxAgeValidator', () => {
+ it('should fail when the input is not an integer and valid time unit.', () => {
+ expect(retentionPolicyMaxAgeValidator('0')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('0.1s')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('1.1m')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('10.1asdf')).toBe(false);
+ });
+
+ it('should only allow values equal or above 60s.', () => {
+ expect(retentionPolicyMaxAgeValidator('0nanos')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('59999999999nanos')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('60000000000nanos')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('60000000001nanos')).toBe(true);
+
+ expect(retentionPolicyMaxAgeValidator('0micros')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('59999999micros')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('60000000micros')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('60000001micros')).toBe(true);
+
+ expect(retentionPolicyMaxAgeValidator('0ms')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('59999ms')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('60000ms')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('60001ms')).toBe(true);
+
+ expect(retentionPolicyMaxAgeValidator('0s')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('1s')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('59s')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('60s')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('61s')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('10000s')).toBe(true);
+
+ expect(retentionPolicyMaxAgeValidator('0m')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('1m')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('100m')).toBe(true);
+
+ expect(retentionPolicyMaxAgeValidator('0h')).toBe(false);
+ expect(retentionPolicyMaxAgeValidator('1h')).toBe(true);
+ expect(retentionPolicyMaxAgeValidator('2h')).toBe(true);
+ });
+});
+
describe('transformFrequencyValidator', () => {
it('should fail when the input is not an integer and valid time unit.', () => {
expect(transformFrequencyValidator('0')).toBe(false);
diff --git a/x-pack/plugins/transform/public/app/common/validators.ts b/x-pack/plugins/transform/public/app/common/validators.ts
index 125a7cd714aa..065a6b4d1c0c 100644
--- a/x-pack/plugins/transform/public/app/common/validators.ts
+++ b/x-pack/plugins/transform/public/app/common/validators.ts
@@ -5,6 +5,9 @@
* 2.0.
*/
+const RETENTION_POLICY_MIN_AGE_SECONDS = 60;
+const TIME_UNITS = ['nanos', 'micros', 'ms', 's', 'm', 'h', 'd'];
+
/**
* Validates continuous mode time delay input.
* Doesn't allow floating intervals.
@@ -14,6 +17,78 @@ export function continuousModeDelayValidator(value: string): boolean {
return value.match(/^(0|\d*(nanos|micros|ms|s|m|h|d))$/) !== null;
}
+/**
+ * Parses a duration uses a string format like `60s`.
+ * @param value User input value.
+ */
+export interface ParsedDuration {
+ number: number;
+ timeUnit: string;
+}
+export function parseDuration(value: string): ParsedDuration | undefined {
+ if (typeof value !== 'string' || value === null) {
+ return;
+ }
+
+ // split string by groups of numbers and letters
+ const regexStr = value.match(/[a-z]+|[^a-z]+/gi);
+
+ // only valid if one group of numbers and one group of letters
+ if (regexStr === null || (Array.isArray(regexStr) && regexStr.length !== 2)) {
+ return;
+ }
+
+ const number = +regexStr[0];
+ const timeUnit = regexStr[1];
+
+ // only valid if number is an integer
+ if (isNaN(number) || !Number.isInteger(number)) {
+ return;
+ }
+
+ if (!TIME_UNITS.includes(timeUnit)) {
+ return;
+ }
+
+ return { number, timeUnit };
+}
+
+export function isValidRetentionPolicyMaxAge({ number, timeUnit }: ParsedDuration): boolean {
+ // only valid if value is equal or more than 60s
+ // supported time units: https://www.elastic.co/guide/en/elasticsearch/reference/master/common-options.html#time-units
+ return (
+ (timeUnit === 'nanos' && number >= RETENTION_POLICY_MIN_AGE_SECONDS * 1000000000) ||
+ (timeUnit === 'micros' && number >= RETENTION_POLICY_MIN_AGE_SECONDS * 1000000) ||
+ (timeUnit === 'ms' && number >= RETENTION_POLICY_MIN_AGE_SECONDS * 1000) ||
+ (timeUnit === 's' && number >= RETENTION_POLICY_MIN_AGE_SECONDS) ||
+ ((timeUnit === 'm' || timeUnit === 'h' || timeUnit === 'd') && number >= 1)
+ );
+}
+
+/**
+ * Validates retention policy max age input.
+ * Doesn't allow floating intervals.
+ * @param value User input value. Minimum of 60s.
+ */
+export function retentionPolicyMaxAgeValidator(value: string): boolean {
+ const parsedValue = parseDuration(value);
+
+ if (parsedValue === undefined) {
+ return false;
+ }
+
+ return isValidRetentionPolicyMaxAge(parsedValue);
+}
+
+// only valid if value is up to 1 hour
+export function isValidFrequency({ number, timeUnit }: ParsedDuration): boolean {
+ return (
+ (timeUnit === 's' && number <= 3600) ||
+ (timeUnit === 'm' && number <= 60) ||
+ (timeUnit === 'h' && number === 1)
+ );
+}
+
/**
* Validates transform frequency input.
* Allows time units of s/m/h only.
@@ -33,20 +108,15 @@ export const transformFrequencyValidator = (value: string): boolean => {
return false;
}
- const valueNumber = +regexStr[0];
- const valueTimeUnit = regexStr[1];
+ const number = +regexStr[0];
+ const timeUnit = regexStr[1];
// only valid if number is an integer above 0
- if (isNaN(valueNumber) || !Number.isInteger(valueNumber) || valueNumber === 0) {
+ if (isNaN(number) || !Number.isInteger(number) || number === 0) {
return false;
}
- // only valid if value is up to 1 hour
- return (
- (valueTimeUnit === 's' && valueNumber <= 3600) ||
- (valueTimeUnit === 'm' && valueNumber <= 60) ||
- (valueTimeUnit === 'h' && valueNumber === 1)
- );
+ return isValidFrequency({ number, timeUnit });
};
/**
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts
index 3b8df3b977ff..fbe32e9bea12 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts
@@ -5,5 +5,95 @@
* 2.0.
*/
+import type { TransformId, TransformPivotConfig } from '../../../../../../common/types/transform';
+
export type EsIndexName = string;
export type IndexPatternTitle = string;
+
+export interface StepDetailsExposedState {
+ continuousModeDateField: string;
+ continuousModeDelay: string;
+ createIndexPattern: boolean;
+ destinationIndex: EsIndexName;
+ isContinuousModeEnabled: boolean;
+ isRetentionPolicyEnabled: boolean;
+ retentionPolicyDateField: string;
+ retentionPolicyMaxAge: string;
+ touched: boolean;
+ transformId: TransformId;
+ transformDescription: string;
+ transformFrequency: string;
+ transformSettingsMaxPageSearchSize: number;
+ transformSettingsDocsPerSecond?: number;
+ valid: boolean;
+ indexPatternTimeField?: string | undefined;
+}
+
+const defaultContinuousModeDelay = '60s';
+const defaultTransformFrequency = '1m';
+const defaultTransformSettingsMaxPageSearchSize = 500;
+
+export function getDefaultStepDetailsState(): StepDetailsExposedState {
+ return {
+ continuousModeDateField: '',
+ continuousModeDelay: defaultContinuousModeDelay,
+ createIndexPattern: true,
+ isContinuousModeEnabled: false,
+ isRetentionPolicyEnabled: false,
+ retentionPolicyDateField: '',
+ retentionPolicyMaxAge: '',
+ transformId: '',
+ transformDescription: '',
+ transformFrequency: defaultTransformFrequency,
+ transformSettingsMaxPageSearchSize: defaultTransformSettingsMaxPageSearchSize,
+ destinationIndex: '',
+ touched: false,
+ valid: false,
+ indexPatternTimeField: undefined,
+ };
+}
+
+export function applyTransformConfigToDetailsState(
+ state: StepDetailsExposedState,
+ transformConfig?: TransformPivotConfig
+): StepDetailsExposedState {
+ // apply the transform configuration to wizard DETAILS state
+ if (transformConfig !== undefined) {
+ // Continuous mode
+ const continuousModeTime = transformConfig.sync?.time;
+ if (continuousModeTime !== undefined) {
+ state.continuousModeDateField = continuousModeTime.field;
+ state.continuousModeDelay = continuousModeTime?.delay ?? defaultContinuousModeDelay;
+ state.isContinuousModeEnabled = true;
+ }
+
+ // Description
+ if (transformConfig.description !== undefined) {
+ state.transformDescription = transformConfig.description;
+ }
+
+ // Frequency
+ if (transformConfig.frequency !== undefined) {
+ state.transformFrequency = transformConfig.frequency;
+ }
+
+ // Retention policy
+ const retentionPolicyTime = transformConfig.retention_policy?.time;
+ if (retentionPolicyTime !== undefined) {
+ state.retentionPolicyDateField = retentionPolicyTime.field;
+ state.retentionPolicyMaxAge = retentionPolicyTime.max_age;
+ state.isRetentionPolicyEnabled = true;
+ }
+
+ // Settings
+ if (transformConfig.settings) {
+ if (typeof transformConfig.settings?.max_page_search_size === 'number') {
+ state.transformSettingsMaxPageSearchSize = transformConfig.settings.max_page_search_size;
+ }
+ if (typeof transformConfig.settings?.docs_per_second === 'number') {
+ state.transformSettingsDocsPerSecond = transformConfig.settings.docs_per_second;
+ }
+ }
+ }
+ return state;
+}
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts
index 4b01e0c3746e..bbc4b42e1b23 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts
@@ -8,6 +8,7 @@
export {
applyTransformConfigToDetailsState,
getDefaultStepDetailsState,
- StepDetailsForm,
-} from './step_details_form';
+ StepDetailsExposedState,
+} from './common';
+export { StepDetailsForm } from './step_details_form';
export { StepDetailsSummary } from './step_details_summary';
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
index 100c37d911fa..1fa16e26565b 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { Fragment, FC, useEffect, useState } from 'react';
+import React, { FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -49,87 +49,23 @@ import {
import { EsIndexName, IndexPatternTitle } from './common';
import {
continuousModeDelayValidator,
+ retentionPolicyMaxAgeValidator,
transformFrequencyValidator,
transformSettingsMaxPageSearchSizeValidator,
} from '../../../../common/validators';
import { StepDefineExposedState } from '../step_define/common';
import { TRANSFORM_FUNCTION } from '../../../../../../common/constants';
-export interface StepDetailsExposedState {
- continuousModeDateField: string;
- continuousModeDelay: string;
- createIndexPattern: boolean;
- destinationIndex: EsIndexName;
- isContinuousModeEnabled: boolean;
- touched: boolean;
- transformId: TransformId;
- transformDescription: string;
- transformFrequency: string;
- transformSettingsMaxPageSearchSize: number;
- transformSettingsDocsPerSecond?: number;
- valid: boolean;
- indexPatternTimeField?: string | undefined;
-}
-
-const defaultContinuousModeDelay = '60s';
-const defaultTransformFrequency = '1m';
-const defaultTransformSettingsMaxPageSearchSize = 500;
-
-export function getDefaultStepDetailsState(): StepDetailsExposedState {
- return {
- continuousModeDateField: '',
- continuousModeDelay: defaultContinuousModeDelay,
- createIndexPattern: true,
- isContinuousModeEnabled: false,
- transformId: '',
- transformDescription: '',
- transformFrequency: defaultTransformFrequency,
- transformSettingsMaxPageSearchSize: defaultTransformSettingsMaxPageSearchSize,
- destinationIndex: '',
- touched: false,
- valid: false,
- indexPatternTimeField: undefined,
- };
-}
+import { getDefaultStepDetailsState, StepDetailsExposedState } from './common';
-export function applyTransformConfigToDetailsState(
- state: StepDetailsExposedState,
- transformConfig?: TransformPivotConfig
-): StepDetailsExposedState {
- // apply the transform configuration to wizard DETAILS state
- if (transformConfig !== undefined) {
- const time = transformConfig.sync?.time;
- if (time !== undefined) {
- state.continuousModeDateField = time.field;
- state.continuousModeDelay = time?.delay ?? defaultContinuousModeDelay;
- state.isContinuousModeEnabled = true;
- }
- if (transformConfig.description !== undefined) {
- state.transformDescription = transformConfig.description;
- }
- if (transformConfig.frequency !== undefined) {
- state.transformFrequency = transformConfig.frequency;
- }
- if (transformConfig.settings) {
- if (typeof transformConfig.settings?.max_page_search_size === 'number') {
- state.transformSettingsMaxPageSearchSize = transformConfig.settings.max_page_search_size;
- }
- if (typeof transformConfig.settings?.docs_per_second === 'number') {
- state.transformSettingsDocsPerSecond = transformConfig.settings.docs_per_second;
- }
- }
- }
- return state;
-}
-
-interface Props {
+interface StepDetailsFormProps {
overrides?: StepDetailsExposedState;
onChange(s: StepDetailsExposedState): void;
searchItems: SearchItems;
stepDefineState: StepDefineExposedState;
}
-export const StepDetailsForm: FC = React.memo(
+export const StepDetailsForm: FC = React.memo(
({ overrides = {}, onChange, searchItems, stepDefineState }) => {
const deps = useAppDependencies();
const toastNotifications = useToastNotifications();
@@ -171,11 +107,6 @@ export const StepDetailsForm: FC = React.memo(
[setIndexPatternTimeField, indexPatternAvailableTimeFields]
);
- // Continuous mode state
- const [isContinuousModeEnabled, setContinuousModeEnabled] = useState(
- defaults.isContinuousModeEnabled
- );
-
const api = useApi();
// fetch existing transform IDs and indices once for form validation
@@ -268,13 +199,41 @@ export const StepDetailsForm: FC = React.memo(
.filter((f) => f.type === KBN_FIELD_TYPES.DATE)
.map((f) => f.name)
.sort();
+
+ // Continuous Mode
const isContinuousModeAvailable = dateFieldNames.length > 0;
+ const [isContinuousModeEnabled, setContinuousModeEnabled] = useState(
+ defaults.isContinuousModeEnabled
+ );
const [continuousModeDateField, setContinuousModeDateField] = useState(
isContinuousModeAvailable ? dateFieldNames[0] : ''
);
const [continuousModeDelay, setContinuousModeDelay] = useState(defaults.continuousModeDelay);
const isContinuousModeDelayValid = continuousModeDelayValidator(continuousModeDelay);
+ // Retention Policy
+ const isRetentionPolicyAvailable = dateFieldNames.length > 0;
+ const [isRetentionPolicyEnabled, setRetentionPolicyEnabled] = useState(
+ defaults.isRetentionPolicyEnabled
+ );
+ const [retentionPolicyDateField, setRetentionPolicyDateField] = useState(
+ isRetentionPolicyAvailable ? dateFieldNames[0] : ''
+ );
+ const [retentionPolicyMaxAge, setRetentionPolicyMaxAge] = useState(
+ defaults.retentionPolicyMaxAge
+ );
+ const retentionPolicyMaxAgeEmpty = retentionPolicyMaxAge === '';
+ const isRetentionPolicyMaxAgeValid = retentionPolicyMaxAgeValidator(retentionPolicyMaxAge);
+
+ // Reset retention policy settings when the user disables the whole option
+ useEffect(() => {
+ if (!isRetentionPolicyEnabled) {
+ setRetentionPolicyDateField(isRetentionPolicyAvailable ? dateFieldNames[0] : '');
+ setRetentionPolicyMaxAge('');
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isRetentionPolicyEnabled]);
+
const transformIdExists = transformIds.some((id) => transformId === id);
const transformIdEmpty = transformId === '';
const transformIdValid = isTransformIdValid(transformId);
@@ -305,7 +264,13 @@ export const StepDetailsForm: FC = React.memo(
!indexNameEmpty &&
indexNameValid &&
(!indexPatternTitleExists || !createIndexPattern) &&
- (!isContinuousModeAvailable || (isContinuousModeAvailable && isContinuousModeDelayValid));
+ (!isContinuousModeAvailable || (isContinuousModeAvailable && isContinuousModeDelayValid)) &&
+ (!isRetentionPolicyAvailable ||
+ !isRetentionPolicyEnabled ||
+ (isRetentionPolicyAvailable &&
+ isRetentionPolicyEnabled &&
+ !retentionPolicyMaxAgeEmpty &&
+ isRetentionPolicyMaxAgeValid));
// expose state to wizard
useEffect(() => {
@@ -314,6 +279,9 @@ export const StepDetailsForm: FC = React.memo(
continuousModeDelay,
createIndexPattern,
isContinuousModeEnabled,
+ isRetentionPolicyEnabled,
+ retentionPolicyDateField,
+ retentionPolicyMaxAge,
transformId,
transformDescription,
transformFrequency,
@@ -331,6 +299,9 @@ export const StepDetailsForm: FC = React.memo(
continuousModeDelay,
createIndexPattern,
isContinuousModeEnabled,
+ isRetentionPolicyEnabled,
+ retentionPolicyDateField,
+ retentionPolicyMaxAge,
transformId,
transformDescription,
transformFrequency,
@@ -417,7 +388,7 @@ export const StepDetailsForm: FC = React.memo(
error={
!indexNameEmpty &&
!indexNameValid && [
-
+ <>
{i18n.translate('xpack.transform.stepDetailsForm.destinationIndexInvalidError', {
defaultMessage: 'Invalid destination index name.',
})}
@@ -430,7 +401,7 @@ export const StepDetailsForm: FC = React.memo(
}
)}
- ,
+ >,
]
}
>
@@ -502,6 +473,8 @@ export const StepDetailsForm: FC = React.memo(
onTimeFieldChanged={onTimeFieldChanged}
/>
)}
+
+ {/* Continuous mode */}
= React.memo(
/>
{isContinuousModeEnabled && (
-
+ <>
= React.memo(
)}
>
setContinuousModeDelay(e.target.value)}
aria-label={i18n.translate(
@@ -580,7 +559,100 @@ export const StepDetailsForm: FC = React.memo(
data-test-subj="transformContinuousDelayInput"
/>
-
+ >
+ )}
+
+ {/* Retention policy */}
+
+ setRetentionPolicyEnabled(!isRetentionPolicyEnabled)}
+ disabled={isRetentionPolicyAvailable === false}
+ data-test-subj="transformRetentionPolicySwitch"
+ />
+
+ {isRetentionPolicyEnabled && (
+ <>
+
+ ({ text }))}
+ value={retentionPolicyDateField}
+ onChange={(e) => setRetentionPolicyDateField(e.target.value)}
+ data-test-subj="transformRetentionPolicyDateFieldSelect"
+ />
+
+
+ setRetentionPolicyMaxAge(e.target.value)}
+ aria-label={i18n.translate(
+ 'xpack.transform.stepDetailsForm.retentionPolicyMaxAgeAriaLabel',
+ {
+ defaultMessage: 'Choose a max age.',
+ }
+ )}
+ isInvalid={!retentionPolicyMaxAgeEmpty && !isRetentionPolicyMaxAgeValid}
+ data-test-subj="transformRetentionPolicyMaxAgeInput"
+ />
+
+ >
)}
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx
index 7fb9f8ba06c0..f39132da8198 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx
@@ -11,13 +11,16 @@ import { i18n } from '@kbn/i18n';
import { EuiAccordion, EuiFormRow, EuiSpacer } from '@elastic/eui';
-import { StepDetailsExposedState } from './step_details_form';
+import { StepDetailsExposedState } from './common';
export const StepDetailsSummary: FC = React.memo((props) => {
const {
continuousModeDateField,
createIndexPattern,
isContinuousModeEnabled,
+ isRetentionPolicyEnabled,
+ retentionPolicyDateField,
+ retentionPolicyMaxAge,
transformId,
transformDescription,
transformFrequency,
@@ -85,6 +88,28 @@ export const StepDetailsSummary: FC = React.memo((props
)}
+ {isRetentionPolicyEnabled && (
+ <>
+
+ {retentionPolicyDateField}
+
+
+ {retentionPolicyMaxAge}
+
+ >
+ )}
+
= ({
+
+ dispatch({ field: 'retentionPolicyField', value })}
+ value={formFields.retentionPolicyField.value}
+ />
+
+ dispatch({ field: 'retentionPolicyMaxAge', value })}
+ value={formFields.retentionPolicyMaxAge.value}
+ />
+
+
+
+
{
});
});
-describe('Transfom: stringValidator()', () => {
+describe('Transform: stringValidator()', () => {
it('should allow an empty string for optional fields', () => {
expect(stringValidator('')).toHaveLength(0);
});
@@ -270,6 +271,43 @@ describe('Transform: frequencyValidator()', () => {
});
});
+describe('Transform: retentionPolicyMaxAgeValidator()', () => {
+ const transformRetentionPolicyMaxAgeValidator = (arg: string) =>
+ retentionPolicyMaxAgeValidator(arg).length === 0;
+
+ it('should only allow values equal or above 60s.', () => {
+ expect(transformRetentionPolicyMaxAgeValidator('0nanos')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('59999999999nanos')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('60000000000nanos')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('60000000001nanos')).toBe(true);
+
+ expect(transformRetentionPolicyMaxAgeValidator('0micros')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('59999999micros')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('60000000micros')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('60000001micros')).toBe(true);
+
+ expect(transformRetentionPolicyMaxAgeValidator('0ms')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('59999ms')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('60000ms')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('60001ms')).toBe(true);
+
+ expect(transformRetentionPolicyMaxAgeValidator('0s')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('1s')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('59s')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('60s')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('61s')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('10000s')).toBe(true);
+
+ expect(transformRetentionPolicyMaxAgeValidator('0m')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('1m')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('100m')).toBe(true);
+
+ expect(transformRetentionPolicyMaxAgeValidator('0h')).toBe(false);
+ expect(transformRetentionPolicyMaxAgeValidator('1h')).toBe(true);
+ expect(transformRetentionPolicyMaxAgeValidator('2h')).toBe(true);
+ });
+});
+
describe('Transform: integerAboveZeroValidator()', () => {
it('should only allow integers above zero', () => {
// integerAboveZeroValidator() returns an array of error messages so
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts
index a86a9cd80126..6680495bdab9 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts
@@ -16,6 +16,12 @@ import { PostTransformsUpdateRequestSchema } from '../../../../../../common/api_
import { TransformConfigUnion } from '../../../../../../common/types/transform';
import { getNestedProperty, setNestedProperty } from '../../../../../../common/utils/object_utils';
+import {
+ isValidFrequency,
+ isValidRetentionPolicyMaxAge,
+ ParsedDuration,
+} from '../../../../common/validators';
+
// This custom hook uses nested reducers to provide a generic framework to manage form state
// and apply it to a final possibly nested configuration object suitable for passing on
// directly to an API call. For now this is only used for the transform edit form.
@@ -25,21 +31,23 @@ import { getNestedProperty, setNestedProperty } from '../../../../../../common/u
// The outer most level reducer defines a flat structure of names for form fields.
// This is a flat structure regardless of whether the final request object will be nested.
// For example, `destinationIndex` and `destinationPipeline` will later be nested under `dest`.
-interface EditTransformFlyoutFieldsState {
- [key: string]: FormField;
- description: FormField;
- destinationIndex: FormField;
- destinationPipeline: FormField;
- frequency: FormField;
- docsPerSecond: FormField;
-}
+type EditTransformFormFields =
+ | 'description'
+ | 'destinationIndex'
+ | 'destinationPipeline'
+ | 'frequency'
+ | 'docsPerSecond'
+ | 'maxPageSearchSize'
+ | 'retentionPolicyField'
+ | 'retentionPolicyMaxAge';
+type EditTransformFlyoutFieldsState = Record;
// The inner reducers apply validation based on supplied attributes of each field.
export interface FormField {
formFieldName: string;
configFieldName: string;
defaultValue: string;
- dependsOn: string[];
+ dependsOn: EditTransformFormFields[];
errorMessages: string[];
isNullable: boolean;
isOptional: boolean;
@@ -122,14 +130,7 @@ export const stringValidator: Validator = (value, isOptional = true) => {
return [];
};
-// Only allow frequencies in the form of 1s/1h etc.
-const frequencyNotValidErrorMessage = i18n.translate(
- 'xpack.transform.transformList.editFlyoutFormFrequencyNotValidErrorMessage',
- {
- defaultMessage: 'The frequency value is not valid.',
- }
-);
-export const frequencyValidator: Validator = (arg) => {
+function parseDurationAboveZero(arg: any, errorMessage: string): ParsedDuration | string[] {
if (typeof arg !== 'string' || arg === null) {
return [stringNotValidErrorMessage];
}
@@ -142,20 +143,49 @@ export const frequencyValidator: Validator = (arg) => {
return [frequencyNotValidErrorMessage];
}
- const valueNumber = +regexStr[0];
- const valueTimeUnit = regexStr[1];
+ const number = +regexStr[0];
+ const timeUnit = regexStr[1];
// only valid if number is an integer above 0
- if (isNaN(valueNumber) || !Number.isInteger(valueNumber) || valueNumber === 0) {
+ if (isNaN(number) || !Number.isInteger(number) || number === 0) {
return [frequencyNotValidErrorMessage];
}
- // only valid if value is up to 1 hour
- return (valueTimeUnit === 's' && valueNumber <= 3600) ||
- (valueTimeUnit === 'm' && valueNumber <= 60) ||
- (valueTimeUnit === 'h' && valueNumber === 1)
- ? []
- : [frequencyNotValidErrorMessage];
+ return { number, timeUnit };
+}
+
+// Only allow frequencies in the form of 1s/1h etc.
+const frequencyNotValidErrorMessage = i18n.translate(
+ 'xpack.transform.transformList.editFlyoutFormFrequencyNotValidErrorMessage',
+ {
+ defaultMessage: 'The frequency value is not valid.',
+ }
+);
+export const frequencyValidator: Validator = (arg) => {
+ const parsedArg = parseDurationAboveZero(arg, frequencyNotValidErrorMessage);
+
+ if (Array.isArray(parsedArg)) {
+ return parsedArg;
+ }
+
+ return isValidFrequency(parsedArg) ? [] : [frequencyNotValidErrorMessage];
+};
+
+// Retention policy max age validator
+const retentionPolicyMaxAgeNotValidErrorMessage = i18n.translate(
+ 'xpack.transform.transformList.editFlyoutFormRetentionPolicyMaxAgeNotValidErrorMessage',
+ {
+ defaultMessage: 'Invalid max age format. Minimum of 60s required.',
+ }
+);
+export const retentionPolicyMaxAgeValidator: Validator = (arg) => {
+ const parsedArg = parseDurationAboveZero(arg, retentionPolicyMaxAgeNotValidErrorMessage);
+
+ if (Array.isArray(parsedArg)) {
+ return parsedArg;
+ }
+
+ return isValidRetentionPolicyMaxAge(parsedArg) ? [] : [retentionPolicyMaxAgeNotValidErrorMessage];
};
const validate = {
@@ -163,10 +193,11 @@ const validate = {
frequency: frequencyValidator,
integerAboveZero: integerAboveZeroValidator,
integerRange10To10000: integerRange10To10000Validator,
+ retentionPolicyMaxAge: retentionPolicyMaxAgeValidator,
} as const;
export const initializeField = (
- formFieldName: string,
+ formFieldName: EditTransformFormFields,
configFieldName: string,
config: TransformConfigUnion,
overloads?: Partial
@@ -199,7 +230,7 @@ export interface EditTransformFlyoutState {
// This is not a redux type action,
// since for now we only have one action type.
interface Action {
- field: keyof EditTransformFlyoutFieldsState;
+ field: EditTransformFormFields;
value: string;
}
@@ -207,7 +238,7 @@ interface Action {
// of the expected final configuration request object.
// Considers options like if a value is nullable or optional.
const getUpdateValue = (
- attribute: keyof EditTransformFlyoutFieldsState,
+ attribute: EditTransformFormFields,
config: TransformConfigUnion,
formState: EditTransformFlyoutFieldsState,
enforceFormValue = false
@@ -251,7 +282,7 @@ export const applyFormFieldsToTransformConfig = (
): PostTransformsUpdateRequestSchema =>
// Iterates over all form fields and only if necessary applies them to
// the request object used for updating the transform.
- Object.keys(formState).reduce(
+ (Object.keys(formState) as EditTransformFormFields[]).reduce(
(updateConfig, field) => merge({ ...updateConfig }, getUpdateValue(field, config, formState)),
{}
);
@@ -292,6 +323,25 @@ export const getDefaultState = (config: TransformConfigUnion): EditTransformFlyo
valueParser: (v) => +v,
}
),
+
+ // retention_policy.*
+ retentionPolicyField: initializeField(
+ 'retentionPolicyField',
+ 'retention_policy.time.field',
+ config,
+ { dependsOn: ['retentionPolicyMaxAge'], isNullable: false, isOptional: true }
+ ),
+ retentionPolicyMaxAge: initializeField(
+ 'retentionPolicyMaxAge',
+ 'retention_policy.time.max_age',
+ config,
+ {
+ dependsOn: ['retentionPolicyField'],
+ isNullable: false,
+ isOptional: true,
+ validator: 'retentionPolicyMaxAge',
+ }
+ ),
},
isFormTouched: false,
isFormValid: true,
@@ -300,7 +350,10 @@ export const getDefaultState = (config: TransformConfigUnion): EditTransformFlyo
// Checks each form field for error messages to return
// if the overall form is valid or not.
const isFormValid = (fieldsState: EditTransformFlyoutFieldsState) =>
- Object.keys(fieldsState).reduce((p, c) => p && fieldsState[c].errorMessages.length === 0, true);
+ (Object.keys(fieldsState) as EditTransformFormFields[]).reduce(
+ (p, c) => p && fieldsState[c].errorMessages.length === 0,
+ true
+ );
// Updates a form field with its new value,
// runs validation and populates
From 20e16bd9a4f22a97c65d95b5276ab83c240a89ca Mon Sep 17 00:00:00 2001
From: Patrick Mueller
Date: Tue, 16 Feb 2021 15:47:56 -0500
Subject: [PATCH 06/23] [alerting] add mustache variable kibanaBaseUrl for
Kibana's publicly exposed base URL (#90525)
resolves https://github.com/elastic/kibana/issues/49392
Adds the top-level mustache variable `kibanaBaseUrl` for action parameter
mustache templates. The value comes from Kibana config, which, if not set
will result in this variable having the value `undefined` which will be rendered
as an empty string.
---
x-pack/plugins/alerts/server/plugin.ts | 3 +
.../create_execution_handler.test.ts | 1 +
.../task_runner/create_execution_handler.ts | 3 +
.../server/task_runner/task_runner.test.ts | 1 +
.../alerts/server/task_runner/task_runner.ts | 3 +
.../task_runner/task_runner_factory.test.ts | 1 +
.../server/task_runner/task_runner_factory.ts | 1 +
.../task_runner/transform_action_params.ts | 3 +
.../application/lib/action_variables.test.ts | 40 ++++++
.../application/lib/action_variables.ts | 19 +++
.../alerting_api_integration/common/config.ts | 3 +-
.../security_and_spaces/config.ts | 1 +
.../tests/alerting/index.ts | 1 +
.../tests/alerting/mustache_templates.ts | 125 ++++++++++++++++++
.../tests/alerting/mustache_templates.ts | 48 +++++++
15 files changed, 252 insertions(+), 1 deletion(-)
create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts
diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts
index 8dba4453d568..5a0745d3f00b 100644
--- a/x-pack/plugins/alerts/server/plugin.ts
+++ b/x-pack/plugins/alerts/server/plugin.ts
@@ -161,6 +161,7 @@ export class AlertingPlugin {
private eventLogService?: IEventLogService;
private eventLogger?: IEventLogger;
private readonly kibanaIndexConfig: Observable<{ kibana: { index: string } }>;
+ private kibanaBaseUrl: string | undefined;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.create().pipe(first()).toPromise();
@@ -176,6 +177,7 @@ export class AlertingPlugin {
core: CoreSetup,
plugins: AlertingPluginsSetup
): PluginSetupContract {
+ this.kibanaBaseUrl = core.http.basePath.publicBaseUrl;
this.licenseState = new LicenseState(plugins.licensing.license$);
this.security = plugins.security;
@@ -371,6 +373,7 @@ export class AlertingPlugin {
eventLogger: this.eventLogger!,
internalSavedObjectsRepository: core.savedObjects.createInternalRepository(['alert']),
alertTypeRegistry: this.alertTypeRegistry!,
+ kibanaBaseUrl: this.kibanaBaseUrl,
});
this.eventLogService!.registerSavedObjectProvider('alert', (request) => {
diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts
index 4de53a38958f..120ab6de296d 100644
--- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts
+++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts
@@ -72,6 +72,7 @@ const createExecutionHandlerParams: jest.Mocked<
alertName: 'name-of-alert',
tags: ['tag-A', 'tag-B'],
apiKey: 'MTIzOmFiYw==',
+ kibanaBaseUrl: 'http://localhost:5601',
alertType,
logger: loggingSystemMock.create().get(),
eventLogger: mockEventLogger,
diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
index ad024d7ddd88..9999ea6a4d3d 100644
--- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
+++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
@@ -39,6 +39,7 @@ export interface CreateExecutionHandlerOptions<
actions: AlertAction[];
spaceId: string;
apiKey: RawAlert['apiKey'];
+ kibanaBaseUrl: string | undefined;
alertType: NormalizedAlertType<
Params,
State,
@@ -82,6 +83,7 @@ export function createExecutionHandler<
spaceId,
apiKey,
alertType,
+ kibanaBaseUrl,
eventLogger,
request,
alertParams,
@@ -126,6 +128,7 @@ export function createExecutionHandler<
context,
actionParams: action.params,
state,
+ kibanaBaseUrl,
alertParams,
}),
};
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts
index 08b288f293bd..bb5e0e583015 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts
@@ -97,6 +97,7 @@ describe('Task Runner', () => {
eventLogger: eventLoggerMock.create(),
internalSavedObjectsRepository: savedObjectsRepositoryMock.create(),
alertTypeRegistry,
+ kibanaBaseUrl: 'https://localhost:5601',
};
const mockedAlertTypeSavedObject: Alert = {
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
index 7e96cf03e061..744be1645199 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
@@ -160,6 +160,7 @@ export class TaskRunner<
tags: string[] | undefined,
spaceId: string,
apiKey: RawAlert['apiKey'],
+ kibanaBaseUrl: string | undefined,
actions: Alert['actions'],
alertParams: Params
) {
@@ -180,6 +181,7 @@ export class TaskRunner<
actions,
spaceId,
alertType: this.alertType,
+ kibanaBaseUrl,
eventLogger: this.context.eventLogger,
request: this.getFakeKibanaRequest(spaceId, apiKey),
alertParams,
@@ -388,6 +390,7 @@ export class TaskRunner<
alert.tags,
spaceId,
apiKey,
+ this.context.kibanaBaseUrl,
alert.actions,
alert.params
);
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts
index 175de7384ed4..343dffa0d5e7 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts
@@ -77,6 +77,7 @@ describe('Task Runner Factory', () => {
eventLogger: eventLoggerMock.create(),
internalSavedObjectsRepository: savedObjectsRepositoryMock.create(),
alertTypeRegistry: alertTypeRegistryMock.create(),
+ kibanaBaseUrl: 'https://localhost:5601',
};
beforeEach(() => {
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts
index 6d8a3aec9263..a023776134e9 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts
@@ -40,6 +40,7 @@ export interface TaskRunnerContext {
basePathService: IBasePath;
internalSavedObjectsRepository: ISavedObjectsRepository;
alertTypeRegistry: AlertTypeRegistry;
+ kibanaBaseUrl: string | undefined;
}
export class TaskRunnerFactory {
diff --git a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
index 7e95fee15e70..4ce30c46cd9f 100644
--- a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
+++ b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
@@ -27,6 +27,7 @@ interface TransformActionParamsOptions {
actionParams: AlertActionParams;
alertParams: AlertTypeParams;
state: AlertInstanceState;
+ kibanaBaseUrl?: string;
context: AlertInstanceContext;
}
@@ -44,6 +45,7 @@ export function transformActionParams({
context,
actionParams,
state,
+ kibanaBaseUrl,
alertParams,
}: TransformActionParamsOptions): AlertActionParams {
// when the list of variables we pass in here changes,
@@ -61,6 +63,7 @@ export function transformActionParams({
context,
date: new Date().toISOString(),
state,
+ kibanaBaseUrl,
params: alertParams,
};
return actionsPlugin.renderActionParameterTemplates(actionTypeId, actionParams, variables);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts
index 0e848d8cc078..0dca49ea5353 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts
@@ -44,10 +44,18 @@ describe('transformActionVariables', () => {
"description": "The alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroup",
},
+ Object {
+ "description": "The alert action subgroup that was used to scheduled actions for the alert.",
+ "name": "alertActionSubgroup",
+ },
Object {
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroupName",
},
+ Object {
+ "description": "The configured server.publicBaseUrl value or empty string if not configured.",
+ "name": "kibanaBaseUrl",
+ },
]
`);
});
@@ -91,10 +99,18 @@ describe('transformActionVariables', () => {
"description": "The alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroup",
},
+ Object {
+ "description": "The alert action subgroup that was used to scheduled actions for the alert.",
+ "name": "alertActionSubgroup",
+ },
Object {
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroupName",
},
+ Object {
+ "description": "The configured server.publicBaseUrl value or empty string if not configured.",
+ "name": "kibanaBaseUrl",
+ },
Object {
"description": "foo-description",
"name": "context.foo",
@@ -146,10 +162,18 @@ describe('transformActionVariables', () => {
"description": "The alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroup",
},
+ Object {
+ "description": "The alert action subgroup that was used to scheduled actions for the alert.",
+ "name": "alertActionSubgroup",
+ },
Object {
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroupName",
},
+ Object {
+ "description": "The configured server.publicBaseUrl value or empty string if not configured.",
+ "name": "kibanaBaseUrl",
+ },
Object {
"description": "foo-description",
"name": "state.foo",
@@ -204,10 +228,18 @@ describe('transformActionVariables', () => {
"description": "The alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroup",
},
+ Object {
+ "description": "The alert action subgroup that was used to scheduled actions for the alert.",
+ "name": "alertActionSubgroup",
+ },
Object {
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroupName",
},
+ Object {
+ "description": "The configured server.publicBaseUrl value or empty string if not configured.",
+ "name": "kibanaBaseUrl",
+ },
Object {
"description": "fooC-description",
"name": "context.fooC",
@@ -280,10 +312,18 @@ describe('transformActionVariables', () => {
"description": "The alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroup",
},
+ Object {
+ "description": "The alert action subgroup that was used to scheduled actions for the alert.",
+ "name": "alertActionSubgroup",
+ },
Object {
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
"name": "alertActionGroupName",
},
+ Object {
+ "description": "The configured server.publicBaseUrl value or empty string if not configured.",
+ "name": "kibanaBaseUrl",
+ },
Object {
"description": "fooC-description",
"name": "context.fooC",
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts
index 65c1145b6d1d..92be6a8685c7 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts
@@ -88,6 +88,17 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] {
}),
});
+ result.push({
+ name: 'alertActionSubgroup',
+ description: i18n.translate(
+ 'xpack.triggersActionsUI.actionVariables.alertActionSubgroupLabel',
+ {
+ defaultMessage:
+ 'The alert action subgroup that was used to scheduled actions for the alert.',
+ }
+ ),
+ });
+
result.push({
name: 'alertActionGroupName',
description: i18n.translate(
@@ -99,5 +110,13 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] {
),
});
+ result.push({
+ name: 'kibanaBaseUrl',
+ description: i18n.translate('xpack.triggersActionsUI.actionVariables.kibanaBaseUrlLabel', {
+ defaultMessage:
+ 'The configured server.publicBaseUrl value or empty string if not configured.',
+ }),
+ });
+
return result;
}
diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts
index 40a5902a32f6..385edbc66a6d 100644
--- a/x-pack/test/alerting_api_integration/common/config.ts
+++ b/x-pack/test/alerting_api_integration/common/config.ts
@@ -19,6 +19,7 @@ interface CreateTestConfigOptions {
ssl?: boolean;
enableActionsProxy: boolean;
rejectUnauthorized?: boolean;
+ publicBaseUrl?: boolean;
}
// test.not-enabled is specifically not enabled
@@ -97,7 +98,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
...xPackApiIntegrationTestsConfig.get('kbnTestServer'),
serverArgs: [
...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'),
- '--server.publicBaseUrl=https://localhost:5601',
+ ...(options.publicBaseUrl ? ['--server.publicBaseUrl=https://localhost:5601'] : []),
`--xpack.actions.allowedHosts=${JSON.stringify(['localhost', 'some.non.existent.com'])}`,
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
'--xpack.alerts.invalidateApiKeysTask.interval="15s"',
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/config.ts b/x-pack/test/alerting_api_integration/security_and_spaces/config.ts
index 2a6ce1c9fe51..314f65c16704 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/config.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/config.ts
@@ -13,4 +13,5 @@ export default createTestConfig('security_and_spaces', {
license: 'trial',
ssl: true,
enableActionsProxy: true,
+ publicBaseUrl: true,
});
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts
index 9a66db05ec71..c1f65fab3669 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts
@@ -51,6 +51,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
loadTestFile(require.resolve('./update_api_key'));
loadTestFile(require.resolve('./alerts'));
loadTestFile(require.resolve('./event_log'));
+ loadTestFile(require.resolve('./mustache_templates'));
});
});
}
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts
new file mode 100644
index 000000000000..6ac50d848572
--- /dev/null
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+/*
+ * These tests ensure that the per-action mustache template escaping works
+ * for actions we have simulators for. It arranges to have an alert that
+ * schedules an action that will contain "escapable" characters in it, and
+ * then validates that the simulator receives the escaped versions.
+ */
+
+import http from 'http';
+import getPort from 'get-port';
+import axios from 'axios';
+import httpProxy from 'http-proxy';
+
+import expect from '@kbn/expect';
+import { Spaces } from '../../scenarios';
+import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib';
+import { FtrProviderContext } from '../../../common/ftr_provider_context';
+import { getSlackServer } from '../../../common/fixtures/plugins/actions_simulators/server/plugin';
+import { getHttpProxyServer } from '../../../common/lib/get_proxy_server';
+
+// eslint-disable-next-line import/no-default-export
+export default function executionStatusAlertTests({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ const retry = getService('retry');
+ const configService = getService('config');
+
+ describe('mustacheTemplates', () => {
+ const objectRemover = new ObjectRemover(supertest);
+ let slackSimulatorURL: string = '';
+ let slackServer: http.Server;
+ let proxyServer: httpProxy | undefined;
+
+ before(async () => {
+ slackServer = await getSlackServer();
+ const availablePort = await getPort({ port: getPort.makeRange(9000, 9100) });
+ if (!slackServer.listening) {
+ slackServer.listen(availablePort);
+ }
+ slackSimulatorURL = `http://localhost:${availablePort}`;
+
+ proxyServer = await getHttpProxyServer(
+ slackSimulatorURL,
+ configService.get('kbnTestServer.serverArgs'),
+ () => {}
+ );
+ });
+
+ after(async () => {
+ await objectRemover.removeAll();
+ slackServer.close();
+
+ if (proxyServer) {
+ proxyServer.close();
+ }
+ });
+
+ it('should render kibanaBaseUrl as non-empty string since configured', async () => {
+ const actionResponse = await supertest
+ .post(`${getUrlPrefix(Spaces[0].id)}/api/actions/action`)
+ .set('kbn-xsrf', 'test')
+ .send({
+ name: 'testing context variable expansion',
+ actionTypeId: '.slack',
+ secrets: {
+ webhookUrl: slackSimulatorURL,
+ },
+ });
+ expect(actionResponse.status).to.eql(200);
+ const createdAction = actionResponse.body;
+ objectRemover.add(Spaces[0].id, createdAction.id, 'action', 'actions');
+
+ const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"';
+
+ const alertResponse = await supertest
+ .post(`${getUrlPrefix(Spaces[0].id)}/api/alerts/alert`)
+ .set('kbn-xsrf', 'foo')
+ .send(
+ getTestAlertData({
+ name: 'testing context variable kibanaBaseUrl',
+ alertTypeId: 'test.patternFiring',
+ params: {
+ pattern: { instance: [true, true] },
+ },
+ actions: [
+ {
+ id: createdAction.id,
+ group: 'default',
+ params: {
+ message: `message {{alertId}} - ${varsTemplate}`,
+ },
+ },
+ ],
+ })
+ );
+ expect(alertResponse.status).to.eql(200);
+ const createdAlert = alertResponse.body;
+ objectRemover.add(Spaces[0].id, createdAlert.id, 'alert', 'alerts');
+
+ const body = await retry.try(async () =>
+ waitForActionBody(slackSimulatorURL, createdAlert.id)
+ );
+ expect(body).to.be('kibanaBaseUrl: "https://localhost:5601"');
+ });
+ });
+
+ async function waitForActionBody(url: string, id: string): Promise {
+ const response = await axios.get(url);
+ expect(response.status).to.eql(200);
+
+ for (const datum of response.data) {
+ const match = datum.match(/^(.*) - (.*)$/);
+ if (match == null) continue;
+
+ if (match[1] === id) return match[2];
+ }
+
+ throw new Error(`no action body posted yet for id ${id}`);
+ }
+}
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts
index 7b3464b6de62..e85f9b03d269 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts
@@ -217,6 +217,54 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
'{"objectA":{"stringB":"B","arrayC":[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}],"objectF":{"stringG":"G","nullG":null}},"stringH":"H","arrayI":[44,45],"nullJ":null}'
);
});
+
+ it('should render kibanaBaseUrl as empty string since not configured', async () => {
+ const actionResponse = await supertest
+ .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`)
+ .set('kbn-xsrf', 'test')
+ .send({
+ name: 'testing context variable expansion',
+ actionTypeId: '.slack',
+ secrets: {
+ webhookUrl: slackSimulatorURL,
+ },
+ });
+ expect(actionResponse.status).to.eql(200);
+ const createdAction = actionResponse.body;
+ objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions');
+
+ const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"';
+
+ const alertResponse = await supertest
+ .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`)
+ .set('kbn-xsrf', 'foo')
+ .send(
+ getTestAlertData({
+ name: 'testing context variable kibanaBaseUrl',
+ alertTypeId: 'test.patternFiring',
+ params: {
+ pattern: { instance: [true, true] },
+ },
+ actions: [
+ {
+ id: createdAction.id,
+ group: 'default',
+ params: {
+ message: `message {{alertId}} - ${varsTemplate}`,
+ },
+ },
+ ],
+ })
+ );
+ expect(alertResponse.status).to.eql(200);
+ const createdAlert = alertResponse.body;
+ objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts');
+
+ const body = await retry.try(async () =>
+ waitForActionBody(slackSimulatorURL, createdAlert.id)
+ );
+ expect(body).to.be('kibanaBaseUrl: ""');
+ });
});
async function waitForActionBody(url: string, id: string): Promise {
From 4a661cdc35aad23e6ead7377be40f7272edc5c08 Mon Sep 17 00:00:00 2001
From: Quynh Nguyen <43350163+qn895@users.noreply.github.com>
Date: Tue, 16 Feb 2021 16:14:27 -0600
Subject: [PATCH 07/23] [ML] Fix DFA feature importance popover empty (#91061)
---
.../components/data_grid/common.ts | 86 +++++++------------
.../components/data_grid/data_grid.tsx | 2 +-
.../expandable_section.scss | 6 ++
.../decision_path_chart.tsx | 6 +-
.../decision_path_classification.tsx | 4 +-
.../decision_path_json_viewer.tsx | 2 +-
.../decision_path_popover.tsx | 8 +-
.../decision_path_regression.tsx | 4 +-
.../missing_decision_path_callout.tsx | 0
.../use_classification_path_data.test.tsx | 2 +-
.../use_classification_path_data.tsx | 4 +-
.../feature_importance.ts | 3 +-
12 files changed, 56 insertions(+), 71 deletions(-)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/decision_path_chart.tsx (95%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/decision_path_classification.tsx (97%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/decision_path_json_viewer.tsx (86%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/decision_path_popover.tsx (94%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/decision_path_regression.tsx (97%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/missing_decision_path_callout.tsx (100%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/use_classification_path_data.test.tsx (98%)
rename x-pack/plugins/ml/public/application/{components/data_grid => data_frame_analytics/pages/analytics_exploration/components}/feature_importance/use_classification_path_data.tsx (98%)
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts
index f169c56205e0..2805a28996ac 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts
@@ -27,7 +27,11 @@ import {
import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics';
import { extractErrorMessage } from '../../../../common/util/errors';
-import { FeatureImportance, TopClasses } from '../../../../common/types/feature_importance';
+import {
+ FeatureImportance,
+ FeatureImportanceClassName,
+ TopClasses,
+} from '../../../../common/types/feature_importance';
import {
BASIC_NUMERICAL_TYPES,
@@ -168,8 +172,9 @@ const getClassName = (className: string, isClassTypeBoolean: boolean) => {
return className;
};
+
/**
- * Helper to transform feature importance flattened fields with arrays back to object structure
+ * Helper to transform feature importance fields with arrays back to primitive value
*
* @param row - EUI data grid data row
* @param mlResultsField - Data frame analytics results field
@@ -180,69 +185,44 @@ export const getFeatureImportance = (
mlResultsField: string,
isClassTypeBoolean = false
): FeatureImportance[] => {
- const featureNames: string[] | undefined =
- row[`${mlResultsField}.feature_importance.feature_name`];
- const classNames: string[] | undefined =
- row[`${mlResultsField}.feature_importance.classes.class_name`];
- const classImportance: number[] | undefined =
- row[`${mlResultsField}.feature_importance.classes.importance`];
-
- if (featureNames === undefined) {
- return [];
- }
-
- // return object structure for classification job
- if (classNames !== undefined && classImportance !== undefined) {
- const overallClassNames = classNames?.slice(0, classNames.length / featureNames.length);
-
- return featureNames.map((fName, index) => {
- const offset = overallClassNames.length * index;
- const featureClassImportance = classImportance.slice(
- offset,
- offset + overallClassNames.length
- );
- return {
- feature_name: fName,
- classes: overallClassNames.map((fClassName, fIndex) => {
+ const featureImportance: Array<{
+ feature_name: string[];
+ classes?: Array<{ class_name: FeatureImportanceClassName[]; importance: number[] }>;
+ importance?: number | number[];
+ }> = row[`${mlResultsField}.feature_importance`];
+ if (featureImportance === undefined) return [];
+
+ return featureImportance.map((fi) => ({
+ feature_name: Array.isArray(fi.feature_name) ? fi.feature_name[0] : fi.feature_name,
+ classes: Array.isArray(fi.classes)
+ ? fi.classes.map((c) => {
+ const processedClass = getProcessedFields(c);
return {
- class_name: getClassName(fClassName, isClassTypeBoolean),
- importance: featureClassImportance[fIndex],
+ importance: processedClass.importance,
+ class_name: getClassName(processedClass.class_name, isClassTypeBoolean),
};
- }),
- };
- });
- }
-
- // return object structure for regression job
- const importance: number[] = row[`${mlResultsField}.feature_importance.importance`];
- return featureNames.map((fName, index) => ({
- feature_name: fName,
- importance: importance[index],
+ })
+ : fi.classes,
+ importance: Array.isArray(fi.importance) ? fi.importance[0] : fi.importance,
}));
};
/**
- * Helper to transforms top classes flattened fields with arrays back to object structure
+ * Helper to transforms top classes fields with arrays back to original primitive value
*
* @param row - EUI data grid data row
* @param mlResultsField - Data frame analytics results field
* @returns nested object structure of feature importance values
*/
export const getTopClasses = (row: Record, mlResultsField: string): TopClasses => {
- const classNames: string[] | undefined = row[`${mlResultsField}.top_classes.class_name`];
- const classProbabilities: number[] | undefined =
- row[`${mlResultsField}.top_classes.class_probability`];
- const classScores: number[] | undefined = row[`${mlResultsField}.top_classes.class_score`];
-
- if (classNames === undefined || classProbabilities === undefined || classScores === undefined) {
- return [];
- }
-
- return classNames.map((className, index) => ({
- class_name: className,
- class_probability: classProbabilities[index],
- class_score: classScores[index],
- }));
+ const topClasses: Array<{
+ class_name: FeatureImportanceClassName[];
+ class_probability: number[];
+ class_score: number[];
+ }> = row[`${mlResultsField}.top_classes`];
+
+ if (topClasses === undefined) return [];
+ return topClasses.map((tc) => getProcessedFields(tc)) as TopClasses;
};
export const useRenderCellValue = (
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
index da34e0f1bc9f..5dad9801eb64 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
+++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
@@ -35,7 +35,7 @@ import {
getTopClasses,
} from './common';
import { UseIndexDataReturnType } from './types';
-import { DecisionPathPopover } from './feature_importance/decision_path_popover';
+import { DecisionPathPopover } from '../../data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_popover';
import {
FeatureImportanceBaseline,
FeatureImportance,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section.scss
index c1c80e8dbd2c..3a548b40d3a9 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section.scss
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section.scss
@@ -5,3 +5,9 @@
.mlExpandableSection-contentPadding {
padding: $euiSizeS;
}
+
+// Make sure the charts tooltip in popover
+// have higher zIndex than Eui popover cells
+[id^='echTooltipPortal'] {
+ z-index: $euiZLevel9 !important;
+}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_chart.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_chart.tsx
similarity index 95%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_chart.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_chart.tsx
index a711d672975a..5e508df7c6ae 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_chart.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_chart.tsx
@@ -25,12 +25,12 @@ import { EuiIcon } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import euiVars from '@elastic/eui/dist/eui_theme_light.json';
-import { DecisionPathPlotData } from './use_classification_path_data';
-import { formatSingleValue } from '../../../formatters/format_value';
+import type { DecisionPathPlotData } from './use_classification_path_data';
+import { formatSingleValue } from '../../../../../formatters/format_value';
import {
FeatureImportanceBaseline,
isRegressionFeatureImportanceBaseline,
-} from '../../../../../common/types/feature_importance';
+} from '../../../../../../../common/types/feature_importance';
const { euiColorFullShade, euiColorMediumShade } = euiVars;
const axisColor = euiColorMediumShade;
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_classification.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_classification.tsx
similarity index 97%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_classification.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_classification.tsx
index 48a0c0871f68..d10755b32d7a 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_classification.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_classification.tsx
@@ -14,11 +14,11 @@ import {
useDecisionPathData,
getStringBasedClassName,
} from './use_classification_path_data';
-import {
+import type {
FeatureImportance,
FeatureImportanceBaseline,
TopClasses,
-} from '../../../../../common/types/feature_importance';
+} from '../../../../../../../common/types/feature_importance';
import { DecisionPathChart } from './decision_path_chart';
import { MissingDecisionPathCallout } from './missing_decision_path_callout';
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_json_viewer.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_json_viewer.tsx
similarity index 86%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_json_viewer.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_json_viewer.tsx
index 93b7bd6bd012..1110ef8171b9 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_json_viewer.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_json_viewer.tsx
@@ -7,7 +7,7 @@
import React, { FC } from 'react';
import { EuiCodeBlock } from '@elastic/eui';
-import { FeatureImportance } from '../../../../../common/types/feature_importance';
+import type { FeatureImportance } from '../../../../../../../common/types/feature_importance';
interface DecisionPathJSONViewerProps {
featureImportance: FeatureImportance[];
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_popover.tsx
similarity index 94%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_popover.tsx
index 3aed0f56d5a7..e1ad6a686390 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_popover.tsx
@@ -16,11 +16,11 @@ import {
isClassificationFeatureImportanceBaseline,
isRegressionFeatureImportanceBaseline,
TopClasses,
-} from '../../../../../common/types/feature_importance';
-import { ANALYSIS_CONFIG_TYPE } from '../../../data_frame_analytics/common';
+} from '../../../../../../../common/types/feature_importance';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../common';
import { ClassificationDecisionPath } from './decision_path_classification';
-import { useMlKibana } from '../../../contexts/kibana';
-import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
+import { useMlKibana } from '../../../../../contexts/kibana';
+import type { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { getStringBasedClassName } from './use_classification_path_data';
interface DecisionPathPopoverProps {
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_regression.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_regression.tsx
similarity index 97%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_regression.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_regression.tsx
index ccb7870fd79d..bb9cdd861788 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_regression.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_regression.tsx
@@ -9,11 +9,11 @@ import React, { FC, useMemo } from 'react';
import { EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import d3 from 'd3';
-import {
+import type {
FeatureImportance,
FeatureImportanceBaseline,
TopClasses,
-} from '../../../../../common/types/feature_importance';
+} from '../../../../../../../common/types/feature_importance';
import { useDecisionPathData, isDecisionPathData } from './use_classification_path_data';
import { DecisionPathChart } from './decision_path_chart';
import { MissingDecisionPathCallout } from './missing_decision_path_callout';
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/missing_decision_path_callout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/missing_decision_path_callout.tsx
similarity index 100%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/missing_decision_path_callout.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/missing_decision_path_callout.tsx
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/use_classification_path_data.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.test.tsx
similarity index 98%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/use_classification_path_data.test.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.test.tsx
index 18bc02ae6384..70c62294cae0 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/use_classification_path_data.test.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.test.tsx
@@ -9,7 +9,7 @@ import {
buildClassificationDecisionPathData,
buildRegressionDecisionPathData,
} from './use_classification_path_data';
-import { FeatureImportance } from '../../../../../common/types/feature_importance';
+import type { FeatureImportance } from '../../../../../../../common/types/feature_importance';
describe('buildClassificationDecisionPathData()', () => {
test('should return correct prediction probability for binary classification', () => {
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/use_classification_path_data.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx
similarity index 98%
rename from x-pack/plugins/ml/public/application/components/data_grid/feature_importance/use_classification_path_data.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx
index ccee43a8c971..5d61d8b3ef0c 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/use_classification_path_data.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx
@@ -14,8 +14,8 @@ import {
isClassificationFeatureImportanceBaseline,
isRegressionFeatureImportanceBaseline,
TopClasses,
-} from '../../../../../common/types/feature_importance';
-import { ExtendedFeatureImportance } from './decision_path_popover';
+} from '../../../../../../../common/types/feature_importance';
+import type { ExtendedFeatureImportance } from './decision_path_popover';
export type DecisionPathPlotData = Array<[string, number, number]>;
diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts
index b8bdc7de16e1..49728603c246 100644
--- a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts
+++ b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts
@@ -14,8 +14,7 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const ml = getService('ml');
- // Failing: See https://github.com/elastic/kibana/issues/90526
- describe.skip('total feature importance panel and decision path popover', function () {
+ describe('total feature importance panel and decision path popover', function () {
const testDataList: Array<{
suiteTitle: string;
archive: string;
From bef5674ccf03467f3a2854e08dd45d87d70318b6 Mon Sep 17 00:00:00 2001
From: Nathan L Smith
Date: Tue, 16 Feb 2021 16:37:44 -0600
Subject: [PATCH 08/23] Move ui_filters API endpoints (#90900)
---
.../index.stories.tsx | 2 +-
.../RumDashboard/hooks/useLocalUIFilters.ts | 6 +-
.../url_params_context/resolve_url_params.ts | 2 +-
.../url_params_context/url_params_context.tsx | 2 +-
.../public/hooks/use_environments_fetcher.tsx | 2 +-
.../get_environments.test.ts.snap} | 4 +-
.../lib/environments/get_all_environments.ts | 4 +
.../get_environments.test.ts} | 2 +-
.../get_environments.ts | 4 +
.../convert_ui_filters/get_es_filter.ts | 2 +-
.../__snapshots__/index.test.ts.snap} | 2 +-
.../ui_filters/local_ui_filters/config.ts | 5 +-
.../get_local_filter_query.ts | 10 +-
.../local_ui_filters/index.test.ts} | 10 +-
.../ui_filters/local_ui_filters/index.ts | 12 +-
.../apm/server/routes/create_apm_api.ts | 12 +-
.../plugins/apm/server/routes/environments.ts | 39 +++++
.../plugins/apm/server/routes/rum_client.ts | 120 ++++++++++++++-
.../plugins/apm/server/routes/ui_filters.ts | 142 ------------------
19 files changed, 197 insertions(+), 185 deletions(-)
rename x-pack/plugins/apm/server/lib/{ui_filters/__snapshots__/queries.test.ts.snap => environments/__snapshots__/get_environments.test.ts.snap} (92%)
rename x-pack/plugins/apm/server/lib/{ui_filters/queries.test.ts => environments/get_environments.test.ts} (96%)
rename x-pack/plugins/apm/server/lib/{ui_filters => environments}/get_environments.ts (95%)
rename x-pack/plugins/apm/server/lib/{ui_filters/local_ui_filters/__snapshots__/queries.test.ts.snap => rum_client/ui_filters/local_ui_filters/__snapshots__/index.test.ts.snap} (93%)
rename x-pack/plugins/apm/server/lib/{ => rum_client}/ui_filters/local_ui_filters/config.ts (89%)
rename x-pack/plugins/apm/server/lib/{ => rum_client}/ui_filters/local_ui_filters/get_local_filter_query.ts (79%)
rename x-pack/plugins/apm/server/lib/{ui_filters/local_ui_filters/queries.test.ts => rum_client/ui_filters/local_ui_filters/index.test.ts} (77%)
rename x-pack/plugins/apm/server/lib/{ => rum_client}/ui_filters/local_ui_filters/index.ts (82%)
create mode 100644 x-pack/plugins/apm/server/routes/environments.ts
delete mode 100644 x-pack/plugins/apm/server/routes/ui_filters.ts
diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx
index 0d44c08355c1..d069d4a11b49 100644
--- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx
@@ -25,7 +25,7 @@ export default {
core: {
http: {
get: (endpoint: string) => {
- if (endpoint === '/api/apm/ui_filters/environments') {
+ if (endpoint === '/api/apm/environments') {
return Promise.resolve(['production']);
} else {
return Promise.resolve({
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
index 3f366300792a..c40f6ba2b885 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
@@ -10,11 +10,11 @@ import { useHistory } from 'react-router-dom';
import { LocalUIFilterName } from '../../../../../common/ui_filter';
import { pickKeys } from '../../../../../common/utils/pick_keys';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { LocalUIFiltersAPIResponse } from '../../../../../server/lib/ui_filters/local_ui_filters';
+import { LocalUIFiltersAPIResponse } from '../../../../../server/lib/rum_client/ui_filters/local_ui_filters';
import {
localUIFilters,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../server/lib/ui_filters/local_ui_filters/config';
+} from '../../../../../server/lib/rum_client/ui_filters/local_ui_filters/config';
import {
fromQuery,
toQuery,
@@ -72,7 +72,7 @@ export function useLocalUIFilters({
(callApmApi) => {
if (shouldFetch && urlParams.start && urlParams.end) {
return callApmApi({
- endpoint: `GET /api/apm/ui_filters/local_filters/rumOverview`,
+ endpoint: 'GET /api/apm/rum/local_filters',
params: {
query: {
uiFilters: JSON.stringify(uiFilters),
diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts
index addef74f5b25..63d719205c2a 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts
+++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts
@@ -9,7 +9,7 @@ import { Location } from 'history';
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
import { pickKeys } from '../../../common/utils/pick_keys';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { localUIFilterNames } from '../../../server/lib/ui_filters/local_ui_filters/config';
+import { localUIFilterNames } from '../../../server/lib/rum_client/ui_filters/local_ui_filters/config';
import { toQuery } from '../../components/shared/Links/url_helpers';
import { TimeRangeComparisonType } from '../../components/shared/time_comparison/get_time_range_comparison';
import {
diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx
index 9cc11eef79ee..8312fedc7eb0 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx
+++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx
@@ -18,7 +18,7 @@ import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
import { LocalUIFilterName } from '../../../common/ui_filter';
import { pickKeys } from '../../../common/utils/pick_keys';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { localUIFilterNames } from '../../../server/lib/ui_filters/local_ui_filters/config';
+import { localUIFilterNames } from '../../../server/lib/rum_client/ui_filters/local_ui_filters/config';
import { UIFilters } from '../../../typings/ui_filters';
import { useDeepObjectIdentity } from '../../hooks/useDeepObjectIdentity';
import { getDateRange } from './helpers';
diff --git a/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx
index f6d3e8480247..b7dc29a36170 100644
--- a/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx
+++ b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx
@@ -36,7 +36,7 @@ export function useEnvironmentsFetcher({
(callApmApi) => {
if (start && end) {
return callApmApi({
- endpoint: 'GET /api/apm/ui_filters/environments',
+ endpoint: 'GET /api/apm/environments',
params: {
query: {
start,
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/environments/__snapshots__/get_environments.test.ts.snap
similarity index 92%
rename from x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap
rename to x-pack/plugins/apm/server/lib/environments/__snapshots__/get_environments.test.ts.snap
index 3baaefe203ce..a244eee3d054 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/environments/__snapshots__/get_environments.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ui filter queries fetches environments 1`] = `
+exports[`getEnvironments fetches environments 1`] = `
Object {
"apm": Object {
"events": Array [
@@ -44,7 +44,7 @@ Object {
}
`;
-exports[`ui filter queries fetches environments without a service name 1`] = `
+exports[`getEnvironments fetches environments without a service name 1`] = `
Object {
"apm": Object {
"events": Array [
diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
index 112700d0b658..8fedcf6224e3 100644
--- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
+++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
@@ -15,6 +15,10 @@ import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_valu
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { withApmSpan } from '../../utils/with_apm_span';
+/**
+ * This is used for getting *all* environments, and does not filter by range.
+ * It's used in places where we get the list of all possible environments.
+ */
export async function getAllEnvironments({
serviceName,
setup,
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/queries.test.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.test.ts
similarity index 96%
rename from x-pack/plugins/apm/server/lib/ui_filters/queries.test.ts
rename to x-pack/plugins/apm/server/lib/environments/get_environments.test.ts
index 4a7d2029463e..53292cabfc33 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/queries.test.ts
+++ b/x-pack/plugins/apm/server/lib/environments/get_environments.test.ts
@@ -11,7 +11,7 @@ import {
inspectSearchParams,
} from '../../utils/test_helpers';
-describe('ui filter queries', () => {
+describe('getEnvironments', () => {
let mock: SearchParamsMock;
afterEach(() => {
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.ts
similarity index 95%
rename from x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
rename to x-pack/plugins/apm/server/lib/environments/get_environments.ts
index dc7def062593..56f0a03910c1 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
+++ b/x-pack/plugins/apm/server/lib/environments/get_environments.ts
@@ -17,6 +17,10 @@ import { withApmSpan } from '../../utils/with_apm_span';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
+/**
+ * This is used for getting the list of environments for the environments selector,
+ * filtered by range.
+ */
export async function getEnvironments({
setup,
serviceName,
diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts
index 7d8bc59e6112..63e0edd6097b 100644
--- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts
@@ -11,7 +11,7 @@ import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es';
import {
localUIFilters,
localUIFilterNames,
-} from '../../ui_filters/local_ui_filters/config';
+} from '../../rum_client/ui_filters/local_ui_filters/config';
import { esKuery } from '../../../../../../../src/plugins/data/server';
export function getEsFilter(uiFilters: UIFilters) {
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/__snapshots__/index.test.ts.snap
similarity index 93%
rename from x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/__snapshots__/queries.test.ts.snap
rename to x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/__snapshots__/index.test.ts.snap
index e7ca65eb740b..40504cec36a6 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/__snapshots__/index.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`local ui filter queries fetches local ui filter aggregations 1`] = `
+exports[`getLocalUIFilters fetches local ui filter aggregations 1`] = `
Object {
"apm": Object {
"events": Array [
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/config.ts
similarity index 89%
rename from x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
rename to x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/config.ts
index 27287ce80ca3..dfe3efe2aadf 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/config.ts
@@ -5,7 +5,10 @@
* 2.0.
*/
-import { filtersByName, LocalUIFilterName } from '../../../../common/ui_filter';
+import {
+ filtersByName,
+ LocalUIFilterName,
+} from '../../../../../common/ui_filter';
export interface LocalUIFilter {
name: LocalUIFilterName;
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/get_local_filter_query.ts
similarity index 79%
rename from x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
rename to x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/get_local_filter_query.ts
index 14b6eeb78c94..8ea635467d0a 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/get_local_filter_query.ts
@@ -6,12 +6,12 @@
*/
import { omit } from 'lodash';
-import { mergeProjection } from '../../../projections/util/merge_projection';
-import { Projection } from '../../../projections/typings';
-import { UIFilters } from '../../../../typings/ui_filters';
-import { getEsFilter } from '../../helpers/convert_ui_filters/get_es_filter';
+import { mergeProjection } from '../../../../projections/util/merge_projection';
+import { Projection } from '../../../../projections/typings';
+import { UIFilters } from '../../../../../typings/ui_filters';
+import { getEsFilter } from '../../../helpers/convert_ui_filters/get_es_filter';
import { localUIFilters } from './config';
-import { LocalUIFilterName } from '../../../../common/ui_filter';
+import { LocalUIFilterName } from '../../../../../common/ui_filter';
export const getLocalFilterQuery = ({
uiFilters,
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/queries.test.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.test.ts
similarity index 77%
rename from x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/queries.test.ts
rename to x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.test.ts
index 4452a9a80d03..7254bb25cc5f 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/queries.test.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.test.ts
@@ -5,18 +5,18 @@
* 2.0.
*/
-import { getLocalUIFilters } from './';
+import { getLocalUIFilters } from '.';
import {
SearchParamsMock,
inspectSearchParams,
-} from '../../../utils/test_helpers';
-import { getServicesProjection } from '../../../projections/services';
+} from '../../../../utils/test_helpers';
+import { getServicesProjection } from '../../../../projections/services';
-describe('local ui filter queries', () => {
+describe('getLocalUIFilters', () => {
let mock: SearchParamsMock;
beforeEach(() => {
- jest.mock('../../helpers/convert_ui_filters/get_es_filter', () => {
+ jest.mock('../../../helpers/convert_ui_filters/get_es_filter', () => {
return [];
});
});
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts
similarity index 82%
rename from x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
rename to x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts
index 966f44158a7b..8fdeb7717186 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts
@@ -6,14 +6,14 @@
*/
import { cloneDeep, orderBy } from 'lodash';
-import { UIFilters } from '../../../../typings/ui_filters';
-import { Projection } from '../../../projections/typings';
-import { PromiseReturnType } from '../../../../../observability/typings/common';
+import { UIFilters } from '../../../../../typings/ui_filters';
+import { Projection } from '../../../../projections/typings';
+import { PromiseReturnType } from '../../../../../../observability/typings/common';
import { getLocalFilterQuery } from './get_local_filter_query';
-import { Setup } from '../../helpers/setup_request';
+import { Setup } from '../../../helpers/setup_request';
import { localUIFilters } from './config';
-import { LocalUIFilterName } from '../../../../common/ui_filter';
-import { withApmSpan } from '../../../utils/with_apm_span';
+import { LocalUIFilterName } from '../../../../../common/ui_filter';
+import { withApmSpan } from '../../../../utils/with_apm_span';
export type LocalUIFiltersAPIResponse = PromiseReturnType<
typeof getLocalUIFilters
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index d22bcb1c501e..fc5d6a3dd0bc 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -11,6 +11,7 @@ import {
apmIndexPatternTitleRoute,
} from './index_pattern';
import { createApi } from './create_api';
+import { environmentsRoute } from './environments';
import {
errorDistributionRoute,
errorGroupsRoute,
@@ -66,10 +67,6 @@ import {
transactionThroughputChatsRoute,
transactionGroupsComparisonStatisticsRoute,
} from './transactions';
-import {
- rumOverviewLocalFiltersRoute,
- uiFiltersEnvironmentsRoute,
-} from './ui_filters';
import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map';
import {
createCustomLinkRoute,
@@ -92,6 +89,7 @@ import {
rumClientMetricsRoute,
rumJSErrors,
rumLongTaskMetrics,
+ rumOverviewLocalFiltersRoute,
rumPageLoadDistBreakdownRoute,
rumPageLoadDistributionRoute,
rumPageViewsTrendRoute,
@@ -113,6 +111,9 @@ const createApmApi = () => {
.add(dynamicIndexPatternRoute)
.add(apmIndexPatternTitleRoute)
+ // Environments
+ .add(environmentsRoute)
+
// Errors
.add(errorDistributionRoute)
.add(errorGroupsRoute)
@@ -170,9 +171,6 @@ const createApmApi = () => {
.add(transactionThroughputChatsRoute)
.add(transactionGroupsComparisonStatisticsRoute)
- // UI filters
- .add(uiFiltersEnvironmentsRoute)
-
// Service map
.add(serviceMapRoute)
.add(serviceMapServiceNodeRoute)
diff --git a/x-pack/plugins/apm/server/routes/environments.ts b/x-pack/plugins/apm/server/routes/environments.ts
new file mode 100644
index 000000000000..448591f7e143
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/environments.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as t from 'io-ts';
+import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
+import { setupRequest } from '../lib/helpers/setup_request';
+import { getEnvironments } from '../lib/environments/get_environments';
+import { createRoute } from './create_route';
+import { rangeRt } from './default_api_types';
+
+export const environmentsRoute = createRoute({
+ endpoint: 'GET /api/apm/environments',
+ params: t.type({
+ query: t.intersection([
+ t.partial({
+ serviceName: t.string,
+ }),
+ rangeRt,
+ ]),
+ }),
+ options: { tags: ['access:apm'] },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+ const { serviceName } = context.params.query;
+ const searchAggregatedTransactions = await getSearchAggregatedTransactions(
+ setup
+ );
+
+ return getEnvironments({
+ setup,
+ serviceName,
+ searchAggregatedTransactions,
+ });
+ },
+});
diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts
index 69e169e96af7..c9fa4253bb58 100644
--- a/x-pack/plugins/apm/server/routes/rum_client.ts
+++ b/x-pack/plugins/apm/server/routes/rum_client.ts
@@ -6,20 +6,33 @@
*/
import * as t from 'io-ts';
-import { createRoute } from './create_route';
-import { setupRequest } from '../lib/helpers/setup_request';
+import { omit } from 'lodash';
+import { jsonRt } from '../../common/runtime_types/json_rt';
+import { LocalUIFilterName } from '../../common/ui_filter';
+import { getEsFilter } from '../lib/helpers/convert_ui_filters/get_es_filter';
+import {
+ Setup,
+ setupRequest,
+ SetupTimeRange,
+} from '../lib/helpers/setup_request';
import { getClientMetrics } from '../lib/rum_client/get_client_metrics';
-import { rangeRt, uiFiltersRt } from './default_api_types';
-import { getPageViewTrends } from '../lib/rum_client/get_page_view_trends';
+import { getJSErrors } from '../lib/rum_client/get_js_errors';
+import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics';
import { getPageLoadDistribution } from '../lib/rum_client/get_page_load_distribution';
+import { getPageViewTrends } from '../lib/rum_client/get_page_view_trends';
import { getPageLoadDistBreakdown } from '../lib/rum_client/get_pl_dist_breakdown';
import { getRumServices } from '../lib/rum_client/get_rum_services';
+import { getUrlSearch } from '../lib/rum_client/get_url_search';
import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown';
import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals';
-import { getJSErrors } from '../lib/rum_client/get_js_errors';
-import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics';
-import { getUrlSearch } from '../lib/rum_client/get_url_search';
import { hasRumData } from '../lib/rum_client/has_rum_data';
+import { getLocalUIFilters } from '../lib/rum_client/ui_filters/local_ui_filters';
+import { localUIFilterNames } from '../lib/rum_client/ui_filters/local_ui_filters/config';
+import { getRumPageLoadTransactionsProjection } from '../projections/rum_page_load_transactions';
+import { Projection } from '../projections/typings';
+import { createRoute } from './create_route';
+import { rangeRt, uiFiltersRt } from './default_api_types';
+import { APMRequestHandlerContext } from './typings';
export const percentileRangeRt = t.partial({
minPercentile: t.string,
@@ -253,3 +266,96 @@ export const rumHasDataRoute = createRoute({
return await hasRumData({ setup });
},
});
+
+// Everything below here was originally in ui_filters.ts but now is here, since
+// UX is the only part of APM using UI filters now.
+
+const filterNamesRt = t.type({
+ filterNames: jsonRt.pipe(
+ t.array(
+ t.keyof(
+ Object.fromEntries(
+ localUIFilterNames.map((filterName) => [filterName, null])
+ ) as Record
+ )
+ )
+ ),
+});
+
+const localUiBaseQueryRt = t.intersection([
+ filterNamesRt,
+ uiFiltersRt,
+ rangeRt,
+]);
+
+function createLocalFiltersRoute<
+ TEndpoint extends string,
+ TProjection extends Projection,
+ TQueryRT extends t.HasProps
+>({
+ endpoint,
+ getProjection,
+ queryRt,
+}: {
+ endpoint: TEndpoint;
+ getProjection: GetProjection<
+ TProjection,
+ t.IntersectionC<[TQueryRT, BaseQueryType]>
+ >;
+ queryRt: TQueryRT;
+}) {
+ return createRoute({
+ endpoint,
+ params: t.type({
+ query: t.intersection([localUiBaseQueryRt, queryRt]),
+ }),
+ options: { tags: ['access:apm'] },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+ const { uiFilters } = setup;
+ const { query } = context.params;
+
+ const { filterNames } = query;
+ const projection = await getProjection({
+ query,
+ context,
+ setup: {
+ ...setup,
+ esFilter: getEsFilter(omit(uiFilters, filterNames)),
+ },
+ });
+
+ return getLocalUIFilters({
+ projection,
+ setup,
+ uiFilters,
+ localFilterNames: filterNames,
+ });
+ },
+ });
+}
+
+export const rumOverviewLocalFiltersRoute = createLocalFiltersRoute({
+ endpoint: 'GET /api/apm/rum/local_filters',
+ getProjection: async ({ setup }) => {
+ return getRumPageLoadTransactionsProjection({
+ setup,
+ });
+ },
+ queryRt: t.type({}),
+});
+
+type BaseQueryType = typeof localUiBaseQueryRt;
+
+type GetProjection<
+ TProjection extends Projection,
+ TQueryRT extends t.HasProps
+> = ({
+ query,
+ setup,
+ context,
+}: {
+ query: t.TypeOf;
+ setup: Setup & SetupTimeRange;
+ context: APMRequestHandlerContext;
+}) => Promise | TProjection;
diff --git a/x-pack/plugins/apm/server/routes/ui_filters.ts b/x-pack/plugins/apm/server/routes/ui_filters.ts
deleted file mode 100644
index b14a47e302ca..000000000000
--- a/x-pack/plugins/apm/server/routes/ui_filters.ts
+++ /dev/null
@@ -1,142 +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 * as t from 'io-ts';
-import { omit } from 'lodash';
-import { jsonRt } from '../../common/runtime_types/json_rt';
-import { LocalUIFilterName } from '../../common/ui_filter';
-import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
-import { getEsFilter } from '../lib/helpers/convert_ui_filters/get_es_filter';
-import {
- Setup,
- setupRequest,
- SetupTimeRange,
-} from '../lib/helpers/setup_request';
-import { getEnvironments } from '../lib/ui_filters/get_environments';
-import { getLocalUIFilters } from '../lib/ui_filters/local_ui_filters';
-import { localUIFilterNames } from '../lib/ui_filters/local_ui_filters/config';
-import { getRumPageLoadTransactionsProjection } from '../projections/rum_page_load_transactions';
-import { Projection } from '../projections/typings';
-import { createRoute } from './create_route';
-import { rangeRt, uiFiltersRt } from './default_api_types';
-import { APMRequestHandlerContext } from './typings';
-
-export const uiFiltersEnvironmentsRoute = createRoute({
- endpoint: 'GET /api/apm/ui_filters/environments',
- params: t.type({
- query: t.intersection([
- t.partial({
- serviceName: t.string,
- }),
- rangeRt,
- ]),
- }),
- options: { tags: ['access:apm'] },
- handler: async ({ context, request }) => {
- const setup = await setupRequest(context, request);
- const { serviceName } = context.params.query;
- const searchAggregatedTransactions = await getSearchAggregatedTransactions(
- setup
- );
-
- return getEnvironments({
- setup,
- serviceName,
- searchAggregatedTransactions,
- });
- },
-});
-
-const filterNamesRt = t.type({
- filterNames: jsonRt.pipe(
- t.array(
- t.keyof(
- Object.fromEntries(
- localUIFilterNames.map((filterName) => [filterName, null])
- ) as Record
- )
- )
- ),
-});
-
-const localUiBaseQueryRt = t.intersection([
- filterNamesRt,
- uiFiltersRt,
- rangeRt,
-]);
-
-function createLocalFiltersRoute<
- TEndpoint extends string,
- TProjection extends Projection,
- TQueryRT extends t.HasProps
->({
- endpoint,
- getProjection,
- queryRt,
-}: {
- endpoint: TEndpoint;
- getProjection: GetProjection<
- TProjection,
- t.IntersectionC<[TQueryRT, BaseQueryType]>
- >;
- queryRt: TQueryRT;
-}) {
- return createRoute({
- endpoint,
- params: t.type({
- query: t.intersection([localUiBaseQueryRt, queryRt]),
- }),
- options: { tags: ['access:apm'] },
- handler: async ({ context, request }) => {
- const setup = await setupRequest(context, request);
- const { uiFilters } = setup;
- const { query } = context.params;
-
- const { filterNames } = query;
- const projection = await getProjection({
- query,
- context,
- setup: {
- ...setup,
- esFilter: getEsFilter(omit(uiFilters, filterNames)),
- },
- });
-
- return getLocalUIFilters({
- projection,
- setup,
- uiFilters,
- localFilterNames: filterNames,
- });
- },
- });
-}
-
-export const rumOverviewLocalFiltersRoute = createLocalFiltersRoute({
- endpoint: 'GET /api/apm/ui_filters/local_filters/rumOverview',
- getProjection: async ({ setup }) => {
- return getRumPageLoadTransactionsProjection({
- setup,
- });
- },
- queryRt: t.type({}),
-});
-
-type BaseQueryType = typeof localUiBaseQueryRt;
-
-type GetProjection<
- TProjection extends Projection,
- TQueryRT extends t.HasProps
-> = ({
- query,
- setup,
- context,
-}: {
- query: t.TypeOf;
- setup: Setup & SetupTimeRange;
- context: APMRequestHandlerContext;
-}) => Promise | TProjection;
From f3982444250b8e72c4c56fa97d371787ac4171ce Mon Sep 17 00:00:00 2001
From: Rashmi Kulkarni
Date: Tue, 16 Feb 2021 14:52:38 -0800
Subject: [PATCH 09/23] unskip navigation test (#91306)
* fixes https://github.com/elastic/kibana/issues/74449
* unskip navigation test
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
test/functional/apps/home/_navigation.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts
index 3a59f45cef8f..401f33b789c8 100644
--- a/test/functional/apps/home/_navigation.ts
+++ b/test/functional/apps/home/_navigation.ts
@@ -15,8 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const appsMenu = getService('appsMenu');
const esArchiver = getService('esArchiver');
- // Failing: See https://github.com/elastic/kibana/issues/88826
- describe.skip('Kibana browser back navigation should work', function describeIndexTests() {
+ describe('Kibana browser back navigation should work', function describeIndexTests() {
before(async () => {
await esArchiver.loadIfNeeded('discover');
await esArchiver.loadIfNeeded('logstash_functional');
From b9bccf33aaf0808d9dade6198e48669a98a81815 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Tue, 16 Feb 2021 15:23:06 -0800
Subject: [PATCH 10/23] [CI] Increase pipeline timeouts (#91587)
Signed-off-by: Tyler Smalley
---
.ci/es-snapshots/Jenkinsfile_verify_es | 2 +-
Jenkinsfile | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es
index 736a71b73d14..f3f07d5f355b 100644
--- a/.ci/es-snapshots/Jenkinsfile_verify_es
+++ b/.ci/es-snapshots/Jenkinsfile_verify_es
@@ -19,7 +19,7 @@ currentBuild.description = "ES: ${SNAPSHOT_VERSION}
Kibana: ${params.branch
def SNAPSHOT_MANIFEST = "https://storage.googleapis.com/kibana-ci-es-snapshots-daily/${SNAPSHOT_VERSION}/archives/${SNAPSHOT_ID}/manifest.json"
-kibanaPipeline(timeoutMinutes: 150) {
+kibanaPipeline(timeoutMinutes: 210) {
catchErrors {
slackNotifications.onFailure(
title: "*<${env.BUILD_URL}|[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure>*",
diff --git a/Jenkinsfile b/Jenkinsfile
index 3b68cde20657..8ab3fecb07a1 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -3,7 +3,7 @@
library 'kibana-pipeline-library'
kibanaLibrary.load()
-kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) {
+kibanaPipeline(timeoutMinutes: 210, checkPrChanges: true, setCommitStatus: true) {
slackNotifications.onFailure(disabled: !params.NOTIFY_ON_FAILURE) {
githubPr.withDefaultPrComments {
ciStats.trackBuild {
From 79176327692efe6756fe8f1846cfd4502fceaf32 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Tue, 16 Feb 2021 16:47:31 -0700
Subject: [PATCH 11/23] [Maps] disable maps interactions when using search
session (#91229)
* [Maps] disable maps interactions when using search session restore
* tslint
* tslint
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../connected_components/mb_map/mb_map.tsx | 28 ++++++++++++++++---
.../maps/public/embeddable/map_embeddable.tsx | 22 +++++++++++++++
2 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
index 25bd589cda65..5ca370f7d54c 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
@@ -84,6 +84,8 @@ export class MBMap extends Component {
private _checker?: ResizeChecker;
private _isMounted: boolean = false;
private _containerRef: HTMLDivElement | null = null;
+ private _prevDisableInteractive?: boolean;
+ private _navigationControl = new mapboxgl.NavigationControl({ showCompass: false });
state: State = {
prevLayerList: undefined,
@@ -181,7 +183,6 @@ export class MBMap extends Component {
style: mbStyle,
scrollZoom: this.props.scrollZoom,
preserveDrawingBuffer: getPreserveDrawingBuffer(),
- interactive: !this.props.settings.disableInteractive,
maxZoom: this.props.settings.maxZoom,
minZoom: this.props.settings.minZoom,
};
@@ -197,9 +198,6 @@ export class MBMap extends Component {
const mbMap = new mapboxgl.Map(options);
mbMap.dragRotate.disable();
mbMap.touchZoomRotate.disableRotation();
- if (!this.props.settings.disableInteractive) {
- mbMap.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-left');
- }
const tooManyFeaturesImageSrc =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAA7DgAAOw4BzLahgwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAARLSURBVHic7ZnPbxRVAMe/7735sWO3293ZlUItJsivCxEE0oTYRgu1FqTQoFSwKTYx8SAH/wHjj4vRozGGi56sMcW2UfqTEuOhppE0KJc2GIuKQFDY7qzdtrudX88D3YTUdFuQN8+k87ltZt7uZz958/bNLAGwBWsYKltANmEA2QKyCQPIFpBNGEC2gGzCALIFZBMGkC0gmzCAbAHZhAFkC8gmDCBbQDZhANkCslnzARQZH6oDpNs0D5UDSUIInePcOpPLfdfnODNBuwQWIAWwNOABwHZN0x8npE6hNLJ4DPWRyFSf40wE5VOEQPBjcR0g3YlE4ybGmtK+/1NzJtOZA/xSYwZMs3nG962T2ez3It2AANaA/kSidYuivOQBs5WM1fUnk6f0u+GXJUqIuUtVXx00zRbRfkIDfBqL7a1WlIYbjvNtTTr99jXXHVpH6dMjK0R4cXq6c9rzxjcx9sKX8XitSEdhAToMI7VP10/97fsTh7PZrgWAN1lW72KE2vOm2b5chDTgtWQyn93x/bEEIetEOQIC14CxVOr1CkKefH929t0v8vn0vcdGEoljGxXl4C3PGz2YyXy+AHARDqtByAxoUdWKBKV70r4/vvTLA0CjZfX+5nkDGxirKzUTgkBIgNaysh3gnF627R+XO+dQJvP1ddcdrmSsbtA020pF+CAW21qrqmUiXIUEqGRsIwD0FQq/lzqv0bJ6rrvucBVjzwyb5ivLRTiiaW+8VV7eIEBVTAANiIIQd9RxZlc6t9Gyem647vn1jD07ZJonl4sQASoevqmgABzwwHnJzc69PGdZ3X+47sgGxuqHTPPE0ggeVtg5/QeEBMhxPg1Aa1DV2GrHPG9ZXy1G2D+wNALn9jyQEeHKAJgP+033Kgrdqij7AFwZtu3bqx3XWShMHtV1o1pRGo4YxiNd+fyEB2DKdX/4aG5u0hbwcylkBryTy/3scT6zW9Nq7ndso2Wdvea6Q1WUHuiPx1/WAXLBcWZXun94UMRcAoD/p+ddTFK6u8MwUvc7vsmyem+67oVqVT0wkEgcF+FYRNhW+L25uX6f84XThtHxIBudE5bVY/t++jFVrU/dvVSFICzAqG3PX/S8rihj2/61qK1AOUB7ksl2jdLUL7Z9rvgcQQRCFsEi5wqFmw26XnhCUQ63GcZmCly95Lrzpca0G0byk3j8tEnpU1c975tmyxoU5QcE8EAEAM5WVOzfoarHAeC2749dcpzxMwsLv07Ztg0AOzVNf03Ttu/S9T2PMlbjc25fdpyutmx2TLRbIAEA4M1otKo1EjmaoHQn4ZwBgA/kAVAK6MXXdzxv/ONcrq/HcbJBeAUWoEizqsaORaPbKglZrxMSZZyrM76f/ovzWx/m85PFWREUgQf4v7Hm/xcIA8gWkE0YQLaAbMIAsgVkEwaQLSCbMIBsAdmEAWQLyCYMIFtANmEA2QKyCQPIFpDNmg/wD3OFdEybUvJjAAAAAElFTkSuQmCC';
@@ -357,6 +355,28 @@ export class MBMap extends Component {
return;
}
+ if (
+ this._prevDisableInteractive === undefined ||
+ this._prevDisableInteractive !== this.props.settings.disableInteractive
+ ) {
+ this._prevDisableInteractive = this.props.settings.disableInteractive;
+ if (this.props.settings.disableInteractive) {
+ this.state.mbMap.boxZoom.disable();
+ this.state.mbMap.doubleClickZoom.disable();
+ this.state.mbMap.dragPan.disable();
+ try {
+ this.state.mbMap.removeControl(this._navigationControl);
+ } catch (error) {
+ // ignore removeControl errors
+ }
+ } else {
+ this.state.mbMap.boxZoom.enable();
+ this.state.mbMap.doubleClickZoom.enable();
+ this.state.mbMap.dragPan.enable();
+ this.state.mbMap.addControl(this._navigationControl, 'top-left');
+ }
+ }
+
let zoomRangeChanged = false;
if (this.props.settings.minZoom !== this.state.mbMap.getMinZoom()) {
this.state.mbMap.setMinZoom(this.props.settings.minZoom);
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
index b769ac489f56..f42a055b24d0 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -29,6 +29,7 @@ import {
} from '../../../../../src/plugins/data/public';
import {
replaceLayerList,
+ setMapSettings,
setQuery,
setRefreshConfig,
disableScrollZoom,
@@ -60,6 +61,7 @@ import {
getCoreI18n,
getHttp,
getChartsPaletteServiceGetColor,
+ getSearchService,
} from '../kibana_services';
import { LayerDescriptor } from '../../common/descriptor_types';
import { MapContainer } from '../connected_components/map_container';
@@ -77,6 +79,14 @@ import {
} from './types';
export { MapEmbeddableInput, MapEmbeddableOutput };
+function getIsRestore(searchSessionId?: string) {
+ if (!searchSessionId) {
+ return false;
+ }
+ const searchSessionOptions = getSearchService().session.getSearchOptions(searchSessionId);
+ return searchSessionOptions ? searchSessionOptions.isRestore : false;
+}
+
export class MapEmbeddable
extends Embeddable
implements ReferenceOrValueEmbeddable {
@@ -85,6 +95,7 @@ export class MapEmbeddable
private _savedMap: SavedMap;
private _renderTooltipContent?: RenderToolTipContent;
private _subscription: Subscription;
+ private _prevIsRestore: boolean = false;
private _prevTimeRange?: TimeRange;
private _prevQuery?: Query;
private _prevRefreshConfig?: RefreshInterval;
@@ -234,6 +245,17 @@ export class MapEmbeddable
if (this.input.syncColors !== this._prevSyncColors) {
this._dispatchSetChartsPaletteServiceGetColor(this.input.syncColors);
}
+
+ const isRestore = getIsRestore(this.input.searchSessionId);
+ if (isRestore !== this._prevIsRestore) {
+ this._prevIsRestore = isRestore;
+ this._savedMap.getStore().dispatch(
+ setMapSettings({
+ disableInteractive: isRestore,
+ hideToolbarOverlay: isRestore,
+ })
+ );
+ }
}
_dispatchSetQuery({
From 5b97d522b6cfbebddbdfd1e08ef3d390c3c9b45a Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Tue, 16 Feb 2021 18:25:31 -0600
Subject: [PATCH 12/23] [Security Solution][Detections] Adds Indicator path
config for indicator match rules (#91260)
* Add new field for overriding threat indicator path
There is no UI for this currently, nor is it used during rule execution.
* Adds form field for indicator path parameter
Also adds missing plumbing that was preventing the new field from being
persisted to the alert/returned in the response.
* Wire up our indicator path config to enrichment
* Add unit test for enriching from a custom indicator path
We always persist to `threat.indicator.*` on the signal, but this allows
users to specify where the enrichment fields can be found on the matched
indicator document.
* Wire up the missing piece of our indicator path config
We were not passing this from the rule itself into the threat matching
logic, and so were merely getting the default value.
An integration test will fix this. Incoming!
* Move indicator path defaulting outside of helper functions
This happens closer to where we pass data from the rule to our helpers,
and will prevent errors/bugs due to defaulting logic down the road.
It makes tests a little more verbose, but that's okay.
* Fix remaining type errors around new rule field
* Make threat indicator path a conditional field
Always sending along this field, but only allowing it for threat match
rules was implicitly breaking the workflow of otther rule types. By
making the field conditional on the rule type, this field only impacts
threat match rules.
This also fixes some types and tests accordingly.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../request/add_prepackaged_rules_schema.ts | 2 +
.../schemas/request/import_rules_schema.ts | 2 +
.../schemas/request/patch_rules_schema.ts | 2 +
.../schemas/request/rule_schemas.mock.ts | 1 +
.../schemas/request/rule_schemas.test.ts | 2 +-
.../schemas/request/rule_schemas.ts | 2 +
.../schemas/response/rules_schema.mocks.ts | 1 +
.../schemas/response/rules_schema.test.ts | 4 +-
.../schemas/response/rules_schema.ts | 5 +
.../schemas/types/threat_mapping.ts | 5 +
.../common/detection_engine/utils.ts | 3 +-
.../rules/step_about_rule/default_value.ts | 1 +
.../rules/step_about_rule/index.tsx | 18 ++++
.../rules/step_about_rule/schema.tsx | 17 ++++
.../detection_engine/rules/types.ts | 2 +
.../rules/create/helpers.test.ts | 9 ++
.../detection_engine/rules/create/helpers.ts | 2 +
.../detection_engine/rules/helpers.test.tsx | 1 +
.../pages/detection_engine/rules/helpers.tsx | 2 +
.../pages/detection_engine/rules/types.ts | 2 +
.../routes/__mocks__/request_responses.ts | 1 +
.../routes/rules/import_rules_route.ts | 2 +
.../detection_engine/routes/rules/utils.ts | 1 +
.../rules/create_rules.mock.ts | 2 +
.../detection_engine/rules/create_rules.ts | 2 +
.../rules/install_prepacked_rules.ts | 2 +
.../rules/patch_rules.mock.ts | 1 +
.../lib/detection_engine/rules/types.ts | 2 +
.../schemas/rule_converters.ts | 1 +
.../detection_engine/schemas/rule_schemas.ts | 2 +
.../signals/__mocks__/es_results.ts | 1 +
.../detection_engine/signals/build_rule.ts | 1 +
.../signals/signal_params_schema.ts | 1 +
.../signals/signal_rule_alert_type.ts | 2 +
.../threat_mapping/build_threat_enrichment.ts | 6 +-
.../threat_mapping/create_threat_signals.ts | 2 +
.../enrich_signal_threat_matches.test.ts | 91 +++++++++++++++++--
.../enrich_signal_threat_matches.ts | 14 ++-
.../signals/threat_mapping/types.ts | 3 +
.../server/lib/detection_engine/types.ts | 2 +
40 files changed, 204 insertions(+), 18 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts
index b76a762ca6cb..981a5422a059 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts
@@ -55,6 +55,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import {
@@ -133,6 +134,7 @@ export const addPrepackagedRulesSchema = t.intersection([
threat_query, // defaults to "undefined" if not set during decode
threat_index, // defaults to "undefined" if not set during decode
threat_language, // defaults "undefined" if not set during decode
+ threat_indicator_path, // defaults "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
})
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts
index 0a7b8b120ba7..8fa5809abe68 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts
@@ -62,6 +62,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import {
@@ -152,6 +153,7 @@ export const importRulesSchema = t.intersection([
threat_query, // defaults to "undefined" if not set during decode
threat_index, // defaults to "undefined" if not set during decode
threat_language, // defaults "undefined" if not set during decode
+ threat_indicator_path, // defaults to "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
})
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts
index 9d5331aeab8e..920fbaf4915c 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts
@@ -57,6 +57,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import { listArrayOrUndefined } from '../types/lists';
@@ -112,6 +113,7 @@ export const patchRulesSchema = t.exact(
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
concurrent_searches,
items_per_search,
})
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts
index 87e5acb5428d..fb29e37a53fd 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts
@@ -56,6 +56,7 @@ export const getCreateThreatMatchRulesSchemaMock = (
rule_id: ruleId,
threat_query: '*:*',
threat_index: ['list-index'],
+ threat_indicator_path: 'threat.indicator',
threat_mapping: [
{
entries: [
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts
index 14b47c8b2b32..6b8211b23088 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts
@@ -1152,7 +1152,7 @@ describe('create rules schema', () => {
});
});
- describe('threat_mapping', () => {
+ describe('threat_match', () => {
test('You can set a threat query, index, mapping, filters when creating a rule', () => {
const payload = getCreateThreatMatchRulesSchemaMock();
const decoded = createRulesSchema.decode(payload);
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts
index 1c9ebe003331..5cf2b6242b2f 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts
@@ -13,6 +13,7 @@ import {
threat_query,
threat_mapping,
threat_index,
+ threat_indicator_path,
concurrent_searches,
items_per_search,
} from '../types/threat_mapping';
@@ -213,6 +214,7 @@ const threatMatchRuleParams = {
filters,
saved_id,
threat_filters,
+ threat_indicator_path,
threat_language: t.keyof({ kuery: null, lucene: null }),
concurrent_searches,
items_per_search,
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts
index b14c646e862d..cf07389e207b 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts
@@ -150,6 +150,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial {
expect(fields).toEqual(expected);
});
- test('should return 8 fields for a rule of type "threat_match"', () => {
+ test('should return nine (9) fields for a rule of type "threat_match"', () => {
const fields = addThreatMatchFields({ type: 'threat_match' });
- expect(fields.length).toEqual(8);
+ expect(fields.length).toEqual(9);
});
});
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
index bcdb0fa9b085..6bd54973e064 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
@@ -70,6 +70,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import { DefaultListArray } from '../types/lists_default_array';
@@ -151,6 +152,7 @@ export const dependentRulesSchema = t.partial({
items_per_search,
threat_mapping,
threat_language,
+ threat_indicator_path,
});
/**
@@ -286,6 +288,9 @@ export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly):
t.exact(t.type({ threat_mapping: dependentRulesSchema.props.threat_mapping })),
t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })),
t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })),
+ t.exact(
+ t.partial({ threat_indicator_path: dependentRulesSchema.props.threat_indicator_path })
+ ),
t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })),
t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })),
t.exact(
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts
index d3975df488de..aab06941686c 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts
@@ -18,6 +18,11 @@ export type ThreatQuery = t.TypeOf;
export const threatQueryOrUndefined = t.union([threat_query, t.undefined]);
export type ThreatQueryOrUndefined = t.TypeOf;
+export const threat_indicator_path = t.string;
+export type ThreatIndicatorPath = t.TypeOf;
+export const threatIndicatorPathOrUndefined = t.union([threat_indicator_path, t.undefined]);
+export type ThreatIndicatorPathOrUndefined = t.TypeOf;
+
export const threat_filters = t.array(t.unknown); // Filters are not easily type-able yet
export type ThreatFilters = t.TypeOf;
export const threatFiltersOrUndefined = t.union([threat_filters, t.undefined]);
diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
index 080b704e9c19..725a2eb9fea7 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
@@ -30,4 +30,5 @@ export const isEqlRule = (ruleType: Type | undefined): boolean => ruleType === '
export const isThresholdRule = (ruleType: Type | undefined): boolean => ruleType === 'threshold';
export const isQueryRule = (ruleType: Type | undefined): boolean =>
ruleType === 'query' || ruleType === 'saved_query';
-export const isThreatMatchRule = (ruleType: Type): boolean => ruleType === 'threat_match';
+export const isThreatMatchRule = (ruleType: Type | undefined): boolean =>
+ ruleType === 'threat_match';
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts
index 08feb5f2e516..f73b2ccfb02a 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts
@@ -29,6 +29,7 @@ export const stepAboutDefaultValue: AboutStepRule = {
license: '',
ruleNameOverride: '',
tags: [],
+ threatIndicatorPath: '',
timestampOverride: '',
threat: threatDefault,
note: '',
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
index 209071d27536..25295a823ea6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
@@ -40,6 +40,7 @@ import { SeverityField } from '../severity_mapping';
import { RiskScoreField } from '../risk_score_mapping';
import { AutocompleteField } from '../autocomplete_field';
import { useFetchIndex } from '../../../../common/containers/source';
+import { isThreatMatchRule } from '../../../../../common/detection_engine/utils';
const CommonUseField = getUseField({ component: Field });
@@ -298,6 +299,23 @@ const StepAboutRuleComponent: FC = ({
/>
+ {isThreatMatchRule(defineRuleData?.ruleType) && (
+ <>
+
+ >
+ )}
+
= {
),
labelAppend: OptionalFieldLabel,
},
+ threatIndicatorPath: {
+ type: FIELD_TYPES.TEXT,
+ label: i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathLabel',
+ {
+ defaultMessage: 'Threat Indicator Path',
+ }
+ ),
+ helpText: i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathHelpText',
+ {
+ defaultMessage:
+ 'Specify the document path containing your threat indicator fields. Used for enrichment of indicator match alerts. Defaults to threat.indicator unless otherwise specified.',
+ }
+ ),
+ labelAppend: OptionalFieldLabel,
+ },
timestampOverride: {
type: FIELD_TYPES.TEXT,
label: i18n.translate(
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
index 591432829d90..b703bc3aa8bc 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
@@ -24,6 +24,7 @@ import {
listArray,
threat_query,
threat_index,
+ threat_indicator_path,
threat_mapping,
threat_language,
threat_filters,
@@ -132,6 +133,7 @@ export const RuleSchema = t.intersection([
threat_query,
threat_filters,
threat_index,
+ threat_indicator_path,
threat_mapping,
threat_language,
timeline_id: t.string,
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
index 5e2aeb4ead93..fdb0513d7b70 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
@@ -493,6 +493,15 @@ describe('helpers', () => {
expect(result.exceptions_list).toEqual([getListMock()]);
});
+ test('returns a threat indicator path', () => {
+ mockData = {
+ ...mockData,
+ threatIndicatorPath: 'my_custom.path',
+ };
+ const result = formatAboutStepData(mockData);
+ expect(result.threat_indicator_path).toEqual('my_custom.path');
+ });
+
test('returns formatted object with both exceptions_lists', () => {
const result = formatAboutStepData(
{
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
index c09f85ce7edc..7c447214cfde 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
@@ -288,6 +288,7 @@ export const formatAboutStepData = (
isBuildingBlock,
note,
ruleNameOverride,
+ threatIndicatorPath,
timestampOverride,
...rest
} = aboutStepData;
@@ -330,6 +331,7 @@ export const formatAboutStepData = (
...singleThreat,
framework: 'MITRE ATT&CK',
})),
+ threat_indicator_path: threatIndicatorPath,
timestamp_override: timestampOverride !== '' ? timestampOverride : undefined,
...(!isEmpty(note) ? { note } : {}),
...rest,
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx
index f0511602bd67..111eb8a5594a 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx
@@ -116,6 +116,7 @@ describe('rule helpers', () => {
severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false },
tags: ['tag1', 'tag2'],
threat: getThreatMock(),
+ threatIndicatorPath: '',
timestampOverride: 'event.ingested',
};
const scheduleRuleStepData = { from: '0s', interval: '5m' };
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx
index 35f9f0c658a6..d37c2d9141f5 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx
@@ -153,6 +153,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
risk_score: riskScore,
tags,
threat,
+ threat_indicator_path: threatIndicatorPath,
} = rule;
return {
@@ -179,6 +180,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
},
falsePositives,
threat: threat as Threats,
+ threatIndicatorPath: threatIndicatorPath ?? '',
};
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts
index 218d8c0178a2..94fdcc4069fc 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts
@@ -101,6 +101,7 @@ export interface AboutStepRule {
ruleNameOverride: string;
tags: string[];
timestampOverride: string;
+ threatIndicatorPath?: string;
threat: Threats;
note: string;
}
@@ -186,6 +187,7 @@ export interface AboutStepRuleJson {
rule_name_override?: RuleNameOverride;
tags: string[];
threat: Threats;
+ threat_indicator_path?: string;
timestamp_override?: TimestampOverride;
note?: string;
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts
index dc6f7d35a639..cf6ea572aa85 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts
@@ -393,6 +393,7 @@ export const getResult = (): RuleAlertType => ({
threatMapping: undefined,
threatLanguage: undefined,
threatIndex: undefined,
+ threatIndicatorPath: undefined,
threatQuery: undefined,
references: ['http://www.example.com', 'https://ww.example.com'],
note: '# Investigative notes',
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
index fb62a8bc6a14..27231ab896b7 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
@@ -174,6 +174,7 @@ export const importRulesRoute = (
threat_query: threatQuery,
threat_mapping: threatMapping,
threat_language: threatLanguage,
+ threat_indicator_path: threatIndicatorPath,
concurrent_searches: concurrentSearches,
items_per_search: itemsPerSearch,
threshold,
@@ -239,6 +240,7 @@ export const importRulesRoute = (
threshold,
threatFilters,
threatIndex,
+ threatIndicatorPath,
threatQuery,
threatMapping,
threatLanguage,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts
index defff7235dcb..45665c61ea3f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts
@@ -150,6 +150,7 @@ export const transformAlertToRule = (
threshold: alert.params.threshold,
threat_filters: alert.params.threatFilters,
threat_index: alert.params.threatIndex,
+ threat_indicator_path: alert.params.threatIndicatorPath,
threat_query: alert.params.threatQuery,
threat_mapping: alert.params.threatMapping,
threat_language: alert.params.threatLanguage,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts
index e36b7b3079eb..1232971c7baf 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts
@@ -48,6 +48,7 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({
itemsPerSearch: undefined,
threatQuery: undefined,
threatIndex: undefined,
+ threatIndicatorPath: undefined,
threshold: undefined,
timestampOverride: undefined,
to: 'now',
@@ -94,6 +95,7 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({
threat: [],
threatFilters: undefined,
threatIndex: undefined,
+ threatIndicatorPath: undefined,
threatMapping: undefined,
threatQuery: undefined,
threatLanguage: undefined,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts
index 9726df176e93..3683cd377e67 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts
@@ -47,6 +47,7 @@ export const createRules = async ({
threat,
threatFilters,
threatIndex,
+ threatIndicatorPath,
threatLanguage,
concurrentSearches,
itemsPerSearch,
@@ -102,6 +103,7 @@ export const createRules = async ({
*/
threatFilters: threatFilters as PartialFilter[] | undefined,
threatIndex,
+ threatIndicatorPath,
threatQuery,
concurrentSearches,
itemsPerSearch,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts
index cd1935ef50c1..0d046bb6ab21 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts
@@ -56,6 +56,7 @@ export const installPrepackagedRules = (
items_per_search: itemsPerSearch,
threat_query: threatQuery,
threat_index: threatIndex,
+ threat_indicator_path: threatIndicatorPath,
threshold,
timestamp_override: timestampOverride,
references,
@@ -110,6 +111,7 @@ export const installPrepackagedRules = (
itemsPerSearch,
threatQuery,
threatIndex,
+ threatIndicatorPath,
threshold,
timestampOverride,
references,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts
index 07eb665c8cbd..22c7dcc3a861 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts
@@ -119,6 +119,7 @@ const rule: SanitizedAlert = {
threshold: undefined,
threatFilters: undefined,
threatIndex: undefined,
+ threatIndicatorPath: undefined,
threatQuery: undefined,
threatMapping: undefined,
threatLanguage: undefined,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts
index 72798c353398..e8be32111a0e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts
@@ -96,6 +96,7 @@ import {
ThreatLanguageOrUndefined,
ConcurrentSearchesOrUndefined,
ItemsPerSearchOrUndefined,
+ ThreatIndicatorPathOrUndefined,
} from '../../../../common/detection_engine/schemas/types/threat_mapping';
import { AlertsClient, PartialAlert } from '../../../../../alerts/server';
@@ -238,6 +239,7 @@ export interface CreateRulesOptions {
threshold: ThresholdOrUndefined;
threatFilters: ThreatFiltersOrUndefined;
threatIndex: ThreatIndexOrUndefined;
+ threatIndicatorPath: ThreatIndicatorPathOrUndefined;
threatQuery: ThreatQueryOrUndefined;
threatMapping: ThreatMappingOrUndefined;
concurrentSearches: ConcurrentSearchesOrUndefined;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts
index 691ac818100a..e9a75af14310 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts
@@ -53,6 +53,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif
threatMapping: params.threat_mapping,
threatLanguage: params.threat_language,
threatIndex: params.threat_index,
+ threatIndicatorPath: params.threat_indicator_path,
concurrentSearches: params.concurrent_searches,
itemsPerSearch: params.items_per_search,
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts
index f31d6af1a0d7..abbcfcaa7910 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts
@@ -14,6 +14,7 @@ import {
threat_query,
concurrentSearchesOrUndefined,
itemsPerSearchOrUndefined,
+ threatIndicatorPathOrUndefined,
} from '../../../../common/detection_engine/schemas/types/threat_mapping';
import {
authorOrUndefined,
@@ -116,6 +117,7 @@ const threatSpecificRuleParams = t.type({
threatMapping: threat_mapping,
threatLanguage: t.union([nonEqlLanguages, t.undefined]),
threatIndex: threat_index,
+ threatIndicatorPath: threatIndicatorPathOrUndefined,
concurrentSearches: concurrentSearchesOrUndefined,
itemsPerSearch: itemsPerSearchOrUndefined,
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts
index 6011c6737697..6177fc4cd466 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts
@@ -59,6 +59,7 @@ export const sampleRuleAlertParams = (
threatQuery: undefined,
threatMapping: undefined,
threatIndex: undefined,
+ threatIndicatorPath: undefined,
threatLanguage: undefined,
timelineId: undefined,
timelineTitle: undefined,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts
index 5586d9e19f7c..8f3fda800d72 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts
@@ -170,6 +170,7 @@ export const buildRuleWithoutOverrides = (
threat_query: ruleParams.threatQuery,
threat_mapping: ruleParams.threatMapping,
threat_language: ruleParams.threatLanguage,
+ threat_indicator_path: ruleParams.threatIndicatorPath,
};
return removeInternalTagsFromRule(rule);
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts
index 2599f7db49f5..da7ee8796afb 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts
@@ -52,6 +52,7 @@ export const signalSchema = schema.object({
exceptionsList: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
threatFilters: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
threatIndex: schema.maybe(schema.arrayOf(schema.string())),
+ threatIndicatorPath: schema.maybe(schema.string()),
threatQuery: schema.maybe(schema.string()),
threatMapping: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
threatLanguage: schema.maybe(schema.string()),
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index d7fce9d83a49..8e63633cd49f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -137,6 +137,7 @@ export const signalRulesAlertType = ({
threatFilters,
threatQuery,
threatIndex,
+ threatIndicatorPath,
threatMapping,
threatLanguage,
timestampOverride,
@@ -508,6 +509,7 @@ export const signalRulesAlertType = ({
threatLanguage,
buildRuleMessage,
threatIndex,
+ threatIndicatorPath,
concurrentSearches: concurrentSearches ?? 1,
itemsPerSearch: itemsPerSearch ?? 9000,
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts
index b14d14821893..4f38f2db9230 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts
@@ -10,6 +10,8 @@ import { enrichSignalThreatMatches } from './enrich_signal_threat_matches';
import { getThreatList } from './get_threat_list';
import { BuildThreatEnrichmentOptions, GetMatchedThreats } from './types';
+const DEFAULT_INDICATOR_PATH = 'threat.indicator';
+
export const buildThreatEnrichment = ({
buildRuleMessage,
exceptionItems,
@@ -18,6 +20,7 @@ export const buildThreatEnrichment = ({
services,
threatFilters,
threatIndex,
+ threatIndicatorPath,
threatLanguage,
threatQuery,
}: BuildThreatEnrichmentOptions): SignalsEnrichment => {
@@ -50,6 +53,7 @@ export const buildThreatEnrichment = ({
return threatResponse.hits.hits;
};
+ const defaultedIndicatorPath = threatIndicatorPath ? threatIndicatorPath : DEFAULT_INDICATOR_PATH;
return (signals: SignalSearchResponse): Promise =>
- enrichSignalThreatMatches(signals, getMatchedThreats);
+ enrichSignalThreatMatches(signals, getMatchedThreats, defaultedIndicatorPath);
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
index 7690eb5eb1d5..e45aea29c423 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
@@ -48,6 +48,7 @@ export const createThreatSignals = async ({
threatLanguage,
buildRuleMessage,
threatIndex,
+ threatIndicatorPath,
name,
concurrentSearches,
itemsPerSearch,
@@ -99,6 +100,7 @@ export const createThreatSignals = async ({
services,
threatFilters,
threatIndex,
+ threatIndicatorPath,
threatLanguage,
threatQuery,
});
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 3c0765b56ae2..fada31411687 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
@@ -93,6 +93,7 @@ describe('buildMatchedIndicator', () => {
const indicators = buildMatchedIndicator({
queries: [],
threats,
+ indicatorPath: 'threat.indicator',
});
expect(indicators).toEqual([]);
@@ -102,6 +103,7 @@ describe('buildMatchedIndicator', () => {
const [indicator] = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(get(indicator, 'matched.atomic')).toEqual('domain_1');
@@ -111,6 +113,7 @@ describe('buildMatchedIndicator', () => {
const [indicator] = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(get(indicator, 'matched.field')).toEqual('event.field');
@@ -120,6 +123,7 @@ describe('buildMatchedIndicator', () => {
const [indicator] = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(get(indicator, 'matched.type')).toEqual('type_1');
@@ -148,6 +152,7 @@ describe('buildMatchedIndicator', () => {
const indicators = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(indicators).toHaveLength(queries.length);
@@ -157,6 +162,7 @@ describe('buildMatchedIndicator', () => {
const indicators = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(indicators).toEqual([
@@ -192,9 +198,9 @@ describe('buildMatchedIndicator', () => {
];
const indicators = buildMatchedIndicator({
- indicatorPath: 'custom.indicator.path',
queries,
threats,
+ indicatorPath: 'custom.indicator.path',
});
expect(indicators).toEqual([
@@ -221,6 +227,7 @@ describe('buildMatchedIndicator', () => {
const indicators = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(indicators).toEqual([
@@ -245,6 +252,7 @@ describe('buildMatchedIndicator', () => {
const indicators = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(indicators).toEqual([
@@ -276,6 +284,7 @@ describe('buildMatchedIndicator', () => {
const indicators = buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
});
expect(indicators).toEqual([
@@ -307,6 +316,7 @@ describe('buildMatchedIndicator', () => {
buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
})
).toThrowError('Expected indicator field to be an object, but found: not an object');
});
@@ -327,6 +337,7 @@ describe('buildMatchedIndicator', () => {
buildMatchedIndicator({
queries,
threats,
+ indicatorPath: 'threat.indicator',
})
).toThrowError('Expected indicator field to be an object, but found: not an object');
});
@@ -352,7 +363,11 @@ describe('enrichSignalThreatMatches', () => {
it('performs no enrichment if there are no signals', async () => {
const signals = getSignalsResponseMock([]);
- const enrichedSignals = await enrichSignalThreatMatches(signals, getMatchedThreats);
+ const enrichedSignals = await enrichSignalThreatMatches(
+ signals,
+ getMatchedThreats,
+ 'threat.indicator'
+ );
expect(enrichedSignals.hits.hits).toEqual([]);
});
@@ -363,7 +378,11 @@ describe('enrichSignalThreatMatches', () => {
matched_queries: [matchedQuery],
});
const signals = getSignalsResponseMock([signalHit]);
- const enrichedSignals = await enrichSignalThreatMatches(signals, getMatchedThreats);
+ const enrichedSignals = await enrichSignalThreatMatches(
+ signals,
+ getMatchedThreats,
+ 'threat.indicator'
+ );
const [enrichedHit] = enrichedSignals.hits.hits;
const indicators = get(enrichedHit._source, 'threat.indicator');
@@ -384,7 +403,11 @@ describe('enrichSignalThreatMatches', () => {
matched_queries: [matchedQuery],
});
const signals = getSignalsResponseMock([signalHit]);
- const enrichedSignals = await enrichSignalThreatMatches(signals, getMatchedThreats);
+ const enrichedSignals = await enrichSignalThreatMatches(
+ signals,
+ getMatchedThreats,
+ 'threat.indicator'
+ );
const [enrichedHit] = enrichedSignals.hits.hits;
const indicators = get(enrichedHit._source, 'threat.indicator');
@@ -401,7 +424,11 @@ describe('enrichSignalThreatMatches', () => {
matched_queries: [matchedQuery],
});
const signals = getSignalsResponseMock([signalHit]);
- const enrichedSignals = await enrichSignalThreatMatches(signals, getMatchedThreats);
+ const enrichedSignals = await enrichSignalThreatMatches(
+ signals,
+ getMatchedThreats,
+ 'threat.indicator'
+ );
const [enrichedHit] = enrichedSignals.hits.hits;
const indicators = get(enrichedHit._source, 'threat.indicator');
@@ -422,9 +449,53 @@ describe('enrichSignalThreatMatches', () => {
matched_queries: [matchedQuery],
});
const signals = getSignalsResponseMock([signalHit]);
- await expect(() => enrichSignalThreatMatches(signals, getMatchedThreats)).rejects.toThrowError(
- 'Expected threat field to be an object, but found: whoops'
+ await expect(() =>
+ enrichSignalThreatMatches(signals, getMatchedThreats, 'threat.indicator')
+ ).rejects.toThrowError('Expected threat field to be an object, but found: whoops');
+ });
+
+ it('enriches from a configured indicator path, if specified', async () => {
+ getMatchedThreats = async () => [
+ getThreatListItemMock({
+ _id: '123',
+ _source: {
+ custom_threat: {
+ custom_indicator: {
+ domain: 'custom_domain',
+ other: 'custom_other',
+ type: 'custom_type',
+ },
+ },
+ },
+ }),
+ ];
+ matchedQuery = encodeThreatMatchNamedQuery(
+ getNamedQueryMock({
+ id: '123',
+ field: 'event.field',
+ value: 'custom_threat.custom_indicator.domain',
+ })
+ );
+ const signalHit = getSignalHitMock({
+ matched_queries: [matchedQuery],
+ });
+ const signals = getSignalsResponseMock([signalHit]);
+ const enrichedSignals = await enrichSignalThreatMatches(
+ signals,
+ getMatchedThreats,
+ 'custom_threat.custom_indicator'
);
+ const [enrichedHit] = enrichedSignals.hits.hits;
+ const indicators = get(enrichedHit._source, 'threat.indicator');
+
+ expect(indicators).toEqual([
+ {
+ domain: 'custom_domain',
+ matched: { atomic: 'custom_domain', field: 'event.field', type: 'custom_type' },
+ other: 'custom_other',
+ type: 'custom_type',
+ },
+ ]);
});
it('merges duplicate matched signals into a single signal with multiple indicators', async () => {
@@ -455,7 +526,11 @@ describe('enrichSignalThreatMatches', () => {
],
});
const signals = getSignalsResponseMock([signalHit, otherSignalHit]);
- const enrichedSignals = await enrichSignalThreatMatches(signals, getMatchedThreats);
+ const enrichedSignals = await enrichSignalThreatMatches(
+ signals,
+ getMatchedThreats,
+ 'threat.indicator'
+ );
expect(enrichedSignals.hits.total).toEqual(expect.objectContaining({ value: 1 }));
expect(enrichedSignals.hits.hits).toHaveLength(1);
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 c298ef98ebcd..c5b032207f1c 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
@@ -16,7 +16,6 @@ import type {
} from './types';
import { extractNamedQueries } from './utils';
-const DEFAULT_INDICATOR_PATH = 'threat.indicator';
const getSignalId = (signal: SignalSourceHit): string => signal._id;
export const groupAndMergeSignalMatches = (signalHits: SignalSourceHit[]): SignalSourceHit[] => {
@@ -43,11 +42,11 @@ export const groupAndMergeSignalMatches = (signalHits: SignalSourceHit[]): Signa
export const buildMatchedIndicator = ({
queries,
threats,
- indicatorPath = DEFAULT_INDICATOR_PATH,
+ indicatorPath,
}: {
queries: ThreatMatchNamedQuery[];
threats: ThreatListItem[];
- indicatorPath?: string;
+ indicatorPath: string;
}): ThreatIndicator[] =>
queries.map((query) => {
const matchedThreat = threats.find((threat) => threat._id === query.id);
@@ -67,7 +66,8 @@ export const buildMatchedIndicator = ({
export const enrichSignalThreatMatches = async (
signals: SignalSearchResponse,
- getMatchedThreats: GetMatchedThreats
+ getMatchedThreats: GetMatchedThreats,
+ indicatorPath: string
): Promise => {
const signalHits = signals.hits.hits;
if (signalHits.length === 0) {
@@ -79,7 +79,11 @@ export const enrichSignalThreatMatches = async (
const matchedThreatIds = [...new Set(signalMatches.flat().map(({ id }) => id))];
const matchedThreats = await getMatchedThreats(matchedThreatIds);
const matchedIndicators = signalMatches.map((queries) =>
- buildMatchedIndicator({ queries, threats: matchedThreats })
+ buildMatchedIndicator({
+ indicatorPath,
+ queries,
+ threats: matchedThreats,
+ })
);
const enrichedSignals: SignalSourceHit[] = uniqueHits.map((signalHit, i) => {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
index b80d3faf9b61..a022cbbdd404 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
@@ -21,6 +21,7 @@ import {
ThreatLanguageOrUndefined,
ConcurrentSearches,
ItemsPerSearch,
+ ThreatIndicatorPathOrUndefined,
} from '../../../../../common/detection_engine/schemas/types/threat_mapping';
import { PartialFilter, RuleTypeParams } from '../../types';
import {
@@ -70,6 +71,7 @@ export interface CreateThreatSignalsOptions {
threatQuery: ThreatQuery;
buildRuleMessage: BuildRuleMessage;
threatIndex: ThreatIndex;
+ threatIndicatorPath: ThreatIndicatorPathOrUndefined;
threatLanguage: ThreatLanguageOrUndefined;
name: string;
concurrentSearches: ConcurrentSearches;
@@ -214,6 +216,7 @@ export interface BuildThreatEnrichmentOptions {
services: AlertServices;
threatFilters: PartialFilter[];
threatIndex: ThreatIndex;
+ threatIndicatorPath: ThreatIndicatorPathOrUndefined;
threatLanguage: ThreatLanguageOrUndefined;
threatQuery: ThreatQuery;
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts
index 4b59fcddcb51..a8721d82285f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts
@@ -47,6 +47,7 @@ import {
ThreatLanguageOrUndefined,
ConcurrentSearchesOrUndefined,
ItemsPerSearchOrUndefined,
+ ThreatIndicatorPathOrUndefined,
} from '../../../common/detection_engine/schemas/types/threat_mapping';
import { LegacyCallAPIOptions } from '../../../../../../src/core/server';
@@ -88,6 +89,7 @@ export interface RuleTypeParams extends AlertTypeParams {
threshold: ThresholdOrUndefined;
threatFilters: PartialFilter[] | undefined;
threatIndex: ThreatIndexOrUndefined;
+ threatIndicatorPath: ThreatIndicatorPathOrUndefined;
threatQuery: ThreatQueryOrUndefined;
threatMapping: ThreatMappingOrUndefined;
threatLanguage: ThreatLanguageOrUndefined;
From 540b1d365dc0ecb2f5a8dcdbe03f9939dd70d71c Mon Sep 17 00:00:00 2001
From: "Devin W. Hurley"
Date: Tue, 16 Feb 2021 20:24:19 -0500
Subject: [PATCH 13/23] [Security Solution] [Detections] Replace 'partial
failure' with 'warning' for rule statuses (#91167)
* removes usage of 'partial failure' status and replaces with a 'warning' status, also adds some logic to be backwards compatible with 'partial failure' statuses
* update integration tests from 'partial failure' to 'warning'
* fix integration test to warn and not error when no index patterns match concrete indices
* fix integration test
* removes outdated comments from the create_rules e2e test
---
.../schemas/common/schemas.ts | 2 +-
.../components/rules/rule_status/helpers.ts | 5 ++++-
.../components/rules/rule_status/index.tsx | 21 +++++++++++++++++--
.../detection_engine/rules/types.ts | 9 +++++++-
.../detection_engine/rules/all/columns.tsx | 6 +++++-
.../detection_engine/rules/details/index.tsx | 2 +-
.../rules/details/translations.ts | 2 +-
.../signals/rule_status_service.mock.ts | 2 +-
.../signals/rule_status_service.test.ts | 6 +++---
.../signals/rule_status_service.ts | 8 +++----
.../signals/signal_rule_alert_type.test.ts | 12 +++++------
.../signals/signal_rule_alert_type.ts | 6 +++---
.../detection_engine/signals/utils.test.ts | 2 +-
.../lib/detection_engine/signals/utils.ts | 12 +++++------
.../security_and_spaces/tests/create_rules.ts | 19 +++++++----------
.../detection_engine_api_integration/utils.ts | 2 +-
16 files changed, 71 insertions(+), 45 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
index 5d1f75726729..aade8be4f503 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
@@ -325,7 +325,7 @@ export const job_status = t.keyof({
succeeded: null,
failed: null,
'going to run': null,
- 'partial failure': null,
+ warning: null,
});
export type JobStatus = t.TypeOf;
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts
index 4ed971ea6a93..cca745659d2c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts
@@ -14,6 +14,9 @@ export const getStatusColor = (status: RuleStatusType | string | null) =>
? 'success'
: status === 'failed'
? 'danger'
- : status === 'executing' || status === 'going to run' || status === 'partial failure'
+ : status === 'executing' ||
+ status === 'going to run' ||
+ status === 'partial failure' ||
+ status === 'warning'
? 'warning'
: 'subdued';
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx
index 6292cc5b530b..677e6de0ff48 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx
@@ -16,7 +16,11 @@ import {
import React, { memo, useCallback, useEffect, useState } from 'react';
import deepEqual from 'fast-deep-equal';
-import { useRuleStatus, RuleInfoStatus } from '../../../containers/detection_engine/rules';
+import {
+ useRuleStatus,
+ RuleInfoStatus,
+ RuleStatusType,
+} from '../../../containers/detection_engine/rules';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { getStatusColor } from './helpers';
@@ -55,6 +59,19 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled })
}
}, [fetchRuleStatus, ruleId]);
+ const getStatus = useCallback((status: RuleStatusType | null | undefined) => {
+ if (status == null) {
+ return getEmptyTagValue();
+ } else if (status != null && status === 'partial failure') {
+ // Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning'
+ // On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status
+ // and this code will no longer be necessary
+ // TODO: remove this code in 8.0.0
+ return 'warning';
+ }
+ return status;
+ }, []);
+
return (
@@ -71,7 +88,7 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled })
- {currentStatus?.status ?? getEmptyTagValue()}
+ {getStatus(currentStatus?.status)}
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
index b703bc3aa8bc..b8f6c4bde3e8 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
@@ -78,6 +78,7 @@ const StatusTypes = t.union([
t.literal('failed'),
t.literal('going to run'),
t.literal('partial failure'),
+ t.literal('warning'),
]);
// TODO: make a ticket
@@ -254,7 +255,13 @@ export interface RuleStatus {
failures: RuleInfoStatus[];
}
-export type RuleStatusType = 'executing' | 'failed' | 'going to run' | 'succeeded';
+export type RuleStatusType =
+ | 'executing'
+ | 'failed'
+ | 'going to run'
+ | 'succeeded'
+ | 'partial failure'
+ | 'warning';
export interface RuleInfoStatus {
alert_id: string;
status_date: string;
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
index 425848cd09af..d110f2d52b3c 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
@@ -313,7 +313,11 @@ export const getMonitoringColumns = (
}}
href={formatUrl(getRuleDetailsUrl(item.id))}
>
- {value}
+ {/* Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning' */}
+ {/* On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status */}
+ {/* and this code will no longer be necessary */}
+ {/* TODO: remove this code in 8.0.0 */}
+ {value === 'partial failure' ? 'warning' : value}
);
},
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
index 5836cac09e9b..ed88ca41146f 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
@@ -314,7 +314,7 @@ const RuleDetailsPageComponent = () => {
/>
);
} else if (
- rule?.status === 'partial failure' &&
+ (rule?.status === 'warning' || rule?.status === 'partial failure') &&
ruleDetailTab === RuleDetailTabs.alerts &&
rule?.last_success_at != null
) {
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts
index 4e6d8f4d567b..1d100fb9109d 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts
@@ -52,7 +52,7 @@ export const ERROR_CALLOUT_TITLE = i18n.translate(
export const PARTIAL_FAILURE_CALLOUT_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.ruleDetails.partialErrorCalloutTitle',
{
- defaultMessage: 'Partial rule failure at',
+ defaultMessage: 'Warning at',
}
);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts
index 04f2b6ff799d..5893b05a1d81 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts
@@ -23,7 +23,7 @@ export const ruleStatusServiceFactoryMock = async ({
success: jest.fn(),
- partialFailure: jest.fn(),
+ warning: jest.fn(),
error: jest.fn(),
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts
index f60591422e1e..7f2962ae0a6c 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts
@@ -54,13 +54,13 @@ describe('buildRuleStatusAttributes', () => {
expect(result.statusDate).toEqual(result.lastSuccessAt);
});
- it('returns partial failure fields if "partial failure"', () => {
+ it('returns warning fields if "warning"', () => {
const result = buildRuleStatusAttributes(
- 'partial failure',
+ 'warning',
'some indices missing timestamp override field'
);
expect(result).toEqual({
- status: 'partial failure',
+ status: 'warning',
statusDate: expectIsoDateString,
lastSuccessAt: expectIsoDateString,
lastSuccessMessage: 'some indices missing timestamp override field',
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts
index f4abf9aa5ced..6e93ed256321 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts
@@ -24,7 +24,7 @@ interface Attributes {
export interface RuleStatusService {
goingToRun: () => Promise;
success: (message: string, attributes?: Attributes) => Promise;
- partialFailure: (message: string, attributes?: Attributes) => Promise;
+ warning: (message: string, attributes?: Attributes) => Promise;
error: (message: string, attributes?: Attributes) => Promise;
}
@@ -48,7 +48,7 @@ export const buildRuleStatusAttributes: (
lastSuccessMessage: message,
};
}
- case 'partial failure': {
+ case 'warning': {
return {
...baseAttributes,
lastSuccessAt: now,
@@ -102,7 +102,7 @@ export const ruleStatusServiceFactory = async ({
});
},
- partialFailure: async (message, attributes) => {
+ warning: async (message, attributes) => {
const [currentStatus] = await getOrCreateRuleStatuses({
alertId,
ruleStatusClient,
@@ -110,7 +110,7 @@ export const ruleStatusServiceFactory = async ({
await ruleStatusClient.update(currentStatus.id, {
...currentStatus.attributes,
- ...buildRuleStatusAttributes('partial failure', message, attributes),
+ ...buildRuleStatusAttributes('warning', message, attributes),
});
},
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
index 02a0582e540f..a79961eb716f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
@@ -110,7 +110,7 @@ describe('rules_notification_alert_type', () => {
find: jest.fn(),
goingToRun: jest.fn(),
error: jest.fn(),
- partialFailure: jest.fn(),
+ warning: jest.fn(),
};
(ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService);
(getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0));
@@ -207,7 +207,7 @@ describe('rules_notification_alert_type', () => {
});
});
- it('should set a partial failure for when rules cannot read ALL provided indices', async () => {
+ it('should set a warning for when rules cannot read ALL provided indices', async () => {
(checkPrivileges as jest.Mock).mockResolvedValueOnce({
username: 'elastic',
has_all_requested: false,
@@ -227,8 +227,8 @@ describe('rules_notification_alert_type', () => {
});
payload.params.index = ['some*', 'myfa*', 'anotherindex*'];
await alert.executor(payload);
- expect(ruleStatusService.partialFailure).toHaveBeenCalled();
- expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain(
+ expect(ruleStatusService.warning).toHaveBeenCalled();
+ expect(ruleStatusService.warning.mock.calls[0][0]).toContain(
'Missing required read privileges on the following indices: ["some*"]'
);
});
@@ -250,8 +250,8 @@ describe('rules_notification_alert_type', () => {
});
payload.params.index = ['some*', 'myfa*'];
await alert.executor(payload);
- expect(ruleStatusService.partialFailure).toHaveBeenCalled();
- expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain(
+ expect(ruleStatusService.warning).toHaveBeenCalled();
+ expect(ruleStatusService.warning.mock.calls[0][0]).toContain(
'This rule may not have the required read privileges to the following indices: ["myfa*","some*"]'
);
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 8e63633cd49f..ecb36a8b050d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -181,7 +181,7 @@ export const signalRulesAlertType = ({
logger.debug(buildRuleMessage('[+] Starting Signal Rule execution'));
logger.debug(buildRuleMessage(`interval: ${interval}`));
- let wrotePartialFailureStatus = false;
+ let wroteWarningStatus = false;
await ruleStatusService.goingToRun();
// check if rule has permissions to access given index pattern
@@ -202,7 +202,7 @@ export const signalRulesAlertType = ({
}),
]);
- wrotePartialFailureStatus = await flow(
+ wroteWarningStatus = await flow(
() =>
tryCatch(
() =>
@@ -659,7 +659,7 @@ export const signalRulesAlertType = ({
`[+] Finished indexing ${result.createdSignalsCount} signals into ${outputIndex}`
)
);
- if (!hasError && !wrotePartialFailureStatus) {
+ if (!hasError && !wroteWarningStatus) {
await ruleStatusService.success('succeeded', {
bulkCreateTimeDurations: result.bulkCreateTimes,
searchAfterTimeDurations: result.searchAfterTimes,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts
index 75bd9f593a6a..f7e1eb762277 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts
@@ -69,7 +69,7 @@ const ruleStatusServiceMock = {
find: jest.fn(),
goingToRun: jest.fn(),
error: jest.fn(),
- partialFailure: jest.fn(),
+ warning: jest.fn(),
};
describe('utils', () => {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
index 72e5bc0c5b87..f6bd5c8a325b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
@@ -79,12 +79,12 @@ export const hasReadIndexPrivileges = async (
if (indexesWithReadPrivileges.length > 0 && indexesWithNoReadPrivileges.length > 0) {
// some indices have read privileges others do not.
- // set a partial failure status
+ // set a warning status
const errorString = `Missing required read privileges on the following indices: ${JSON.stringify(
indexesWithNoReadPrivileges
)}`;
logger.error(buildRuleMessage(errorString));
- await ruleStatusService.partialFailure(errorString);
+ await ruleStatusService.warning(errorString);
return true;
} else if (
indexesWithReadPrivileges.length === 0 &&
@@ -96,7 +96,7 @@ export const hasReadIndexPrivileges = async (
indexesWithNoReadPrivileges
)}`;
logger.error(buildRuleMessage(errorString));
- await ruleStatusService.partialFailure(errorString);
+ await ruleStatusService.warning(errorString);
return true;
}
return false;
@@ -119,7 +119,7 @@ export const hasTimestampFields = async (
inputIndices
)}`;
logger.error(buildRuleMessage(errorString));
- await ruleStatusService.error(errorString);
+ await ruleStatusService.warning(errorString);
return true;
} else if (
!wroteStatus &&
@@ -128,7 +128,7 @@ export const hasTimestampFields = async (
timestampFieldCapsResponse.body.fields[timestampField]?.unmapped?.indices != null)
) {
// if there is a timestamp override and the unmapped array for the timestamp override key is not empty,
- // partial failure
+ // warning
const errorString = `The following indices are missing the ${
timestampField === '@timestamp'
? 'timestamp field "@timestamp"'
@@ -139,7 +139,7 @@ export const hasTimestampFields = async (
: timestampFieldCapsResponse.body.fields[timestampField].unmapped.indices
)}`;
logger.error(buildRuleMessage(errorString));
- await ruleStatusService.partialFailure(errorString);
+ await ruleStatusService.warning(errorString);
return true;
}
return wroteStatus;
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts
index a735eba6693f..a9120bde274f 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts
@@ -118,7 +118,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(statusBody[body.id].current_status.status).to.eql('succeeded');
});
- it('should create a single rule with a rule_id and an index pattern that does not match anything available and fail the rule', async () => {
+ it('should create a single rule with a rule_id and an index pattern that does not match anything available and warning for the rule', async () => {
const simpleRule = getRuleForSignalTesting(['does-not-exist-*']);
const { body } = await supertest
.post(DETECTION_ENGINE_RULES_URL)
@@ -126,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => {
.send(simpleRule)
.expect(200);
- await waitForRuleSuccessOrStatus(supertest, body.id, 'failed');
+ await waitForRuleSuccessOrStatus(supertest, body.id, 'warning');
const { body: statusBody } = await supertest
.post(DETECTION_ENGINE_RULES_STATUS_URL)
@@ -134,8 +134,8 @@ export default ({ getService }: FtrProviderContext) => {
.send({ ids: [body.id] })
.expect(200);
- expect(statusBody[body.id].current_status.status).to.eql('failed');
- expect(statusBody[body.id].current_status.last_failure_message).to.eql(
+ expect(statusBody[body.id].current_status.status).to.eql('warning');
+ expect(statusBody[body.id].current_status.last_success_message).to.eql(
'The following index patterns did not match any indices: ["does-not-exist-*"]'
);
});
@@ -287,10 +287,7 @@ export default ({ getService }: FtrProviderContext) => {
await deleteAllAlerts(supertest);
await esArchiver.unload('security_solution/timestamp_override');
});
- it('should create a single rule which has a timestamp override and generates two signals with a failing status', async () => {
- // should be a failing status because one of the indices in the index pattern is missing
- // the timestamp override field.
-
+ it('should create a single rule which has a timestamp override and generates two signals with a "warning" status', async () => {
// defaults to event.ingested timestamp override.
// event.ingested is one of the timestamp fields set on the es archive data
// inside of x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz
@@ -302,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => {
.expect(200);
const bodyId = body.id;
- await waitForRuleSuccessOrStatus(supertest, bodyId, 'partial failure');
+ await waitForRuleSuccessOrStatus(supertest, bodyId, 'warning');
await waitForSignalsToBePresent(supertest, 2, [bodyId]);
const { body: statusBody } = await supertest
@@ -311,9 +308,7 @@ export default ({ getService }: FtrProviderContext) => {
.send({ ids: [bodyId] })
.expect(200);
- // set to "failed" for now. Will update this with a partial failure
- // once I figure out the logic
- expect(statusBody[bodyId].current_status.status).to.eql('partial failure');
+ expect(statusBody[bodyId].current_status.status).to.eql('warning');
});
});
});
diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts
index 158247ee244d..4875e837556f 100644
--- a/x-pack/test/detection_engine_api_integration/utils.ts
+++ b/x-pack/test/detection_engine_api_integration/utils.ts
@@ -972,7 +972,7 @@ export const getRule = async (
export const waitForRuleSuccessOrStatus = async (
supertest: SuperTest,
id: string,
- status: 'succeeded' | 'failed' | 'partial failure' = 'succeeded'
+ status: 'succeeded' | 'failed' | 'partial failure' | 'warning' = 'succeeded'
): Promise => {
await waitFor(async () => {
const { body } = await supertest
From 63ac0f74be6c0339d8ec63a6679ee3ebb93e153a Mon Sep 17 00:00:00 2001
From: Quynh Nguyen <43350163+qn895@users.noreply.github.com>
Date: Tue, 16 Feb 2021 20:00:02 -0600
Subject: [PATCH 14/23] [ML] Add better UI support for runtime fields
Transforms (#90363)
* [ML] Add RT support for transforms from index pattern
* [ML] Add support for cloned transform from api
* [ML] Add support for runtime pivot
* [ML] Add support for api created runtime
* [ML] Add preview for expanded row
* [ML] Add runtime fields to dropdown options
* [ML] Add runtime fields to latest
* [ML] Fix duplicate columns
* [ML] Update types and test
* [ML] Add runtime mappings to index pattern on creation
* [ML] Add callout to show unsupported fields in dfa
* [ML] Update types to RuntimeField
* [ML] Fix runtime fields, remove runtime mappings, fix copy to console
* [ML] Fix incompatible kbn field type
* [ML] Add advanced mappings editor
* [ML] Add support for filter terms agg control
* [ML] Fix jest tests hanging
* [ML] Fix translations
* [ML] Fix over-sized buttons for filter range
* [ML] Update runtime mappings schema
* [ML] Update runtime mappings schema
* [ML] Use isRecord for object checks
* [ML] Fix and more message
* [ML] Update schema to correctly match types
* [ML] Update schema to correctly match types
* [ML] Fix pivot duplicates
* [ML] Rename isRecord to isPopulatedObject
* [ML] Remove fit-content
* [ML] Update runtime field type to prevent potential conflicts
* Revert "[ML] Remove fit-content"
This reverts commit 76c9c799
* [ML] Remove misc comment
* [ML] Fix missing typeof
* [ML] Add sorts and constants
* [ML] Add i18n to includedFields description
* [ML] fix imports
* [ML] Only pass runtime mappings if it's latest
* [ML] Fix functional tests
---
.../ml/common/types/feature_importance.ts | 6 +-
x-pack/plugins/ml/common/types/fields.ts | 16 +-
.../plugins/ml/common/util/datafeed_utils.ts | 2 +-
x-pack/plugins/ml/common/util/job_utils.ts | 22 +--
x-pack/plugins/ml/common/util/object_utils.ts | 10 +
.../ml/common/util/validation_utils.ts | 2 +-
.../components/data_grid/common.ts | 73 ++++++++
.../application/components/data_grid/index.ts | 2 +
.../configuration_step_details.tsx | 13 +-
.../configuration_step_form.tsx | 43 +++++
.../hooks/use_index_data.ts | 9 +-
.../jobs/jobs_list/components/utils.js | 3 +-
.../datafeed_preview.tsx | 3 +-
.../results_service/result_service_rx.ts | 3 +-
x-pack/plugins/ml/public/shared.ts | 2 +-
.../ml/server/models/job_service/jobs.ts | 7 +-
.../common/api_schemas/transforms.ts | 23 +++
.../public/app/common/pivot_group_by.ts | 5 +
.../public/app/common/request.test.ts | 83 +++++++++
.../transform/public/app/common/request.ts | 31 +++-
.../public/app/common/utils/object_utils.ts | 10 +
.../public/app/hooks/use_index_data.test.tsx | 16 +-
.../public/app/hooks/use_index_data.ts | 61 ++++--
.../public/app/hooks/use_pivot_data.ts | 57 ++----
.../advanced_runtime_mappings_editor.tsx | 70 +++++++
...dvanced_runtime_mappings_editor_switch.tsx | 42 +++++
.../index.ts | 8 +
.../advanced_runtime_mappings_settings.tsx | 175 ++++++++++++++++++
.../index.ts | 8 +
.../step_create/step_create_form.tsx | 10 +
.../apply_transform_config_to_define_state.ts | 8 +-
.../step_define/common/common.test.ts | 165 +++++++++++++++++
.../components/filter_agg_form.test.tsx | 24 ++-
.../filter_agg/components/filter_agg_form.tsx | 33 ++--
.../components/filter_range_form.tsx | 11 +-
.../components/filter_term_form.tsx | 3 +-
.../common/get_default_step_define_state.ts | 3 +
.../common/get_pivot_dropdown_options.ts | 55 +++++-
.../components/step_define/common/types.ts | 25 ++-
.../use_advanced_runtime_mappings_editor.ts | 102 ++++++++++
.../hooks/use_latest_function_config.ts | 41 +++-
.../step_define/hooks/use_pivot_config.ts | 4 +-
.../step_define/hooks/use_step_define_form.ts | 29 ++-
.../step_define/step_define_form.tsx | 25 ++-
.../step_define/step_define_summary.tsx | 7 +-
.../step_details/step_details_form.tsx | 3 +-
.../components/wizard/wizard.tsx | 11 +-
.../expanded_row_preview_pane.tsx | 5 +-
48 files changed, 1224 insertions(+), 145 deletions(-)
create mode 100644 x-pack/plugins/ml/common/util/object_utils.ts
create mode 100644 x-pack/plugins/transform/public/app/common/utils/object_utils.ts
create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx
create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx
create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/index.ts
create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx
create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/index.ts
create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts
diff --git a/x-pack/plugins/ml/common/types/feature_importance.ts b/x-pack/plugins/ml/common/types/feature_importance.ts
index 2e45c3cd4d8c..964ce8c32578 100644
--- a/x-pack/plugins/ml/common/types/feature_importance.ts
+++ b/x-pack/plugins/ml/common/types/feature_importance.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { isPopulatedObject } from '../util/object_utils';
+
export type FeatureImportanceClassName = string | number | boolean;
export interface ClassFeatureImportance {
@@ -87,7 +89,7 @@ export function isClassificationFeatureImportanceBaseline(
baselineData: any
): baselineData is ClassificationFeatureImportanceBaseline {
return (
- typeof baselineData === 'object' &&
+ isPopulatedObject(baselineData) &&
baselineData.hasOwnProperty('classes') &&
Array.isArray(baselineData.classes)
);
@@ -96,5 +98,5 @@ export function isClassificationFeatureImportanceBaseline(
export function isRegressionFeatureImportanceBaseline(
baselineData: any
): baselineData is RegressionFeatureImportanceBaseline {
- return typeof baselineData === 'object' && baselineData.hasOwnProperty('baseline');
+ return isPopulatedObject(baselineData) && baselineData.hasOwnProperty('baseline');
}
diff --git a/x-pack/plugins/ml/common/types/fields.ts b/x-pack/plugins/ml/common/types/fields.ts
index ae157cef5735..581ce861e833 100644
--- a/x-pack/plugins/ml/common/types/fields.ts
+++ b/x-pack/plugins/ml/common/types/fields.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ES_FIELD_TYPES, RuntimeField } from '../../../../../src/plugins/data/common';
+import { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common';
import {
ML_JOB_AGGREGATION,
KIBANA_AGGREGATION,
@@ -106,4 +106,18 @@ export interface AggCardinality {
}
export type RollupFields = Record]>;
+
+// Replace this with import once #88995 is merged
+const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const;
+type RuntimeType = typeof RUNTIME_FIELD_TYPES[number];
+
+export interface RuntimeField {
+ type: RuntimeType;
+ script:
+ | string
+ | {
+ source: string;
+ };
+}
+
export type RuntimeMappings = Record;
diff --git a/x-pack/plugins/ml/common/util/datafeed_utils.ts b/x-pack/plugins/ml/common/util/datafeed_utils.ts
index fa1a940ba549..c0579ce94799 100644
--- a/x-pack/plugins/ml/common/util/datafeed_utils.ts
+++ b/x-pack/plugins/ml/common/util/datafeed_utils.ts
@@ -20,7 +20,7 @@ export const getDatafeedAggregations = (
};
export const getAggregationBucketsName = (aggregations: any): string | undefined => {
- if (typeof aggregations === 'object') {
+ if (aggregations !== null && typeof aggregations === 'object') {
const keys = Object.keys(aggregations);
return keys.length > 0 ? keys[0] : undefined;
}
diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts
index 711103b499ec..ab56726e160f 100644
--- a/x-pack/plugins/ml/common/util/job_utils.ts
+++ b/x-pack/plugins/ml/common/util/job_utils.ts
@@ -28,6 +28,7 @@ import {
getDatafeedAggregations,
} from './datafeed_utils';
import { findAggField } from './validation_utils';
+import { isPopulatedObject } from './object_utils';
export interface ValidationResults {
valid: boolean;
@@ -51,17 +52,9 @@ export function calculateDatafeedFrequencyDefaultSeconds(bucketSpanSeconds: numb
}
export function hasRuntimeMappings(job: CombinedJob): boolean {
- const hasDatafeed =
- typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0;
+ const hasDatafeed = isPopulatedObject(job.datafeed_config);
if (hasDatafeed) {
- const runtimeMappings =
- typeof job.datafeed_config.runtime_mappings === 'object'
- ? Object.keys(job.datafeed_config.runtime_mappings)
- : undefined;
-
- if (Array.isArray(runtimeMappings) && runtimeMappings.length > 0) {
- return true;
- }
+ return isPopulatedObject(job.datafeed_config.runtime_mappings);
}
return false;
}
@@ -114,7 +107,11 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex
// If the datafeed uses script fields, we can only plot the time series if
// model plot is enabled. Without model plot it will be very difficult or impossible
// to invert to a reverse search of the underlying metric data.
- if (isSourceDataChartable === true && typeof job.datafeed_config?.script_fields === 'object') {
+ if (
+ isSourceDataChartable === true &&
+ job.datafeed_config?.script_fields !== null &&
+ typeof job.datafeed_config?.script_fields === 'object'
+ ) {
// Perform extra check to see if the detector is using a scripted field.
const scriptFields = Object.keys(job.datafeed_config.script_fields);
isSourceDataChartable =
@@ -123,8 +120,7 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex
scriptFields.indexOf(dtr.over_field_name!) === -1;
}
- const hasDatafeed =
- typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0;
+ const hasDatafeed = isPopulatedObject(job.datafeed_config);
if (hasDatafeed) {
// We cannot plot the source data for some specific aggregation configurations
const aggs = getDatafeedAggregations(job.datafeed_config);
diff --git a/x-pack/plugins/ml/common/util/object_utils.ts b/x-pack/plugins/ml/common/util/object_utils.ts
new file mode 100644
index 000000000000..4bbd0c1c2810
--- /dev/null
+++ b/x-pack/plugins/ml/common/util/object_utils.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const isPopulatedObject = >(arg: any): arg is T => {
+ return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0;
+};
diff --git a/x-pack/plugins/ml/common/util/validation_utils.ts b/x-pack/plugins/ml/common/util/validation_utils.ts
index 7f0208e726ab..66084f83ea87 100644
--- a/x-pack/plugins/ml/common/util/validation_utils.ts
+++ b/x-pack/plugins/ml/common/util/validation_utils.ts
@@ -45,7 +45,7 @@ export function findAggField(
value = returnParent === true ? aggs : aggs[k];
return true;
}
- if (aggs.hasOwnProperty(k) && typeof aggs[k] === 'object') {
+ if (aggs.hasOwnProperty(k) && aggs[k] !== null && typeof aggs[k] === 'object') {
value = findAggField(aggs[k], fieldName, returnParent);
return value !== undefined;
}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts
index 2805a28996ac..069c13df2470 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts
@@ -48,6 +48,9 @@ import { getNestedProperty } from '../../util/object_utils';
import { mlFieldFormatService } from '../../services/field_format_service';
import { DataGridItem, IndexPagination, RenderCellValue } from './types';
+import type { RuntimeField } from '../../../../../../../src/plugins/data/common/index_patterns';
+import { RuntimeMappings } from '../../../../common/types/fields';
+import { isPopulatedObject } from '../../../../common/util/object_utils';
export const INIT_MAX_COLUMNS = 10;
@@ -86,6 +89,37 @@ export const getFieldsFromKibanaIndexPattern = (indexPattern: IndexPattern): str
return indexPatternFields;
};
+/**
+ * Return a map of runtime_mappings for each of the index pattern field provided
+ * to provide in ES search queries
+ * @param indexPatternFields
+ * @param indexPattern
+ * @param clonedRuntimeMappings
+ */
+export const getRuntimeFieldsMapping = (
+ indexPatternFields: string[] | undefined,
+ indexPattern: IndexPattern | undefined,
+ clonedRuntimeMappings?: RuntimeMappings
+) => {
+ if (!Array.isArray(indexPatternFields) || indexPattern === undefined) return {};
+ const ipRuntimeMappings = indexPattern.getComputedFields().runtimeFields;
+ let combinedRuntimeMappings: RuntimeMappings = {};
+
+ if (isPopulatedObject(ipRuntimeMappings)) {
+ indexPatternFields.forEach((ipField) => {
+ if (ipRuntimeMappings.hasOwnProperty(ipField)) {
+ combinedRuntimeMappings[ipField] = ipRuntimeMappings[ipField];
+ }
+ });
+ }
+ if (isPopulatedObject(clonedRuntimeMappings)) {
+ combinedRuntimeMappings = { ...combinedRuntimeMappings, ...clonedRuntimeMappings };
+ }
+ return Object.keys(combinedRuntimeMappings).length > 0
+ ? { runtime_mappings: combinedRuntimeMappings }
+ : {};
+};
+
export interface FieldTypes {
[key: string]: ES_FIELD_TYPES;
}
@@ -135,6 +169,45 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results
};
export const NON_AGGREGATABLE = 'non-aggregatable';
+
+export const getDataGridSchemaFromESFieldType = (
+ fieldType: ES_FIELD_TYPES | undefined | RuntimeField['type']
+): string | undefined => {
+ // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
+ // To fall back to the default string schema it needs to be undefined.
+ let schema;
+
+ switch (fieldType) {
+ case ES_FIELD_TYPES.GEO_POINT:
+ case ES_FIELD_TYPES.GEO_SHAPE:
+ schema = 'json';
+ break;
+ case ES_FIELD_TYPES.BOOLEAN:
+ schema = 'boolean';
+ break;
+ case ES_FIELD_TYPES.DATE:
+ case ES_FIELD_TYPES.DATE_NANOS:
+ schema = 'datetime';
+ break;
+ case ES_FIELD_TYPES.BYTE:
+ case ES_FIELD_TYPES.DOUBLE:
+ case ES_FIELD_TYPES.FLOAT:
+ case ES_FIELD_TYPES.HALF_FLOAT:
+ case ES_FIELD_TYPES.INTEGER:
+ case ES_FIELD_TYPES.LONG:
+ case ES_FIELD_TYPES.SCALED_FLOAT:
+ case ES_FIELD_TYPES.SHORT:
+ schema = 'numeric';
+ break;
+ // keep schema undefined for text based columns
+ case ES_FIELD_TYPES.KEYWORD:
+ case ES_FIELD_TYPES.TEXT:
+ break;
+ }
+
+ return schema;
+};
+
export const getDataGridSchemaFromKibanaFieldType = (
field: IFieldType | undefined
): string | undefined => {
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts
index ccd2f3f56e45..79a8d65f9905 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts
@@ -7,8 +7,10 @@
export {
getDataGridSchemasFromFieldTypes,
+ getDataGridSchemaFromESFieldType,
getDataGridSchemaFromKibanaFieldType,
getFieldsFromKibanaIndexPattern,
+ getRuntimeFieldsMapping,
multiColumnSortFactory,
showDataGridColumnChartErrorMessageToast,
useRenderCellValue,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx
index f92d391ecd4a..ef88c363e3e2 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx
@@ -69,9 +69,16 @@ export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) =
}),
description:
includes.length > MAX_INCLUDES_LENGTH
- ? `${includes.slice(0, MAX_INCLUDES_LENGTH).join(', ')} ... (and ${
- includes.length - MAX_INCLUDES_LENGTH
- } more)`
+ ? i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.includedFieldsAndMoreDescription',
+ {
+ defaultMessage: '{includedFields} ... (and {extraCount} more)',
+ values: {
+ extraCount: includes.length - MAX_INCLUDES_LENGTH,
+ includedFields: includes.slice(0, MAX_INCLUDES_LENGTH).join(', '),
+ },
+ }
+ )
: includes.join(', '),
},
];
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
index 6ad874d3abd6..0432094c30c5 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
@@ -8,6 +8,7 @@
import React, { FC, Fragment, useEffect, useMemo, useRef, useState } from 'react';
import {
EuiBadge,
+ EuiCallOut,
EuiComboBox,
EuiComboBoxOptionOption,
EuiFormRow,
@@ -19,6 +20,7 @@ import {
import { i18n } from '@kbn/i18n';
import { debounce } from 'lodash';
+import { FormattedMessage } from '@kbn/i18n/react';
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
import { useMlContext } from '../../../../../contexts/ml';
@@ -62,6 +64,8 @@ const requiredFieldsErrorText = i18n.translate(
}
);
+const maxRuntimeFieldsDisplayCount = 5;
+
export const ConfigurationStepForm: FC = ({
actions,
state,
@@ -314,6 +318,15 @@ export const ConfigurationStepForm: FC = ({
};
}, [jobType, dependentVariable, trainingPercent, JSON.stringify(includes), jobConfigQueryString]);
+ const unsupportedRuntimeFields = useMemo(
+ () =>
+ currentIndexPattern.fields
+ .getAll()
+ .filter((f) => f.runtimeField)
+ .map((f) => `'${f.displayName}'`),
+ [currentIndexPattern.fields]
+ );
+
return (
@@ -445,6 +458,36 @@ export const ConfigurationStepForm: FC = ({
>
+ {Array.isArray(unsupportedRuntimeFields) && unsupportedRuntimeFields.length > 0 && (
+ <>
+
+ 0 ? (
+
+ ) : (
+ ''
+ ),
+ unsupportedRuntimeFields: unsupportedRuntimeFields
+ .slice(0, maxRuntimeFieldsDisplayCount)
+ .join(', '),
+ }}
+ />
+
+
+ >
+ )}
+
{
- const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern);
+ const indexPatternFields = useMemo(() => getFieldsFromKibanaIndexPattern(indexPattern), [
+ indexPattern,
+ ]);
// EuiDataGrid State
const columns: EuiDataGridColumn[] = [
@@ -75,7 +78,6 @@ export const useIndexData = (
s[column.id] = { order: column.direction };
return s;
}, {} as EsSorting);
-
const esSearchRequest = {
index: indexPattern.title,
body: {
@@ -86,6 +88,7 @@ export const useIndexData = (
fields: ['*'],
_source: false,
...(Object.keys(sort).length > 0 ? { sort } : {}),
+ ...getRuntimeFieldsMapping(indexPatternFields, indexPattern),
},
};
@@ -105,7 +108,7 @@ export const useIndexData = (
useEffect(() => {
getIndexData();
// custom comparison
- }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]);
+ }, [indexPattern.title, indexPatternFields, JSON.stringify([query, pagination, sortingColumns])]);
const dataLoader = useMemo(() => new DataLoader(indexPattern, toastNotifications), [
indexPattern,
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js
index 98d8b5eaf912..5b8fa5c672c6 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js
@@ -19,6 +19,7 @@ import { stringMatch } from '../../../util/string_utils';
import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states';
import { parseInterval } from '../../../../../common/util/parse_interval';
import { mlCalendarService } from '../../../services/calendar_service';
+import { isPopulatedObject } from '../../../../../common/util/object_utils';
export function loadFullJob(jobId) {
return new Promise((resolve, reject) => {
@@ -379,7 +380,7 @@ export function checkForAutoStartDatafeed() {
mlJobService.tempJobCloningObjects.datafeed = undefined;
mlJobService.tempJobCloningObjects.createdBy = undefined;
- const hasDatafeed = typeof datafeed === 'object' && Object.keys(datafeed).length > 0;
+ const hasDatafeed = isPopulatedObject(datafeed);
const datafeedId = hasDatafeed ? datafeed.datafeed_id : '';
return {
id: job.job_id,
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx
index 6afc1122fcda..916a25271c63 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx
@@ -21,6 +21,7 @@ import { CombinedJob } from '../../../../../../../../common/types/anomaly_detect
import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor';
import { mlJobService } from '../../../../../../services/job_service';
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../../../common/util/job_utils';
+import { isPopulatedObject } from '../../../../../../../../common/util/object_utils';
export const DatafeedPreview: FC<{
combinedJob: CombinedJob | null;
@@ -64,7 +65,7 @@ export const DatafeedPreview: FC<{
const resp = await mlJobService.searchPreview(combinedJob);
let data = resp.hits.hits;
// the first item under aggregations can be any name
- if (typeof resp.aggregations === 'object' && Object.keys(resp.aggregations).length > 0) {
+ if (isPopulatedObject(resp.aggregations)) {
const accessor = Object.keys(resp.aggregations)[0];
data = resp.aggregations[accessor].buckets.slice(0, ML_DATA_PREVIEW_COUNT);
}
diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts
index ec1d36a1ced4..a8ae42658f36 100644
--- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts
+++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts
@@ -24,6 +24,7 @@ import { findAggField } from '../../../../common/util/validation_utils';
import { getDatafeedAggregations } from '../../../../common/util/datafeed_utils';
import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils';
import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types';
+import { isPopulatedObject } from '../../../../common/util/object_utils';
interface ResultResponse {
success: boolean;
@@ -175,7 +176,7 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) {
// when the field is an aggregation field, because the field doesn't actually exist in the indices
// we need to pass all the sub aggs from the original datafeed config
// so that we can access the aggregated field
- if (typeof aggFields === 'object' && Object.keys(aggFields).length > 0) {
+ if (isPopulatedObject(aggFields)) {
// first item under aggregations can be any name, not necessarily 'buckets'
const accessor = Object.keys(aggFields)[0];
const tempAggs = { ...(aggFields[accessor].aggs ?? aggFields[accessor].aggregations) };
diff --git a/x-pack/plugins/ml/public/shared.ts b/x-pack/plugins/ml/public/shared.ts
index a0107ce8e049..7fb27f889c41 100644
--- a/x-pack/plugins/ml/public/shared.ts
+++ b/x-pack/plugins/ml/public/shared.ts
@@ -17,8 +17,8 @@ export * from '../common/types/audit_message';
export * from '../common/util/anomaly_utils';
export * from '../common/util/errors';
export * from '../common/util/validators';
+export * from '../common/util/date_utils';
export * from './application/formatters/metric_change_description';
export * from './application/components/data_grid';
export * from './application/data_frame_analytics/common';
-export * from '../common/util/date_utils';
diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts
index 0af8f1e1ec1c..dc2c04540ef2 100644
--- a/x-pack/plugins/ml/server/models/job_service/jobs.ts
+++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts
@@ -39,6 +39,7 @@ import {
} from '../../../common/util/job_utils';
import { groupsProvider } from './groups';
import type { MlClient } from '../../lib/ml_client';
+import { isPopulatedObject } from '../../../common/util/object_utils';
interface Results {
[id: string]: {
@@ -172,8 +173,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) {
});
const jobs = fullJobsList.map((job) => {
- const hasDatafeed =
- typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0;
+ const hasDatafeed = isPopulatedObject(job.datafeed_config);
const dataCounts = job.data_counts;
const errorMessage = getSingleMetricViewerJobErrorMessage(job);
@@ -233,8 +233,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) {
const jobs = fullJobsList.map((job) => {
jobsMap[job.job_id] = job.groups || [];
- const hasDatafeed =
- typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0;
+ const hasDatafeed = isPopulatedObject(job.datafeed_config);
const timeRange: { to?: number; from?: number } = {};
const dataCounts = job.data_counts;
diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts
index f9dedf0acb56..3d8d7ef4d8ae 100644
--- a/x-pack/plugins/transform/common/api_schemas/transforms.ts
+++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts
@@ -64,7 +64,30 @@ export const settingsSchema = schema.object({
docs_per_second: schema.maybe(schema.nullable(schema.number())),
});
+export const runtimeMappingsSchema = schema.maybe(
+ schema.recordOf(
+ schema.string(),
+ schema.object({
+ type: schema.oneOf([
+ schema.literal('keyword'),
+ schema.literal('long'),
+ schema.literal('double'),
+ schema.literal('date'),
+ schema.literal('ip'),
+ schema.literal('boolean'),
+ ]),
+ script: schema.oneOf([
+ schema.string(),
+ schema.object({
+ source: schema.string(),
+ }),
+ ]),
+ })
+ )
+);
+
export const sourceSchema = schema.object({
+ runtime_mappings: runtimeMappingsSchema,
index: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]),
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
});
diff --git a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts
index 18b0c7dde819..4f23b8aa4d86 100644
--- a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts
+++ b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts
@@ -10,6 +10,7 @@ import { Dictionary } from '../../../common/types/common';
import { EsFieldName } from '../../../common/types/fields';
import { GenericAgg } from '../../../common/types/pivot_group_by';
import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/common';
+import { PivotAggsConfigWithUiSupport } from './pivot_aggs';
export enum PIVOT_SUPPORTED_GROUP_BY_AGGS {
DATE_HISTOGRAM = 'date_histogram',
@@ -117,3 +118,7 @@ export function getEsAggFromGroupByConfig(groupByConfig: GroupByConfigBase): Gen
[agg]: esAgg,
};
}
+
+export function isPivotAggConfigWithUiSupport(arg: any): arg is PivotAggsConfigWithUiSupport {
+ return arg.hasOwnProperty('agg') && arg.hasOwnProperty('field');
+}
diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts
index fa39419c254b..13e7c0a9feb7 100644
--- a/x-pack/plugins/transform/public/app/common/request.test.ts
+++ b/x-pack/plugins/transform/public/app/common/request.test.ts
@@ -27,6 +27,7 @@ import {
PivotQuery,
} from './request';
import { LatestFunctionConfigUI } from '../../../common/types/transform';
+import { RuntimeField } from '../../../../../../src/plugins/data/common/index_patterns';
const simpleQuery: PivotQuery = { query_string: { query: 'airline:AAL' } };
@@ -168,6 +169,9 @@ describe('Transform: Common', () => {
validationStatus: {
isValid: true,
},
+ runtimeMappings: undefined,
+ runtimeMappingsUpdated: false,
+ isRuntimeMappingsEditorEnabled: false,
};
const transformDetailsState: StepDetailsExposedState = {
continuousModeDateField: 'the-continuous-mode-date-field',
@@ -212,6 +216,85 @@ describe('Transform: Common', () => {
});
});
+ test('getCreateTransformRequestBody() with runtime mappings', () => {
+ const runtimeMappings = {
+ rt_bytes_bigger: {
+ type: 'double',
+ script: {
+ source: "emit(doc['bytes'].value * 2.0)",
+ },
+ } as RuntimeField,
+ };
+
+ const pivotState: StepDefineExposedState = {
+ aggList: { 'the-agg-name': aggsAvg },
+ groupByList: { 'the-group-by-name': groupByTerms },
+ isAdvancedPivotEditorEnabled: false,
+ isAdvancedSourceEditorEnabled: false,
+ sourceConfigUpdated: false,
+ searchLanguage: 'kuery',
+ searchString: 'the-query',
+ searchQuery: 'the-search-query',
+ valid: true,
+ transformFunction: 'pivot',
+ latestConfig: {} as LatestFunctionConfigUI,
+ previewRequest: {
+ pivot: {
+ aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } },
+ group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } },
+ },
+ },
+ validationStatus: {
+ isValid: true,
+ },
+ runtimeMappings,
+ runtimeMappingsUpdated: false,
+ isRuntimeMappingsEditorEnabled: false,
+ };
+ const transformDetailsState: StepDetailsExposedState = {
+ continuousModeDateField: 'the-continuous-mode-date-field',
+ continuousModeDelay: 'the-continuous-mode-delay',
+ createIndexPattern: false,
+ isContinuousModeEnabled: false,
+ isRetentionPolicyEnabled: false,
+ retentionPolicyDateField: '',
+ retentionPolicyMaxAge: '',
+ transformId: 'the-transform-id',
+ transformDescription: 'the-transform-description',
+ transformFrequency: '1m',
+ transformSettingsMaxPageSearchSize: 100,
+ transformSettingsDocsPerSecond: 400,
+ destinationIndex: 'the-destination-index',
+ touched: true,
+ valid: true,
+ };
+
+ const request = getCreateTransformRequestBody(
+ 'the-index-pattern-title',
+ pivotState,
+ transformDetailsState
+ );
+
+ expect(request).toEqual({
+ description: 'the-transform-description',
+ dest: { index: 'the-destination-index' },
+ frequency: '1m',
+ pivot: {
+ aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } },
+ group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } },
+ },
+ settings: {
+ max_page_search_size: 100,
+ docs_per_second: 400,
+ },
+ source: {
+ index: ['the-index-pattern-title'],
+ query: { query_string: { default_operator: 'AND', query: 'the-search-query' } },
+ runtime_mappings: runtimeMappings,
+ },
+ });
+ });
+
test('getCreateTransformSettingsRequestBody() with multiple settings', () => {
const transformDetailsState: Partial = {
transformSettingsDocsPerSecond: 400,
diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts
index 82faa8021381..e4cfd0a874f0 100644
--- a/x-pack/plugins/transform/public/app/common/request.ts
+++ b/x-pack/plugins/transform/public/app/common/request.ts
@@ -20,6 +20,7 @@ import type {
import type { SavedSearchQuery } from '../hooks/use_search_items';
import type { StepDefineExposedState } from '../sections/create_transform/components/step_define';
import type { StepDetailsExposedState } from '../sections/create_transform/components/step_details';
+import { isPopulatedObject } from './utils/object_utils';
export interface SimpleQuery {
query_string: {
@@ -57,10 +58,34 @@ export function isDefaultQuery(query: PivotQuery): boolean {
return isSimpleQuery(query) && query.query_string.query === '*';
}
+export function getCombinedRuntimeMappings(
+ indexPattern: IndexPattern | undefined,
+ runtimeMappings?: StepDefineExposedState['runtimeMappings']
+): StepDefineExposedState['runtimeMappings'] | undefined {
+ let combinedRuntimeMappings = {};
+
+ // Use runtime field mappings defined inline from API
+ if (isPopulatedObject(runtimeMappings)) {
+ combinedRuntimeMappings = { ...combinedRuntimeMappings, ...runtimeMappings };
+ }
+
+ // And runtime field mappings defined by index pattern
+ if (indexPattern !== undefined) {
+ const ipRuntimeMappings = indexPattern.getComputedFields().runtimeFields;
+ combinedRuntimeMappings = { ...combinedRuntimeMappings, ...ipRuntimeMappings };
+ }
+
+ if (isPopulatedObject(combinedRuntimeMappings)) {
+ return combinedRuntimeMappings;
+ }
+ return undefined;
+}
+
export function getPreviewTransformRequestBody(
indexPatternTitle: IndexPattern['title'],
query: PivotQuery,
- partialRequest?: StepDefineExposedState['previewRequest'] | undefined
+ partialRequest?: StepDefineExposedState['previewRequest'] | undefined,
+ runtimeMappings?: StepDefineExposedState['runtimeMappings']
): PostTransformsPreviewRequestSchema {
const index = indexPatternTitle.split(',').map((name: string) => name.trim());
@@ -68,6 +93,7 @@ export function getPreviewTransformRequestBody(
source: {
index,
...(!isDefaultQuery(query) && !isMatchAllQuery(query) ? { query } : {}),
+ ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}),
},
...(partialRequest ?? {}),
};
@@ -95,7 +121,8 @@ export const getCreateTransformRequestBody = (
...getPreviewTransformRequestBody(
indexPatternTitle,
getPivotQuery(pivotState.searchQuery),
- pivotState.previewRequest
+ pivotState.previewRequest,
+ pivotState.runtimeMappings
),
// conditionally add optional description
...(transformDetailsState.transformDescription !== ''
diff --git a/x-pack/plugins/transform/public/app/common/utils/object_utils.ts b/x-pack/plugins/transform/public/app/common/utils/object_utils.ts
new file mode 100644
index 000000000000..4bbd0c1c2810
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/common/utils/object_utils.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const isPopulatedObject = >(arg: any): arg is T => {
+ return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0;
+};
diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx
index d7a760503a00..bd361afac2d8 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx
+++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx
@@ -25,6 +25,7 @@ jest.mock('./use_api');
import { useAppDependencies } from '../__mocks__/app_dependencies';
import { MlSharedContext } from '../__mocks__/shared_context';
+import { RuntimeField } from '../../../../../../src/plugins/data/common/index_patterns';
const query: SimpleQuery = {
query_string: {
@@ -33,13 +34,21 @@ const query: SimpleQuery = {
},
};
+const runtimeMappings = {
+ rt_bytes_bigger: {
+ type: 'double',
+ script: {
+ source: "emit(doc['bytes'].value * 2.0)",
+ },
+ } as RuntimeField,
+};
+
describe('Transform: useIndexData()', () => {
test('indexPattern set triggers loading', async () => {
const mlShared = await getMlSharedImports();
const wrapper: FC = ({ children }) => (
{children}
);
-
const { result, waitForNextUpdate } = renderHook(
() =>
useIndexData(
@@ -48,7 +57,8 @@ describe('Transform: useIndexData()', () => {
title: 'the-title',
fields: [],
} as unknown) as SearchItems['indexPattern'],
- query
+ query,
+ runtimeMappings
),
{ wrapper }
);
@@ -77,7 +87,7 @@ describe('Transform: with useIndexData()', () => {
ml: { DataGrid },
} = useAppDependencies();
const props = {
- ...useIndexData(indexPattern, { match_all: {} }),
+ ...useIndexData(indexPattern, { match_all: {} }, runtimeMappings),
copyToClipboard: 'the-copy-to-clipboard-code',
copyToClipboardDescription: 'the-copy-to-clipboard-description',
dataTestSubj: 'the-data-test-subj',
diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts
index ff2d5d2a8d71..abc63d886dbc 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts
+++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { useEffect } from 'react';
+import { useEffect, useMemo } from 'react';
import { EuiDataGridColumn } from '@elastic/eui';
@@ -21,10 +21,12 @@ import { SearchItems } from './use_search_items';
import { useApi } from './use_api';
import { useAppDependencies, useToastNotifications } from '../app_dependencies';
+import type { StepDefineExposedState } from '../sections/create_transform/components/step_define/common';
export const useIndexData = (
indexPattern: SearchItems['indexPattern'],
- query: PivotQuery
+ query: PivotQuery,
+ combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings']
): UseIndexDataReturnType => {
const api = useApi();
const toastNotifications = useToastNotifications();
@@ -32,6 +34,7 @@ export const useIndexData = (
ml: {
getFieldType,
getDataGridSchemaFromKibanaFieldType,
+ getDataGridSchemaFromESFieldType,
getFieldsFromKibanaIndexPattern,
showDataGridColumnChartErrorMessageToast,
useDataGrid,
@@ -43,14 +46,37 @@ export const useIndexData = (
const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern);
- // EuiDataGrid State
- const columns: EuiDataGridColumn[] = [
- ...indexPatternFields.map((id) => {
+ const columns: EuiDataGridColumn[] = useMemo(() => {
+ let result: Array<{ id: string; schema: string | undefined }> = [];
+
+ // Get the the runtime fields that are defined from API field and index patterns
+ if (combinedRuntimeMappings !== undefined) {
+ result = Object.keys(combinedRuntimeMappings).map((fieldName) => {
+ const field = combinedRuntimeMappings[fieldName];
+ const schema = getDataGridSchemaFromESFieldType(field.type);
+ return { id: fieldName, schema };
+ });
+ }
+
+ // Combine the runtime field that are defined from API field
+ indexPatternFields.forEach((id) => {
const field = indexPattern.fields.getByName(id);
- const schema = getDataGridSchemaFromKibanaFieldType(field);
- return { id, schema };
- }),
- ];
+ if (!field?.runtimeField) {
+ const schema = getDataGridSchemaFromKibanaFieldType(field);
+ result.push({ id, schema });
+ }
+ });
+
+ return result.sort((a, b) => a.id.localeCompare(b.id));
+ }, [
+ indexPatternFields,
+ indexPattern.fields,
+ combinedRuntimeMappings,
+ getDataGridSchemaFromESFieldType,
+ getDataGridSchemaFromKibanaFieldType,
+ ]);
+
+ // EuiDataGrid State
const dataGrid = useDataGrid(columns);
@@ -92,9 +118,12 @@ export const useIndexData = (
from: pagination.pageIndex * pagination.pageSize,
size: pagination.pageSize,
...(Object.keys(sort).length > 0 ? { sort } : {}),
+ ...(typeof combinedRuntimeMappings === 'object' &&
+ Object.keys(combinedRuntimeMappings).length > 0
+ ? { runtime_mappings: combinedRuntimeMappings }
+ : {}),
},
};
-
const resp = await api.esSearch(esSearchRequest);
if (!isEsSearchResponse(resp)) {
@@ -134,7 +163,17 @@ export const useIndexData = (
fetchDataGridData();
// custom comparison
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]);
+ }, [
+ indexPattern.title,
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ JSON.stringify([
+ query,
+ pagination,
+ sortingColumns,
+ indexPatternFields,
+ combinedRuntimeMappings,
+ ]),
+ ]);
useEffect(() => {
if (chartsVisible) {
diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
index 673d8d38aa8f..62b3a077df5e 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
+++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
@@ -71,7 +71,8 @@ export const usePivotData = (
indexPatternTitle: SearchItems['indexPattern']['title'],
query: PivotQuery,
validationStatus: StepDefineExposedState['validationStatus'],
- requestPayload: StepDefineExposedState['previewRequest']
+ requestPayload: StepDefineExposedState['previewRequest'],
+ combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings']
): UseIndexDataReturnType => {
const [
previewMappingsProperties,
@@ -79,7 +80,13 @@ export const usePivotData = (
] = useState({});
const api = useApi();
const {
- ml: { formatHumanReadableDateTimeSeconds, multiColumnSortFactory, useDataGrid, INDEX_STATUS },
+ ml: {
+ getDataGridSchemaFromESFieldType,
+ formatHumanReadableDateTimeSeconds,
+ multiColumnSortFactory,
+ useDataGrid,
+ INDEX_STATUS,
+ },
} = useAppDependencies();
// Filters mapping properties of type `object`, which get returned for nested field parents.
@@ -97,38 +104,7 @@ export const usePivotData = (
// EuiDataGrid State
const columns: EuiDataGridColumn[] = columnKeys.map((id) => {
const field = previewMappingsProperties[id];
-
- // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
- // To fall back to the default string schema it needs to be undefined.
- let schema;
-
- switch (field?.type) {
- case ES_FIELD_TYPES.GEO_POINT:
- case ES_FIELD_TYPES.GEO_SHAPE:
- schema = 'json';
- break;
- case ES_FIELD_TYPES.BOOLEAN:
- schema = 'boolean';
- break;
- case ES_FIELD_TYPES.DATE:
- case ES_FIELD_TYPES.DATE_NANOS:
- schema = 'datetime';
- break;
- case ES_FIELD_TYPES.BYTE:
- case ES_FIELD_TYPES.DOUBLE:
- case ES_FIELD_TYPES.FLOAT:
- case ES_FIELD_TYPES.HALF_FLOAT:
- case ES_FIELD_TYPES.INTEGER:
- case ES_FIELD_TYPES.LONG:
- case ES_FIELD_TYPES.SCALED_FLOAT:
- case ES_FIELD_TYPES.SHORT:
- schema = 'numeric';
- break;
- // keep schema undefined for text based columns
- case ES_FIELD_TYPES.KEYWORD:
- case ES_FIELD_TYPES.TEXT:
- break;
- }
+ const schema = getDataGridSchemaFromESFieldType(field?.type);
return { id, schema };
});
@@ -159,7 +135,12 @@ export const usePivotData = (
setNoDataMessage('');
setStatus(INDEX_STATUS.LOADING);
- const previewRequest = getPreviewTransformRequestBody(indexPatternTitle, query, requestPayload);
+ const previewRequest = getPreviewTransformRequestBody(
+ indexPatternTitle,
+ query,
+ requestPayload,
+ combinedRuntimeMappings
+ );
const resp = await api.getTransformsPreview(previewRequest);
if (!isPostTransformsPreviewResponseSchema(resp)) {
@@ -196,11 +177,7 @@ export const usePivotData = (
getPreviewData();
// custom comparison
/* eslint-disable react-hooks/exhaustive-deps */
- }, [
- indexPatternTitle,
- JSON.stringify([requestPayload, query]),
- /* eslint-enable react-hooks/exhaustive-deps */
- ]);
+ }, [indexPatternTitle, JSON.stringify([requestPayload, query, combinedRuntimeMappings])]);
if (sortingColumns.length > 0) {
tableItems.sort(multiColumnSortFactory(sortingColumns));
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx
new file mode 100644
index 000000000000..087bae97e287
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { isEqual } from 'lodash';
+import React, { memo, FC } from 'react';
+
+import { EuiCodeEditor } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+
+import { StepDefineFormHook } from '../step_define';
+
+export const AdvancedRuntimeMappingsEditor: FC = memo(
+ ({
+ actions: {
+ convertToJson,
+ setAdvancedRuntimeMappingsConfig,
+ setRuntimeMappingsEditorApplyButtonEnabled,
+ },
+ state: { advancedEditorRuntimeMappingsLastApplied, advancedRuntimeMappingsConfig, xJsonMode },
+ }) => {
+ return (
+ {
+ setAdvancedRuntimeMappingsConfig(d);
+
+ // Disable the "Apply"-Button if the config hasn't changed.
+ if (advancedEditorRuntimeMappingsLastApplied === d) {
+ setRuntimeMappingsEditorApplyButtonEnabled(false);
+ return;
+ }
+
+ // Try to parse the string passed on from the editor.
+ // If parsing fails, the "Apply"-Button will be disabled
+ try {
+ JSON.parse(convertToJson(d));
+ setRuntimeMappingsEditorApplyButtonEnabled(true);
+ } catch (e) {
+ setRuntimeMappingsEditorApplyButtonEnabled(false);
+ }
+ }}
+ setOptions={{
+ fontSize: '12px',
+ }}
+ theme="textmate"
+ aria-label={i18n.translate('xpack.transform.stepDefineForm.advancedEditorAriaLabel', {
+ defaultMessage: 'Advanced pivot editor',
+ })}
+ />
+ );
+ },
+ (prevProps, nextProps) => isEqual(pickProps(prevProps), pickProps(nextProps))
+);
+
+function pickProps(props: StepDefineFormHook['runtimeMappingsEditor']) {
+ return [
+ props.state.advancedEditorRuntimeMappingsLastApplied,
+ props.state.advancedRuntimeMappingsConfig,
+ ];
+}
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx
new file mode 100644
index 000000000000..be297c10a8f8
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/advanced_runtime_mappings_editor_switch.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { FC } from 'react';
+import { EuiSwitch } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { StepDefineFormHook } from '../step_define';
+
+export const AdvancedRuntimeMappingsEditorSwitch: FC<
+ StepDefineFormHook['runtimeMappingsEditor']
+> = (props) => {
+ const {
+ actions: { setRuntimeMappingsUpdated, toggleRuntimeMappingsEditor },
+ state: { isRuntimeMappingsEditorEnabled },
+ } = props;
+
+ // If switching to KQL after updating via editor - reset search
+ const toggleEditorHandler = (reset = false) => {
+ if (reset === true) {
+ setRuntimeMappingsUpdated(false);
+ }
+ toggleRuntimeMappingsEditor(reset);
+ };
+
+ return (
+ toggleEditorHandler()}
+ data-test-subj="transformAdvancedRuntimeMappingsEditorSwitch"
+ />
+ );
+};
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/index.ts
new file mode 100644
index 000000000000..89a05690cab5
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor_switch/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { AdvancedRuntimeMappingsEditorSwitch } from './advanced_runtime_mappings_editor_switch';
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx
new file mode 100644
index 000000000000..f3c121a86cdc
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx
@@ -0,0 +1,175 @@
+/*
+ * 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, { FC } from 'react';
+import {
+ EuiButton,
+ EuiButtonIcon,
+ EuiCopy,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { StepDefineFormHook } from '../step_define';
+import { AdvancedRuntimeMappingsEditor } from '../advanced_runtime_mappings_editor/advanced_runtime_mappings_editor';
+import { AdvancedRuntimeMappingsEditorSwitch } from '../advanced_runtime_mappings_editor_switch';
+import {
+ isPivotGroupByConfigWithUiSupport,
+ PivotAggsConfigWithUiSupport,
+} from '../../../../common';
+import { isPivotAggConfigWithUiSupport } from '../../../../common/pivot_group_by';
+
+const advancedEditorsSidebarWidth = '220px';
+const COPY_TO_CLIPBOARD_RUNTIME_MAPPINGS = i18n.translate(
+ 'xpack.transform.indexPreview.copyRuntimeMappingsClipboardTooltip',
+ {
+ defaultMessage: 'Copy Dev Console statement of the runtime mappings to the clipboard.',
+ }
+);
+
+export const AdvancedRuntimeMappingsSettings: FC = (props) => {
+ const {
+ actions: { applyRuntimeMappingsEditorChanges },
+ state: {
+ runtimeMappings,
+ advancedRuntimeMappingsConfig,
+ isRuntimeMappingsEditorApplyButtonEnabled,
+ isRuntimeMappingsEditorEnabled,
+ },
+ } = props.runtimeMappingsEditor;
+ const {
+ actions: { deleteAggregation, deleteGroupBy },
+ state: { groupByList, aggList },
+ } = props.pivotConfig;
+
+ const applyChanges = () => {
+ const nextConfig = JSON.parse(advancedRuntimeMappingsConfig);
+ const previousConfig = runtimeMappings;
+
+ applyRuntimeMappingsEditorChanges();
+
+ // If the user updates the name of the runtime mapping fields
+ // delete any groupBy or aggregation associated with the deleted field
+ Object.keys(groupByList).forEach((groupByKey) => {
+ const groupBy = groupByList[groupByKey];
+ if (
+ isPivotGroupByConfigWithUiSupport(groupBy) &&
+ previousConfig?.hasOwnProperty(groupBy.field) &&
+ !nextConfig.hasOwnProperty(groupBy.field)
+ ) {
+ deleteGroupBy(groupByKey);
+ }
+ });
+ Object.keys(aggList).forEach((aggName) => {
+ const agg = aggList[aggName] as PivotAggsConfigWithUiSupport;
+ if (
+ isPivotAggConfigWithUiSupport(agg) &&
+ agg.field !== undefined &&
+ previousConfig?.hasOwnProperty(agg.field) &&
+ !nextConfig.hasOwnProperty(agg.field)
+ ) {
+ deleteAggregation(aggName);
+ }
+ });
+ };
+ return (
+ <>
+
+
+
+
+
+ {runtimeMappings !== undefined && Object.keys(runtimeMappings).length > 0 ? (
+
+ ) : (
+
+ )}
+
+ {isRuntimeMappingsEditorEnabled && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {(copy: () => void) => (
+
+ )}
+
+
+
+
+
+ {isRuntimeMappingsEditorEnabled && (
+
+
+
+ {i18n.translate(
+ 'xpack.transform.stepDefineForm.advancedRuntimeMappingsEditorHelpText',
+ {
+ defaultMessage:
+ 'The advanced editor allows you to edit the runtime mappings of the transform configuration.',
+ }
+ )}
+
+
+
+ {i18n.translate(
+ 'xpack.transform.stepDefineForm.advancedSourceEditorApplyButtonText',
+ {
+ defaultMessage: 'Apply changes',
+ }
+ )}
+
+
+ )}
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/index.ts
new file mode 100644
index 000000000000..69b3bc36a559
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { AdvancedRuntimeMappingsSettings } from './advanced_runtime_mappings_settings';
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
index 807830d74989..34832ec968e2 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
@@ -46,6 +46,9 @@ import {
PutTransformsLatestRequestSchema,
PutTransformsPivotRequestSchema,
} from '../../../../../../common/api_schemas/transforms';
+import type { RuntimeField } from '../../../../../../../../../src/plugins/data/common/index_patterns';
+import { isPopulatedObject } from '../../../../common/utils/object_utils';
+import { isLatestTransform } from '../../../../../../common/types/transform';
export interface StepDetailsExposedState {
created: boolean;
@@ -189,12 +192,19 @@ export const StepCreateForm: FC = React.memo(
const createKibanaIndexPattern = async () => {
setLoading(true);
const indexPatternName = transformConfig.dest.index;
+ const runtimeMappings = transformConfig.source.runtime_mappings as Record<
+ string,
+ RuntimeField
+ >;
try {
const newIndexPattern = await indexPatterns.createAndSave(
{
title: indexPatternName,
timeFieldName,
+ ...(isPopulatedObject(runtimeMappings) && isLatestTransform(transformConfig)
+ ? { runtimeFieldMap: runtimeMappings }
+ : {}),
},
false,
true
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts
index 77b60b6f5966..6298874a2036 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts
@@ -30,12 +30,19 @@ import { TRANSFORM_FUNCTION } from '../../../../../../../common/constants';
import { StepDefineFormProps } from '../step_define_form';
import { validateLatestConfig } from '../hooks/use_latest_function_config';
import { validatePivotConfig } from '../hooks/use_pivot_config';
+import { getCombinedRuntimeMappings } from '../../../../../common/request';
export function applyTransformConfigToDefineState(
state: StepDefineExposedState,
transformConfig?: TransformBaseConfig,
indexPattern?: StepDefineFormProps['searchItems']['indexPattern']
): StepDefineExposedState {
+ // apply runtime mappings from both the index pattern and inline configurations
+ state.runtimeMappings = getCombinedRuntimeMappings(
+ indexPattern,
+ transformConfig?.source?.runtime_mappings
+ );
+
if (transformConfig === undefined) {
return state;
}
@@ -107,6 +114,5 @@ export function applyTransformConfigToDefineState(
// applying a transform config to wizard state will always result in a valid configuration
state.valid = true;
-
return state;
}
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts
index deaaddc44ba7..fcdbac8c7ff3 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts
@@ -8,6 +8,7 @@
import { getPivotDropdownOptions } from '../common';
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import { FilterAggForm } from './filter_agg/components';
+import type { RuntimeField } from '../../../../../../../../../../src/plugins/data/common/index_patterns';
describe('Transform: Define Pivot Common', () => {
test('getPivotDropdownOptions()', () => {
@@ -109,5 +110,169 @@ describe('Transform: Define Pivot Common', () => {
},
},
});
+
+ const runtimeMappings = {
+ rt_bytes_bigger: {
+ type: 'double',
+ script: {
+ source: "emit(doc['bytes'].value * 2.0)",
+ },
+ } as RuntimeField,
+ };
+ const optionsWithRuntimeFields = getPivotDropdownOptions(indexPattern, runtimeMappings);
+ expect(optionsWithRuntimeFields).toMatchObject({
+ aggOptions: [
+ {
+ label: ' the-f[i]e>ld ',
+ options: [
+ { label: 'avg( the-f[i]e>ld )' },
+ { label: 'cardinality( the-f[i]e>ld )' },
+ { label: 'max( the-f[i]e>ld )' },
+ { label: 'min( the-f[i]e>ld )' },
+ { label: 'percentiles( the-f[i]e>ld )' },
+ { label: 'sum( the-f[i]e>ld )' },
+ { label: 'value_count( the-f[i]e>ld )' },
+ { label: 'filter( the-f[i]e>ld )' },
+ ],
+ },
+ {
+ label: 'rt_bytes_bigger',
+ options: [
+ { label: 'avg(rt_bytes_bigger)' },
+ { label: 'cardinality(rt_bytes_bigger)' },
+ { label: 'max(rt_bytes_bigger)' },
+ { label: 'min(rt_bytes_bigger)' },
+ { label: 'percentiles(rt_bytes_bigger)' },
+ { label: 'sum(rt_bytes_bigger)' },
+ { label: 'value_count(rt_bytes_bigger)' },
+ { label: 'filter(rt_bytes_bigger)' },
+ ],
+ },
+ ],
+ aggOptionsData: {
+ 'avg( the-f[i]e>ld )': {
+ agg: 'avg',
+ aggName: 'the-field.avg',
+ dropDownName: 'avg( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ },
+ 'cardinality( the-f[i]e>ld )': {
+ agg: 'cardinality',
+ aggName: 'the-field.cardinality',
+ dropDownName: 'cardinality( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ },
+ 'max( the-f[i]e>ld )': {
+ agg: 'max',
+ aggName: 'the-field.max',
+ dropDownName: 'max( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ },
+ 'min( the-f[i]e>ld )': {
+ agg: 'min',
+ aggName: 'the-field.min',
+ dropDownName: 'min( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ },
+ 'percentiles( the-f[i]e>ld )': {
+ agg: 'percentiles',
+ aggName: 'the-field.percentiles',
+ dropDownName: 'percentiles( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ percents: [1, 5, 25, 50, 75, 95, 99],
+ },
+ 'sum( the-f[i]e>ld )': {
+ agg: 'sum',
+ aggName: 'the-field.sum',
+ dropDownName: 'sum( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ },
+ 'value_count( the-f[i]e>ld )': {
+ agg: 'value_count',
+ aggName: 'the-field.value_count',
+ dropDownName: 'value_count( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ },
+ 'filter( the-f[i]e>ld )': {
+ agg: 'filter',
+ aggName: 'the-field.filter',
+ dropDownName: 'filter( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ isSubAggsSupported: true,
+ AggFormComponent: FilterAggForm,
+ },
+ 'avg(rt_bytes_bigger)': {
+ agg: 'avg',
+ aggName: 'rt_bytes_bigger.avg',
+ dropDownName: 'avg(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ },
+ 'cardinality(rt_bytes_bigger)': {
+ agg: 'cardinality',
+ aggName: 'rt_bytes_bigger.cardinality',
+ dropDownName: 'cardinality(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ },
+ 'max(rt_bytes_bigger)': {
+ agg: 'max',
+ aggName: 'rt_bytes_bigger.max',
+ dropDownName: 'max(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ },
+ 'min(rt_bytes_bigger)': {
+ agg: 'min',
+ aggName: 'rt_bytes_bigger.min',
+ dropDownName: 'min(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ },
+ 'percentiles(rt_bytes_bigger)': {
+ agg: 'percentiles',
+ aggName: 'rt_bytes_bigger.percentiles',
+ dropDownName: 'percentiles(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ percents: [1, 5, 25, 50, 75, 95, 99],
+ },
+ 'sum(rt_bytes_bigger)': {
+ agg: 'sum',
+ aggName: 'rt_bytes_bigger.sum',
+ dropDownName: 'sum(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ },
+ 'value_count(rt_bytes_bigger)': {
+ agg: 'value_count',
+ aggName: 'rt_bytes_bigger.value_count',
+ dropDownName: 'value_count(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ },
+ 'filter(rt_bytes_bigger)': {
+ agg: 'filter',
+ aggName: 'rt_bytes_bigger.filter',
+ dropDownName: 'filter(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ isSubAggsSupported: true,
+ AggFormComponent: FilterAggForm,
+ },
+ },
+ groupByOptions: [
+ { label: 'histogram( the-f[i]e>ld )' },
+ { label: 'histogram(rt_bytes_bigger)' },
+ ],
+ groupByOptionsData: {
+ 'histogram( the-f[i]e>ld )': {
+ agg: 'histogram',
+ aggName: 'the-field',
+ dropDownName: 'histogram( the-f[i]e>ld )',
+ field: ' the-f[i]e>ld ',
+ interval: '10',
+ },
+ 'histogram(rt_bytes_bigger)': {
+ agg: 'histogram',
+ aggName: 'rt_bytes_bigger',
+ dropDownName: 'histogram(rt_bytes_bigger)',
+ field: 'rt_bytes_bigger',
+ interval: '10',
+ },
+ },
+ });
});
});
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx
index dae8f61aaa4d..7f9c4256f775 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx
@@ -10,11 +10,23 @@ import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { FilterAggForm } from './filter_agg_form';
import { CreateTransformWizardContext } from '../../../../wizard/wizard';
-import { KBN_FIELD_TYPES } from '../../../../../../../../../../../../src/plugins/data/common';
+import {
+ KBN_FIELD_TYPES,
+ RuntimeField,
+} from '../../../../../../../../../../../../src/plugins/data/common';
import { IndexPattern } from '../../../../../../../../../../../../src/plugins/data/public';
import { FilterTermForm } from './filter_term_form';
describe('FilterAggForm', () => {
+ const runtimeMappings = {
+ rt_bytes_bigger: {
+ type: 'double',
+ script: {
+ source: "emit(doc['bytes'].value * 2.0)",
+ },
+ } as RuntimeField,
+ };
+
const indexPattern = ({
fields: {
getByName: jest.fn((fieldName: string) => {
@@ -37,7 +49,7 @@ describe('FilterAggForm', () => {
const { getByLabelText, findByTestId, container } = render(
-
+
@@ -62,7 +74,7 @@ describe('FilterAggForm', () => {
const { findByTestId } = render(
-
+
@@ -90,7 +102,7 @@ describe('FilterAggForm', () => {
const { rerender, findByTestId } = render(
-
+
@@ -99,7 +111,7 @@ describe('FilterAggForm', () => {
// re-render the same component with different props
rerender(
-
+
@@ -127,7 +139,7 @@ describe('FilterAggForm', () => {
const { findByTestId, container } = render(
-
+
{
- const { indexPattern } = useContext(CreateTransformWizardContext);
+ const { indexPattern, runtimeMappings } = useContext(CreateTransformWizardContext);
- const filterAggsOptions = useMemo(() => getSupportedFilterAggs(selectedField, indexPattern!), [
- indexPattern,
- selectedField,
- ]);
+ const filterAggsOptions = useMemo(
+ () => getSupportedFilterAggs(selectedField, indexPattern!, runtimeMappings),
+ [indexPattern, selectedField, runtimeMappings]
+ );
useUpdateEffect(() => {
// reset filter agg on field change
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx
index 2e9ad761d3b7..67c904946d30 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx
@@ -17,6 +17,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { FilterAggConfigRange } from '../types';
+const BUTTON_SIZE = 40;
/**
* Form component for the range filter aggregation for number type fields.
*/
@@ -45,7 +46,7 @@ export const FilterRangeForm: FilterAggConfigRange['aggTypeConfig']['FilterAggFo
return (
<>
-
+
{
updateConfig({ includeFrom: e.target.checked });
}}
@@ -94,13 +96,14 @@ export const FilterRangeForm: FilterAggConfigRange['aggTypeConfig']['FilterAggFo
step="any"
append={
{
updateConfig({ includeTo: !includeTo });
}}
fill={includeTo}
>
- {includeTo ? '≤' : '<'}s
+ {includeTo ? '≤' : '<'}
}
/>
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx
index ad06cfb31a62..f2db6167c163 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx
@@ -26,7 +26,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm
selectedField,
}) => {
const api = useApi();
- const { indexPattern } = useContext(CreateTransformWizardContext);
+ const { indexPattern, runtimeMappings } = useContext(CreateTransformWizardContext);
const toastNotifications = useToastNotifications();
const [options, setOptions] = useState([]);
@@ -38,6 +38,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm
const esSearchRequest = {
index: indexPattern!.title,
body: {
+ ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}),
query: {
wildcard: {
[selectedField!]: {
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts
index d3b1df41b3cf..c75da651f79d 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts
@@ -30,5 +30,8 @@ export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineE
isValid: false,
},
previewRequest: undefined,
+ runtimeMappings: undefined,
+ runtimeMappingsUpdated: false,
+ isRuntimeMappingsEditorEnabled: false,
};
}
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts
index 6845d096a2e0..c88b60498968 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts
@@ -7,6 +7,7 @@
import { EuiComboBoxOptionOption } from '@elastic/eui';
import {
+ ES_FIELD_TYPES,
IndexPattern,
KBN_FIELD_TYPES,
} from '../../../../../../../../../../src/plugins/data/public';
@@ -24,11 +25,40 @@ import {
import { getDefaultAggregationConfig } from './get_default_aggregation_config';
import { getDefaultGroupByConfig } from './get_default_group_by_config';
-import { Field } from './types';
+import type { Field, StepDefineExposedState } from './types';
+import { isPopulatedObject } from '../../../../../common/utils/object_utils';
const illegalEsAggNameChars = /[[\]>]/g;
-export function getPivotDropdownOptions(indexPattern: IndexPattern) {
+export function getKibanaFieldTypeFromEsType(type: string): KBN_FIELD_TYPES {
+ switch (type) {
+ case ES_FIELD_TYPES.FLOAT:
+ case ES_FIELD_TYPES.HALF_FLOAT:
+ case ES_FIELD_TYPES.SCALED_FLOAT:
+ case ES_FIELD_TYPES.DOUBLE:
+ case ES_FIELD_TYPES.INTEGER:
+ case ES_FIELD_TYPES.LONG:
+ case ES_FIELD_TYPES.SHORT:
+ case ES_FIELD_TYPES.UNSIGNED_LONG:
+ return KBN_FIELD_TYPES.NUMBER;
+
+ case ES_FIELD_TYPES.DATE:
+ case ES_FIELD_TYPES.DATE_NANOS:
+ return KBN_FIELD_TYPES.DATE;
+
+ case ES_FIELD_TYPES.KEYWORD:
+ case ES_FIELD_TYPES.STRING:
+ return KBN_FIELD_TYPES.STRING;
+
+ default:
+ return type as KBN_FIELD_TYPES;
+ }
+}
+
+export function getPivotDropdownOptions(
+ indexPattern: IndexPattern,
+ runtimeMappings?: StepDefineExposedState['runtimeMappings']
+) {
// The available group by options
const groupByOptions: EuiComboBoxOptionOption[] = [];
const groupByOptionsData: PivotGroupByConfigWithUiSupportDict = {};
@@ -38,11 +68,26 @@ export function getPivotDropdownOptions(indexPattern: IndexPattern) {
const aggOptionsData: PivotAggsConfigWithUiSupportDict = {};
const ignoreFieldNames = ['_id', '_index', '_type'];
- const fields = indexPattern.fields
- .filter((field) => field.aggregatable === true && !ignoreFieldNames.includes(field.name))
+ const indexPatternFields = indexPattern.fields
+ .filter(
+ (field) =>
+ field.aggregatable === true && !ignoreFieldNames.includes(field.name) && !field.runtimeField
+ )
.map((field): Field => ({ name: field.name, type: field.type as KBN_FIELD_TYPES }));
- fields.forEach((field) => {
+ // Support for runtime_mappings that are defined by queries
+ let runtimeFields: Field[] = [];
+ if (isPopulatedObject(runtimeMappings)) {
+ runtimeFields = Object.keys(runtimeMappings).map((fieldName) => {
+ const field = runtimeMappings[fieldName];
+ return { name: fieldName, type: getKibanaFieldTypeFromEsType(field.type) };
+ });
+ }
+
+ const sortByLabel = (a: Field, b: Field) => a.name.localeCompare(b.name);
+
+ const combinedFields = [...indexPatternFields, ...runtimeFields].sort(sortByLabel);
+ combinedFields.forEach((field) => {
// Group by
const availableGroupByAggs: [] = getNestedProperty(pivotGroupByFieldSupport, field.type);
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts
index d1325e4af5ce..cdba7a3f5482 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts
@@ -9,7 +9,11 @@ import { KBN_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/
import { EsFieldName } from '../../../../../../../common/types/fields';
-import { PivotAggsConfigDict, PivotGroupByConfigDict } from '../../../../../common';
+import {
+ PivotAggsConfigDict,
+ PivotGroupByConfigDict,
+ PivotGroupByConfigWithUiSupportDict,
+} from '../../../../../common';
import { SavedSearchQuery } from '../../../../../hooks/use_search_items';
import { QUERY_LANGUAGE } from './constants';
@@ -30,10 +34,24 @@ export interface Field {
type: KBN_FIELD_TYPES;
}
+// Replace this with import once #88995 is merged
+const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const;
+type RuntimeType = typeof RUNTIME_FIELD_TYPES[number];
+
+export interface RuntimeField {
+ type: RuntimeType;
+ script:
+ | string
+ | {
+ source: string;
+ };
+}
+
+export type RuntimeMappings = Record;
export interface StepDefineExposedState {
transformFunction: TransformFunction;
aggList: PivotAggsConfigDict;
- groupByList: PivotGroupByConfigDict;
+ groupByList: PivotGroupByConfigDict | PivotGroupByConfigWithUiSupportDict;
latestConfig: LatestFunctionConfigUI;
isAdvancedPivotEditorEnabled: boolean;
isAdvancedSourceEditorEnabled: boolean;
@@ -47,6 +65,9 @@ export interface StepDefineExposedState {
* Undefined when the form is incomplete or invalid
*/
previewRequest: { latest: LatestFunctionConfig } | { pivot: PivotConfigDefinition } | undefined;
+ runtimeMappings?: RuntimeMappings;
+ runtimeMappingsUpdated: boolean;
+ isRuntimeMappingsEditorEnabled: boolean;
}
export function isPivotPartialRequest(arg: any): arg is { pivot: PivotConfigDefinition } {
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts
new file mode 100644
index 000000000000..9bb5f91ae03c
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts
@@ -0,0 +1,102 @@
+/*
+ * 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 { useState } from 'react';
+import { XJsonMode } from '@kbn/ace';
+import { StepDefineExposedState } from '../common';
+import { XJson } from '../../../../../../../../../../src/plugins/es_ui_shared/public';
+
+const { useXJsonMode } = XJson;
+const xJsonMode = new XJsonMode();
+
+export const useAdvancedRuntimeMappingsEditor = (defaults: StepDefineExposedState) => {
+ const stringifiedRuntimeMappings = JSON.stringify(defaults.runtimeMappings, null, 2);
+
+ // Advanced editor for source config state
+ const [runtimeMappingsUpdated, setRuntimeMappingsUpdated] = useState(
+ defaults.runtimeMappingsUpdated
+ );
+ const [runtimeMappings, setRuntimeMappings] = useState(defaults.runtimeMappings);
+
+ const [
+ isRuntimeMappingsEditorSwitchModalVisible,
+ setRuntimeMappingsEditorSwitchModalVisible,
+ ] = useState(false);
+
+ const [isRuntimeMappingsEditorEnabled, setRuntimeMappingsEditorEnabled] = useState(
+ defaults.isRuntimeMappingsEditorEnabled
+ );
+
+ const [
+ isRuntimeMappingsEditorApplyButtonEnabled,
+ setRuntimeMappingsEditorApplyButtonEnabled,
+ ] = useState(false);
+
+ const [
+ advancedEditorRuntimeMappingsLastApplied,
+ setAdvancedEditorRuntimeMappingsLastApplied,
+ ] = useState(stringifiedRuntimeMappings);
+
+ const [advancedEditorRuntimeMappings, setAdvancedEditorRuntimeMappings] = useState(
+ stringifiedRuntimeMappings
+ );
+
+ const {
+ convertToJson,
+ setXJson: setAdvancedRuntimeMappingsConfig,
+ xJson: advancedRuntimeMappingsConfig,
+ } = useXJsonMode(stringifiedRuntimeMappings ?? '');
+
+ const applyRuntimeMappingsEditorChanges = () => {
+ const parsedRuntimeMappings = JSON.parse(advancedRuntimeMappingsConfig);
+ const prettySourceConfig = JSON.stringify(parsedRuntimeMappings, null, 2);
+ setRuntimeMappingsUpdated(true);
+ setRuntimeMappings(parsedRuntimeMappings);
+ setAdvancedEditorRuntimeMappings(prettySourceConfig);
+ setAdvancedEditorRuntimeMappingsLastApplied(prettySourceConfig);
+ setRuntimeMappingsEditorApplyButtonEnabled(false);
+ };
+
+ // If switching to KQL after updating via editor - reset search
+ const toggleRuntimeMappingsEditor = (reset = false) => {
+ if (reset === true) {
+ setRuntimeMappingsUpdated(false);
+ }
+ if (isRuntimeMappingsEditorEnabled === false) {
+ setAdvancedEditorRuntimeMappingsLastApplied(advancedEditorRuntimeMappings);
+ }
+
+ setRuntimeMappingsEditorEnabled(!isRuntimeMappingsEditorEnabled);
+ setRuntimeMappingsEditorApplyButtonEnabled(false);
+ };
+
+ return {
+ actions: {
+ applyRuntimeMappingsEditorChanges,
+ setRuntimeMappingsEditorApplyButtonEnabled,
+ setRuntimeMappingsEditorEnabled,
+ setAdvancedEditorRuntimeMappings,
+ setAdvancedEditorRuntimeMappingsLastApplied,
+ setRuntimeMappingsEditorSwitchModalVisible,
+ setRuntimeMappingsUpdated,
+ toggleRuntimeMappingsEditor,
+ convertToJson,
+ setAdvancedRuntimeMappingsConfig,
+ },
+ state: {
+ advancedEditorRuntimeMappings,
+ advancedEditorRuntimeMappingsLastApplied,
+ isRuntimeMappingsEditorApplyButtonEnabled,
+ isRuntimeMappingsEditorEnabled,
+ isRuntimeMappingsEditorSwitchModalVisible,
+ runtimeMappingsUpdated,
+ advancedRuntimeMappingsConfig,
+ xJsonMode,
+ runtimeMappings,
+ },
+ };
+};
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts
index ecc8bf673d93..d52bd3f5bf70 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts
@@ -32,19 +32,28 @@ export const latestConfigMapper = {
* Provides available options for unique_key and sort fields
* @param indexPattern
* @param aggConfigs
+ * @param runtimeMappings
*/
function getOptions(
indexPattern: StepDefineFormProps['searchItems']['indexPattern'],
- aggConfigs: AggConfigs
+ aggConfigs: AggConfigs,
+ runtimeMappings?: StepDefineExposedState['runtimeMappings']
) {
const aggConfig = aggConfigs.aggs[0];
const param = aggConfig.type.params.find((p) => p.type === 'field');
const filteredIndexPatternFields = param
- ? ((param as unknown) as FieldParamType).getAvailableFields(aggConfig)
+ ? ((param as unknown) as FieldParamType)
+ .getAvailableFields(aggConfig)
+ // runtimeMappings may already include runtime fields defined by the index pattern
+ .filter((ip) => ip.runtimeField === undefined)
: [];
const ignoreFieldNames = new Set(['_source', '_type', '_index', '_id', '_version', '_score']);
+ const runtimeFieldsOptions = runtimeMappings
+ ? Object.keys(runtimeMappings).map((k) => ({ label: k, value: k }))
+ : [];
+
const uniqueKeyOptions: Array> = filteredIndexPatternFields
.filter((v) => !ignoreFieldNames.has(v.name))
.map((v) => ({
@@ -52,7 +61,16 @@ function getOptions(
value: v.name,
}));
- const sortFieldOptions: Array> = indexPattern.fields
+ const runtimeFieldsSortOptions: Array> = runtimeMappings
+ ? Object.entries(runtimeMappings)
+ .filter(([fieldName, fieldMapping]) => fieldMapping.type === 'date')
+ .map(([fieldName, fieldMapping]) => ({
+ label: fieldName,
+ value: fieldName,
+ }))
+ : [];
+
+ const indexPatternFieldsSortOptions: Array> = indexPattern.fields
// The backend API for `latest` allows all field types for sort but the UI will be limited to `date`.
.filter((v) => !ignoreFieldNames.has(v.name) && v.sortable && v.type === 'date')
.map((v) => ({
@@ -60,7 +78,15 @@ function getOptions(
value: v.name,
}));
- return { uniqueKeyOptions, sortFieldOptions };
+ const sortByLabel = (a: EuiComboBoxOptionOption, b: EuiComboBoxOptionOption) =>
+ a.label.localeCompare(b.label);
+
+ return {
+ uniqueKeyOptions: [...uniqueKeyOptions, ...runtimeFieldsOptions].sort(sortByLabel),
+ sortFieldOptions: [...indexPatternFieldsSortOptions, ...runtimeFieldsSortOptions].sort(
+ sortByLabel
+ ),
+ };
}
/**
@@ -86,7 +112,8 @@ export function validateLatestConfig(config?: LatestFunctionConfig) {
export function useLatestFunctionConfig(
defaults: StepDefineExposedState['latestConfig'],
- indexPattern: StepDefineFormProps['searchItems']['indexPattern']
+ indexPattern: StepDefineFormProps['searchItems']['indexPattern'],
+ runtimeMappings: StepDefineExposedState['runtimeMappings']
): {
config: LatestFunctionConfigUI;
uniqueKeyOptions: Array>;
@@ -104,8 +131,8 @@ export function useLatestFunctionConfig(
const { uniqueKeyOptions, sortFieldOptions } = useMemo(() => {
const aggConfigs = data.search.aggs.createAggConfigs(indexPattern, [{ type: 'terms' }]);
- return getOptions(indexPattern, aggConfigs);
- }, [indexPattern, data.search.aggs]);
+ return getOptions(indexPattern, aggConfigs, runtimeMappings);
+ }, [indexPattern, data.search.aggs, runtimeMappings]);
const updateLatestFunctionConfig = useCallback(
(update) =>
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 1748f6f8fd48..a02d3bafac98 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
@@ -115,8 +115,8 @@ export const usePivotConfig = (
const toastNotifications = useToastNotifications();
const { aggOptions, aggOptionsData, groupByOptions, groupByOptionsData } = useMemo(
- () => getPivotDropdownOptions(indexPattern),
- [indexPattern]
+ () => getPivotDropdownOptions(indexPattern, defaults.runtimeMappings),
+ [defaults.runtimeMappings, indexPattern]
);
// The list of selected aggregations
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts
index c2f01db05ff3..0ceea070df1b 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts
@@ -19,6 +19,7 @@ import { usePivotConfig } from './use_pivot_config';
import { useSearchBar } from './use_search_bar';
import { useLatestFunctionConfig } from './use_latest_function_config';
import { TRANSFORM_FUNCTION } from '../../../../../../../common/constants';
+import { useAdvancedRuntimeMappingsEditor } from './use_advanced_runtime_mappings_editor';
export type StepDefineFormHook = ReturnType;
@@ -30,12 +31,18 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
const searchBar = useSearchBar(defaults, indexPattern);
const pivotConfig = usePivotConfig(defaults, indexPattern);
- const latestFunctionConfig = useLatestFunctionConfig(defaults.latestConfig, indexPattern);
+
+ const latestFunctionConfig = useLatestFunctionConfig(
+ defaults.latestConfig,
+ indexPattern,
+ defaults?.runtimeMappings
+ );
const previewRequest = getPreviewTransformRequestBody(
indexPattern.title,
searchBar.state.pivotQuery,
- pivotConfig.state.requestPayload
+ pivotConfig.state.requestPayload,
+ defaults?.runtimeMappings
);
// pivot config hook
@@ -44,12 +51,17 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
// source config hook
const advancedSourceEditor = useAdvancedSourceEditor(defaults, previewRequest);
+ // runtime mappings config hook
+ const runtimeMappingsEditor = useAdvancedRuntimeMappingsEditor(defaults);
+
useEffect(() => {
+ const runtimeMappings = runtimeMappingsEditor.state.runtimeMappings;
if (!advancedSourceEditor.state.isAdvancedSourceEditorEnabled) {
const previewRequestUpdate = getPreviewTransformRequestBody(
indexPattern.title,
searchBar.state.pivotQuery,
- pivotConfig.state.requestPayload
+ pivotConfig.state.requestPayload,
+ runtimeMappings
);
const stringifiedSourceConfigUpdate = JSON.stringify(
@@ -60,7 +72,6 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
advancedSourceEditor.actions.setAdvancedEditorSourceConfig(stringifiedSourceConfigUpdate);
}
-
onChange({
transformFunction,
latestConfig: latestFunctionConfig.config,
@@ -84,6 +95,9 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
transformFunction === TRANSFORM_FUNCTION.PIVOT
? pivotConfig.state.requestPayload
: latestFunctionConfig.requestPayload,
+ runtimeMappings,
+ runtimeMappingsUpdated: runtimeMappingsEditor.state.runtimeMappingsUpdated,
+ isRuntimeMappingsEditorEnabled: runtimeMappingsEditor.state.isRuntimeMappingsEditorEnabled,
});
// custom comparison
/* eslint-disable react-hooks/exhaustive-deps */
@@ -92,9 +106,13 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
JSON.stringify(advancedSourceEditor.state),
pivotConfig.state,
JSON.stringify(searchBar.state),
+ JSON.stringify([
+ runtimeMappingsEditor.state.runtimeMappings,
+ runtimeMappingsEditor.state.runtimeMappingsUpdated,
+ runtimeMappingsEditor.state.isRuntimeMappingsEditorEnabled,
+ ]),
latestFunctionConfig.config,
transformFunction,
- /* eslint-enable react-hooks/exhaustive-deps */
]);
return {
@@ -102,6 +120,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
setTransformFunction,
advancedPivotEditor,
advancedSourceEditor,
+ runtimeMappingsEditor,
pivotConfig,
latestFunctionConfig,
searchBar,
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
index a5d9310e586e..1ddb9aa61045 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
@@ -57,6 +57,7 @@ import { getAggConfigFromEsAgg } from '../../../../common/pivot_aggs';
import { TransformFunctionSelector } from './transform_function_selector';
import { TRANSFORM_FUNCTION } from '../../../../../../common/constants';
import { LatestFunctionForm } from './latest_function_form';
+import { AdvancedRuntimeMappingsSettings } from '../advanced_runtime_mappings_settings';
export interface StepDefineFormProps {
overrides?: StepDefineExposedState;
@@ -67,7 +68,6 @@ export interface StepDefineFormProps {
export const StepDefineForm: FC = React.memo((props) => {
const { searchItems } = props;
const { indexPattern } = searchItems;
-
const {
ml: { DataGrid },
} = useAppDependencies();
@@ -87,11 +87,14 @@ export const StepDefineForm: FC = React.memo((props) => {
const pivotQuery = stepDefineForm.searchBar.state.pivotQuery;
const indexPreviewProps = {
- ...useIndexData(indexPattern, stepDefineForm.searchBar.state.pivotQuery),
+ ...useIndexData(
+ indexPattern,
+ stepDefineForm.searchBar.state.pivotQuery,
+ stepDefineForm.runtimeMappingsEditor.state.runtimeMappings
+ ),
dataTestSubj: 'transformIndexPreview',
toastNotifications,
};
-
const { requestPayload, validationStatus } =
stepDefineForm.transformFunction === TRANSFORM_FUNCTION.PIVOT
? stepDefineForm.pivotConfig.state
@@ -102,7 +105,8 @@ export const StepDefineForm: FC = React.memo((props) => {
pivotQuery,
stepDefineForm.transformFunction === TRANSFORM_FUNCTION.PIVOT
? stepDefineForm.pivotConfig.state.requestPayload
- : stepDefineForm.latestFunctionConfig.requestPayload
+ : stepDefineForm.latestFunctionConfig.requestPayload,
+ stepDefineForm.runtimeMappingsEditor.state.runtimeMappings
);
const copyToClipboardSource = getIndexDevConsoleStatement(pivotQuery, indexPattern.title);
@@ -122,7 +126,13 @@ export const StepDefineForm: FC = React.memo((props) => {
);
const pivotPreviewProps = {
- ...usePivotData(indexPattern.title, pivotQuery, validationStatus, requestPayload),
+ ...usePivotData(
+ indexPattern.title,
+ pivotQuery,
+ validationStatus,
+ requestPayload,
+ stepDefineForm.runtimeMappingsEditor.state.runtimeMappings
+ ),
dataTestSubj: 'transformPivotPreview',
title: i18n.translate('xpack.transform.pivotPreview.transformPreviewTitle', {
defaultMessage: 'Transform preview',
@@ -273,7 +283,7 @@ export const StepDefineForm: FC = React.memo((props) => {
defaultMessage:
'The advanced editor allows you to edit the source query clause of the transform configuration.',
}
- )}{' '}
+ )}
{i18n.translate(
'xpack.transform.stepDefineForm.advancedEditorHelpTextLink',
@@ -304,6 +314,9 @@ export const StepDefineForm: FC = React.memo((props) => {
+
+
+
>
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx
index 614965c8a3ef..27e25596c980 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx
@@ -37,6 +37,7 @@ interface Props {
export const StepDefineSummary: FC = ({
formState: {
+ runtimeMappings,
searchString,
searchQuery,
groupByList,
@@ -57,14 +58,16 @@ export const StepDefineSummary: FC = ({
const previewRequest = getPreviewTransformRequestBody(
searchItems.indexPattern.title,
pivotQuery,
- partialPreviewRequest
+ partialPreviewRequest,
+ runtimeMappings
);
const pivotPreviewProps = usePivotData(
searchItems.indexPattern.title,
pivotQuery,
validationStatus,
- partialPreviewRequest
+ partialPreviewRequest,
+ runtimeMappings
);
const isModifiedQuery =
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
index 1fa16e26565b..0d39ec77d059 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
@@ -118,7 +118,8 @@ export const StepDetailsForm: FC = React.memo(
const previewRequest = getPreviewTransformRequestBody(
searchItems.indexPattern.title,
pivotQuery,
- partialPreviewRequest
+ partialPreviewRequest,
+ stepDefineState.runtimeMappings
);
const transformPreview = await api.getTransformsPreview(previewRequest);
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
index 9837ace27207..5ae464affa01 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
@@ -32,6 +32,7 @@ import {
} from '../step_details';
import { WizardNav } from '../wizard_nav';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
+import type { RuntimeMappings } from '../step_define/common/types';
enum KBN_MANAGEMENT_PAGE_CLASSNAME {
DEFAULT_BODY = 'mgtPage__body',
@@ -89,8 +90,12 @@ interface WizardProps {
searchItems: SearchItems;
}
-export const CreateTransformWizardContext = createContext<{ indexPattern: IndexPattern | null }>({
+export const CreateTransformWizardContext = createContext<{
+ indexPattern: IndexPattern | null;
+ runtimeMappings: RuntimeMappings | undefined;
+}>({
indexPattern: null,
+ runtimeMappings: undefined,
});
export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) => {
@@ -239,7 +244,9 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems })
const stepsConfig = [stepDefine, stepDetails, stepCreate];
return (
-
+
);
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx
index 2ee558d449c9..87ae90afdf9c 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx
@@ -29,7 +29,7 @@ export const ExpandedRowPreviewPane: FC = ({ transf
} = useAppDependencies();
const toastNotifications = useToastNotifications();
- const { searchQuery, validationStatus, previewRequest } = useMemo(
+ const { searchQuery, validationStatus, previewRequest, runtimeMappings } = useMemo(
() =>
applyTransformConfigToDefineState(
getDefaultStepDefineState({} as SearchItems),
@@ -48,7 +48,8 @@ export const ExpandedRowPreviewPane: FC = ({ transf
indexPatternTitle,
pivotQuery,
validationStatus,
- previewRequest
+ previewRequest,
+ runtimeMappings
);
return (
From e2a288edbaad453a48c7b2f8893c4d43238a244c Mon Sep 17 00:00:00 2001
From: Candace Park <56409205+parkiino@users.noreply.github.com>
Date: Tue, 16 Feb 2021 21:34:48 -0500
Subject: [PATCH 15/23] [Security Solution][Endpoint][Admin] Endpoint Details
UX Enhancements (#90870)
---
.../pages/endpoint_hosts/store/reducer.ts | 2 +
.../pages/endpoint_hosts/store/selectors.ts | 11 +++
.../management/pages/endpoint_hosts/types.ts | 4 +
.../view/details/endpoint_details.tsx | 99 +++++++++++++------
.../endpoint_hosts/view/details/index.tsx | 28 +++++-
.../view/details/policy_response.tsx | 2 +-
.../endpoint_hosts/view/host_constants.ts | 9 ++
.../pages/endpoint_hosts/view/index.test.tsx | 48 ++++-----
8 files changed, 137 insertions(+), 66 deletions(-)
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 099880942566..4547ae3b3424 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
@@ -43,6 +43,7 @@ export const initialEndpointListState: Immutable = {
endpointsTotalError: undefined,
queryStrategyVersion: undefined,
policyVersionInfo: undefined,
+ hostStatus: undefined,
};
/* eslint-disable-next-line complexity */
@@ -109,6 +110,7 @@ export const endpointListReducer: ImmutableReducer = (
...state,
details: action.payload.metadata,
policyVersionInfo: action.payload.policy_info,
+ hostStatus: action.payload.host_status,
detailsLoading: false,
detailsError: undefined,
};
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 313ef3ed403b..17ce24e7cda7 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
@@ -16,6 +16,7 @@ import {
HostPolicyResponseConfiguration,
HostPolicyResponseActionStatus,
MetadataQueryStrategyVersions,
+ HostStatus,
} from '../../../../../common/endpoint/types';
import { EndpointState, EndpointIndexUIQueryParams } from '../types';
import { extractListPaginationParams } from '../../../common/routing';
@@ -224,6 +225,16 @@ export const showView: (state: EndpointState) => 'policy_response' | 'details' =
}
);
+/**
+ * Returns the Host Status which is connected the fleet agent
+ */
+export const hostStatusInfo: (state: Immutable) => HostStatus = createSelector(
+ (state) => state.hostStatus,
+ (hostStatus) => {
+ return hostStatus ? hostStatus : HostStatus.ERROR;
+ }
+);
+
/**
* Returns the Policy Response overall status
*/
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 104c17d332bd..7e989276edeb 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
@@ -13,6 +13,7 @@ import {
AppLocation,
PolicyData,
MetadataQueryStrategyVersions,
+ HostStatus,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import { GetPackagesResponse } from '../../../../../fleet/common';
@@ -79,6 +80,9 @@ export interface EndpointState {
queryStrategyVersion?: MetadataQueryStrategyVersions;
/** The policy IDs and revision number of the corresponding agent, and endpoint. May be more recent than what's running */
policyVersionInfo?: HostInfo['policy_info'];
+ /** The status of the host, which is mapped to the Elastic Agent status in Fleet
+ */
+ hostStatus?: HostStatus;
}
/**
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
index ce16391206ec..eb3e534ba427 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
@@ -10,23 +10,27 @@ import {
EuiDescriptionList,
EuiHealth,
EuiHorizontalRule,
- EuiLink,
EuiListGroup,
EuiListGroupItem,
EuiIcon,
EuiText,
EuiFlexGroup,
EuiFlexItem,
+ EuiBadge,
} from '@elastic/eui';
import React, { memo, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { isPolicyOutOfDate } from '../../utils';
-import { HostInfo, HostMetadata } from '../../../../../../common/endpoint/types';
+import { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types';
import { useEndpointSelector, useAgentDetailsIngestUrl } from '../hooks';
import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
-import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants';
+import {
+ POLICY_STATUS_TO_HEALTH_COLOR,
+ POLICY_STATUS_TO_BADGE_COLOR,
+ HOST_STATUS_TO_HEALTH_COLOR,
+} from '../host_constants';
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
@@ -48,6 +52,7 @@ const LinkToExternalApp = styled.div`
margin-top: ${(props) => props.theme.eui.ruleMargins.marginMedium};
.linkToAppIcon {
margin-right: ${(props) => props.theme.eui.ruleMargins.marginXSmall};
+ vertical-align: top;
}
.linkToAppPopoutIcon {
margin-left: ${(props) => props.theme.eui.ruleMargins.marginXSmall};
@@ -57,7 +62,15 @@ const LinkToExternalApp = styled.div`
const openReassignFlyoutSearch = '?openReassignFlyout=true';
export const EndpointDetails = memo(
- ({ details, policyInfo }: { details: HostMetadata; policyInfo?: HostInfo['policy_info'] }) => {
+ ({
+ details,
+ policyInfo,
+ hostStatus,
+ }: {
+ details: HostMetadata;
+ policyInfo?: HostInfo['policy_info'];
+ hostStatus: HostStatus;
+ }) => {
const agentId = details.elastic.agent.id;
const {
url: agentDetailsUrl,
@@ -78,6 +91,25 @@ export const EndpointDetails = memo(
}),
description: details.host.os.full,
},
+ {
+ title: i18n.translate('xpack.securitySolution.endpoint.details.agentStatus', {
+ defaultMessage: 'Agent Status',
+ }),
+ description: (
+
+
+
+
+
+ ),
+ },
{
title: i18n.translate('xpack.securitySolution.endpoint.details.lastSeen', {
defaultMessage: 'Last Seen',
@@ -85,7 +117,7 @@ export const EndpointDetails = memo(
description: ,
},
];
- }, [details]);
+ }, [details, hostStatus]);
const [policyResponseUri, policyResponseRoutePath] = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -135,13 +167,15 @@ export const EndpointDetails = memo(
defaultMessage: 'Integration Policy',
}),
description: (
- <>
-
- {details.Endpoint.policy.applied.name}
-
+
+
+
+ {details.Endpoint.policy.applied.name}
+
+
{details.Endpoint.policy.applied.endpoint_policy_version && (
@@ -167,7 +201,7 @@ export const EndpointDetails = memo(
)}
- >
+
),
},
{
@@ -175,25 +209,26 @@ export const EndpointDetails = memo(
defaultMessage: 'Policy Response',
}),
description: (
-
- {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
-
-
-
-
-
-
+
+
+
+
),
},
];
@@ -248,7 +283,7 @@ export const EndpointDetails = memo(
onClick={handleReassignEndpointsClick}
data-test-subj="endpointDetailsLinkToIngest"
>
-
+
{
} = queryParams;
const details = useEndpointSelector(detailsData);
const policyInfo = useEndpointSelector(policyVersionInfo);
+ const hostStatus = useEndpointSelector(hostStatusInfo);
const loading = useEndpointSelector(detailsLoading);
const error = useEndpointSelector(detailsError);
const show = useEndpointSelector(showView);
@@ -83,7 +86,7 @@ export const EndpointDetailsFlyout = memo(() => {
onClose={handleFlyoutClose}
style={{ zIndex: 4001 }}
data-test-subj="endpointDetailsFlyout"
- size="s"
+ size="m"
>
{loading ? (
@@ -112,7 +115,11 @@ export const EndpointDetailsFlyout = memo(() => {
{show === 'details' && (
<>
-
+
>
)}
@@ -125,6 +132,14 @@ export const EndpointDetailsFlyout = memo(() => {
EndpointDetailsFlyout.displayName = 'EndpointDetailsFlyout';
+const PolicyResponseFlyout = styled.div`
+ .endpointDetailsPolicyResponseFlyoutBody {
+ .euiFlyoutBody__overflowContent {
+ padding-top: 0;
+ }
+ }
+`;
+
const PolicyResponseFlyoutPanel = memo<{
hostMeta: HostMetadata;
}>(({ hostMeta }) => {
@@ -165,12 +180,15 @@ const PolicyResponseFlyoutPanel = memo<{
}, [backToDetailsClickHandler, detailsUri]);
return (
- <>
+
-
+
)}
- >
+
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx
index 5c883e9affe1..b6c6be673da6 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx
@@ -29,7 +29,7 @@ import {
*/
const PolicyResponseConfigAccordion = styled(EuiAccordion)`
.euiAccordion__triggerWrapper {
- padding: ${(props) => props.theme.eui.paddingSizes.s};
+ padding: ${(props) => props.theme.eui.paddingSizes.xs};
}
&.euiAccordion-isOpen {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
index 4745cd9de249..71f6d78caea7 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
@@ -28,6 +28,15 @@ export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
unsupported: 'subdued',
});
+export const POLICY_STATUS_TO_BADGE_COLOR = Object.freeze<
+ { [key in keyof typeof HostPolicyResponseActionStatus]: string }
+>({
+ success: 'secondary',
+ warning: 'warning',
+ failure: 'danger',
+ unsupported: 'default',
+});
+
export const POLICY_STATUS_TO_TEXT = Object.freeze<
{ [key in keyof typeof HostPolicyResponseActionStatus]: string }
>({
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 99c313220e06..9925b35616c9 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
@@ -645,49 +645,41 @@ describe('when on the list page', () => {
it('should display Success overall policy status', async () => {
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Success');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="success"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Success');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(109\, 204\, 177\)\;/
+ );
});
it('should display Warning overall policy status', async () => {
mockEndpointListApi(createPolicyResponse(HostPolicyResponseActionStatus.warning));
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Warning');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="warning"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Warning');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(241\, 216\, 111\)\;/
+ );
});
it('should display Failed overall policy status', async () => {
mockEndpointListApi(createPolicyResponse(HostPolicyResponseActionStatus.failure));
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Failed');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="danger"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Failed');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(255\, 126\, 98\)\;/
+ );
});
it('should display Unknown overall policy status', async () => {
mockEndpointListApi(createPolicyResponse('' as HostPolicyResponseActionStatus));
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Unknown');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="subdued"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Unknown');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(211\, 218\, 230\)\;/
+ );
});
it('should include the link to reassignment in Ingest', async () => {
From 57a42b2edd237bc93ee8e41bed89ec9ae839792a Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Tue, 16 Feb 2021 19:29:36 -0800
Subject: [PATCH 16/23] skip flaky suite (#91592)
---
x-pack/test/accessibility/apps/dashboard_edit_panel.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/accessibility/apps/dashboard_edit_panel.ts b/x-pack/test/accessibility/apps/dashboard_edit_panel.ts
index 7df4c558515f..90b3c4ef4d49 100644
--- a/x-pack/test/accessibility/apps/dashboard_edit_panel.ts
+++ b/x-pack/test/accessibility/apps/dashboard_edit_panel.ts
@@ -18,7 +18,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['security', 'common']);
const toasts = getService('toasts');
- describe('Dashboard Edit Panel', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/91592
+ describe.skip('Dashboard Edit Panel', () => {
before(async () => {
await esArchiver.load('dashboard/drilldowns');
await esArchiver.loadIfNeeded('logstash_functional');
From 042f35226f65faf6fef4787f8de5bea1ea33310b Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Tue, 16 Feb 2021 19:31:47 -0800
Subject: [PATCH 17/23] skip flaky suite (#91450)
---
.../apps/ml/data_frame_analytics/classification_creation.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts
index 1d67408b7336..b13ee508b357 100644
--- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts
+++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts
@@ -12,7 +12,8 @@ export default function ({ getService }: FtrProviderContext) {
const ml = getService('ml');
const editedDescription = 'Edited description';
- describe('classification creation', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/91450
+ describe.skip('classification creation', function () {
before(async () => {
await esArchiver.loadIfNeeded('ml/bm_classification');
await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp');
From adc50dd267bd6c41170c905a7c1a5fc8613bc0e8 Mon Sep 17 00:00:00 2001
From: Andrew Goldstein
Date: Tue, 16 Feb 2021 20:51:44 -0700
Subject: [PATCH 18/23] [Security Solution] [Timeline] Endpoint row renderers
(2nd batch) (#91446)
## [Security Solution] [Timeline] Endpoint row renderers (2nd batch)
This PR implements the 2nd batch of Endpoint row renderers, **including the new Ransomware alerts**, by adding new row renderers for the following Endpoint alerts and events:
| event.dataset | event.type | event.category | event.action |
|--------------------------|------------|----------------|-----------------|
| endpoint.alerts | denied | file | creation |
| endpoint.alerts | allowed | file | creation |
| endpoint.alerts | denied | file | files-encrypted |
| endpoint.alerts | allowed | file | files-encrypted |
| endpoint.alerts | denied | file | modification |
| endpoint.alerts | allowed | file | modification |
| endpoint.alerts | denied | file | rename |
| endpoint.alerts | allowed | file | rename |
| endpoint.alerts | denied | process | execution |
| endpoint.alerts | allowed | process | execution |
| endpoint.events.file | change | file | modification |
| endpoint.events.file | change | file | overwrite |
| endpoint.events.file | change | file | rename |
| endpoint.events.registry | change | registry | modification |
| endpoint.events.library | start | library | load |
| endpoint.events.network | protocol | network | http_request |
| endpoint.events.process | start | process | exec |
| endpoint.events.process | start | process | fork |
Other updates:
- All row renders will now only display the `file.hash.sha256` and `process.hash.sha256`. (The `sha1` and `md5` hashes will no longer be displayed)
## Malware File Creation Prevented alert
Malware File Creation Prevented alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: denied and event.category: file and event.action: creation
```
### Sample Malware File Creation Prevented alert
![malware_file_creation_prevented](https://user-images.githubusercontent.com/4459398/107970084-e4762b00-6f6d-11eb-88c8-c9fd474d2de4.png)
`win2019-endpoint-1` was prevented from creating a malicious file `6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmp` in `C:\Users\sean\Downloads\6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmp` via `chrome.exe` (`8944`) `C:\Program Files\Google\Chrome\Application\chrome.exe` via parent process `explorer.exe` (`1008`) with result `success`
`7cc42618e580f233fee47e82312cc5c3476cb5de9219ba3f9eb7f99ac0659c30`
### Fields in a Malware File Creation Prevented alert
`user.name` \ `user.domain` @ `host.name` was prevented from creating a malicious file `file.name` in `file.path` via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`file.hash.sha256`
## Malware File Creation Detected alert
Malware File Creation Detected alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: allowed and event.category: file and event.action: creation
```
### Sample Malware File Creation Detected alert
![malware_file_creation_detected](https://user-images.githubusercontent.com/4459398/107970897-f7d5c600-6f6e-11eb-83a8-7324e34506c1.png)
`DESKTOP-1` was detected creating a malicious file `mimikatz_write.exe` in `C:\temp\mimikatz_write.exe` via `python.exe` (`4400`) `C:\Python27\python.exe` `main.py` `-a` `execute` `-p` `c:\temp` via parent process `pythonservice.exe` (`2936`) with result `success`
`263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0`
### Fields in a Malware File Creation Detected alert
`user.name` \ `user.domain` @ `host.name` was detected creating a malicious file `file.name` in `file.path` via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`file.hash.sha256`
## Ransomware Files Encrypted Prevented alert
Ransomware Files Encrypted Prevented alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: denied and event.category: file and event.action: files-encrypted
```
### Sample Ransomware Files Encrypted Prevented alert
![ransomware_files-encrypted_prevented](https://user-images.githubusercontent.com/4459398/107973327-56e90a00-6f72-11eb-8337-8bb15bd24ad2.png)
`DESKTOP-1` ransomware was prevented from encrypting files via `powershell.exe` (`6056`) `powershell.exe` `-file` `mock_ransomware_v3.ps1` via parent process `cmd.exe` (`10680`) with result `success`
`e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7`
### Fields in a Ransomware Files Encrypted Prevented alert
`user.name` \ `user.domain` @ `host.name` ransomware was prevented from encrypting files via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`process.hash.sha256`
## Ransomware Files Encrypted Detected alert
Ransomware Files Encrypted Detected alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: allowed and event.category: file and event.action: files-encrypted
```
### Sample Ransomware Files Encrypted Detected alert
![ransomware_files-encrypted_detected](https://user-images.githubusercontent.com/4459398/107976086-42a70c00-6f76-11eb-8977-74ad47191d71.png)
`DESKTOP-1` ransomware was detected encrypting files via `powershell.exe` (`4684`) `powershell.exe` `-file` `mock_ransomware_v3.ps1` via parent process `cmd.exe` (`8616`) with result `success`
`e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7`
### Fields in a Ransomware Files Encrypted Detected alert
`user.name` \ `user.domain` @ `host.name` ransomware was detected encrypting files via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`process.hash.sha256`
## Malware File Modification Prevented alert
Malware File Modification Prevented alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: denied and event.category: file and event.action: modification
```
### Sample Malware File Modification Prevented alert
![malware_file_modification_prevented](https://user-images.githubusercontent.com/4459398/107979686-3a51cf80-6f7c-11eb-92ff-f164536f6c70.png)
`win2019-endpoint-1` was prevented from modifying a malicious file `mimikatz - Copy.exe` in `C:\Users\sean\Downloads\mimikatz_trunk (1)\x64\mimikatz - Copy.exe` via `explorer.exe` (`1008`) `C:\Windows\Explorer.EXE` via parent process `C:\Windows\System32\userinit.exe` (`356`) with result `success`
`31eb1de7e840a342fd468e558e5ab627bcb4c542a8fe01aec4d5ba01d539a0fc`
### Fields in a Malware File Modification Prevented alert
`user.name` \ `user.domain` @ `host.name` was prevented from modifying a malicious file `file.name` in `file.path` via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`file.hash.sha256`
## Malware File Modification Detected alert
Malware File Modification Detected alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: allowed and event.category: file and event.action: modification
```
### Sample Malware File Modification Detected alert
![malware_file_modification_detected](https://user-images.githubusercontent.com/4459398/107980920-55bdda00-6f7e-11eb-9d08-2aa02253a958.png)
`mac-1.local` was detected modifying a malicious file `aircrack` in `/private/var/root/write_malware/modules/write_malware/aircrack` via `Python` (`5995`) `/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python` `main.py` `-a` `modify` via parent process `Python` (`97`) with result `success`
`f0954d9673878b2223b00b7ec770c7b438d876a9bb44ec78457e5c618f31f52b`
### Fields in a Malware File Modification Detected alert
`user.name` \ `user.domain` @ `host.name` was detected modifying a malicious file `file.name` in `file.path` via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`file.hash.sha256`
## Malware File Rename Prevented alert
Malware File Rename Prevented alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: denied and event.category: file and event.action: rename
```
### Sample Malware File Rename Prevented alert
![malware_file_rename_prevented](https://user-images.githubusercontent.com/4459398/107981991-6e2ef400-6f80-11eb-8d48-3c9aa48c5d72.png)
`win2019-endpoint-1` was prevented from renaming a malicious file `23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe` in `C:\Users\sean\Downloads\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe` via `explorer.exe` (`1008`) `C:\Windows\Explorer.EXE` via parent process `C:\Windows\System32\userinit.exe` (`356`) with result `success`
`23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97`
### Fields in a Malware File Rename Prevented alert
`user.name` \ `user.domain` @ `host.name` was prevented from renaming a malicious file `file.name` in `file.path` via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`file.hash.sha256`
## Malware File Rename Detected alert
Malware File Rename Detected alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: allowed and event.category: file and event.action: rename
```
### Sample Malware File Rename Detected alert
![malware_file_rename_detected](https://user-images.githubusercontent.com/4459398/107983209-ab948100-6f82-11eb-893f-359fa0bd3a19.png)
`win2019-endpoint-1` was detected renaming a malicious file `23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe` in `C:\Users\sean\Downloads\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe` via `explorer.exe` (`1008`) `C:\Windows\Explorer.EXE` via parent process `C:\Windows\System32\userinit.exe` (`356`) with result `success`
`23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97`
### Fields in a Malware File Rename Detected alert
`user.name` \ `user.domain` @ `host.name` was detected renaming a malicious file `file.name` in `file.path` via `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`file.hash.sha256`
## Malware Process Execution Prevented alert
Malware Process Execution Prevented alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: denied and event.category: process and event.action: execution
```
### Sample Malware Process Execution Prevented alert
![malware_process_execution_prevented](https://user-images.githubusercontent.com/4459398/107986073-8b67c080-6f88-11eb-89a5-95434639631e.png)
`win2019-endpoint-1` was prevented from executing a malicious process `C:\Users\sean\Downloads\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe` (`6920`) `C:\Users\sean\Downloads\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe` via parent process `explorer.exe` (`1008`) with result `success`
`3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb`
### Fields in a Sample Malware Process Execution Prevented alert
`host.name` was prevented from executing a malicious process `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`process.hash.sha256`
## Malware Process Execution Detected alert
Malware Process Execution Detected alerts with the following `event.dataset`, `event.type`, `event.category`, and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.alerts and event.type: allowed and event.category: process and event.action: execution
```
### Sample Malware Process Execution Detected alert
![malware_process_execution_detected](https://user-images.githubusercontent.com/4459398/107986475-590a9300-6f89-11eb-9dbc-373efe005c85.png)
`DESKTOP-1` was detected executing a malicious process `mimikatz_write.exe` (`8668`) `c:\temp\mimikatz_write.exe` via parent process `python.exe` (`4400`) with result `success`
`263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0`
### Fields in a Sample Malware Process Execution Detected alert
`host.name` was detected executing a malicious process `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`) with result `event.outcome`
`process.hash.sha256`
## File (FIM) Modification events
Endpoint File (FIM) Modification events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.file and event.action: modification
```
### Sample rendered File (FIM) Modification event
Each field with `this formatting` is draggable (to pivot a search) in the row-rendered event:
![file_modification](https://user-images.githubusercontent.com/4459398/106680191-641df600-657b-11eb-974e-e2afbc7698a3.png)
`admin` @ `test-Mac.local` modified a file `.dat.nosync01a5.6hoWv1` in `/Users/admin/Library/Application Support/CrashReporter/.dat.nosync01a5.6hoWv1` via `diagnostics_agent` `(421)`
### Fields in a File (FIM) Modification event
`user.name` \ `user.domain` @ `host.name` modified a file `file.name` in `file.path` via `process.name` `(process.pid)`
## File (FIM) Overwrite events
Endpoint File (FIM) Overwrite events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.file and event.action: overwrite
```
### Sample rendered File (FIM) Overwrite event
![file_overwrite](https://user-images.githubusercontent.com/4459398/106675692-c9b9b480-6572-11eb-9f78-fb0b4bf0b05d.png)
`LOCAL SERVICE` \ `NT AUTHORITY` @ `windows-endpoint-1` overwrote a file `lastalive0.dat` in `C:\Windows\ServiceState\EventLog\Data\lastalive0.dat` via `svchost.exe` `(1228)`
### Fields in a File (FIM) Overwrite event
`user.name` \ `user.domain` @ `host.name` overwrote a file `file.name` in `file.path` via `process.name` `(process.pid)`
## File (FIM) Rename events
Endpoint File (FIM) Rename events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.file and event.action: rename
```
### Sample rendered File (FIM) Rename event
![file_rename](https://user-images.githubusercontent.com/4459398/106534633-c4e0fc00-64b1-11eb-8213-494b51e8cdf9.png)
`LOCAL SERVICE` \ `NT AUTHORITY` @ `windows-endpoint-1` renamed a file `SRU.log` in `C:\Windows\System32\sru\SRU.log` from its original path `C:\Windows\System32\sru\SRUtmp.log` via `svchost.exe` `(1204)`
### Fields in a File (FIM) Rename event
`user.name` \ `user.domain` @ `host.name` renamed a file `file.name` in `file.path` from its original path `file.Ext.original.path` via `process.name` `(process.pid)`
## Registry Modification events
Registry Modification events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.registry and event.action: modification
```
### Sample Registry Modification event
![registry_modification](https://user-images.githubusercontent.com/4459398/107091637-56f14900-67bf-11eb-9c8b-7f748e848bac.png)
`SYSTEM` \ `NT AUTHORITY` @ `win2019-endpoint-1` modified registry key `SOFTWARE\WOW6432Node\Google\Update\ClientState\{430FD4D0-B729-4F61-AA34-91526481799D}\CurrentState` with new value `HKLM\SOFTWARE\WOW6432Node\Google\Update\ClientState\{430FD4D0-B729-4F61-AA34-91526481799D}\CurrentState\StateValue` via `GoogleUpdate.exe` `(7408)`
### Fields in a Registry Modification event
`user.name` \ `user.domain` @ `host.name` modified registry key `registry.key` with new value `registry.path` via `process.name` `(process.pid)`
## Library Load events
Library Load events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.library and event.action: load
```
### Sample Library Load event
![library_load](https://user-images.githubusercontent.com/4459398/107261734-ea638d80-69fc-11eb-8b2c-0a4f453b3f95.png)
`SYSTEM` \ `NT AUTHORITY` @ `win2019-endpoint-1` loaded library `bcrypt.dll` in `C:\Windows\System32\bcrypt.dll` via `sshd.exe` `(9644)`
`e70f5d8f87aab14e3160227d38387889befbe37fa4f8f5adc59eff52804b35fd`
`2c4ba5c1482987d50a182bad915f52cd6611ee63`
`00439016776de367bad087d739a03797`
### Fields in a Library Load event
`user.name` \ `user.domain` @ `host.name` loaded library `file.name` in `file.path` via `process.name` `(process.pid)`
`file.hash.sha256`
`file.hash.sha1`
`file.hash.md5`
## HTTP Request events
HTTP Request events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.network and event.action: http_request
```
### Sample HTTP Request event
![http_request](https://user-images.githubusercontent.com/4459398/107546591-c5505580-6b89-11eb-8081-fe492312cc12.png)
Network HTTP Request events, like the one in the screenshot above, are also rendered by the Netflow row renderer, which displays information that includes the directionality of the connection, protocol, and source / destination details.
`NETWORK SERVICE` \ `NT AUTHORITY` @ `win2019-endpoint-1` made a http request via `svchost.exe` `(2232)`
### Fields in a HTTP Request event
`user.name` \ `user.domain` @ `host.name` made a http request via `process.name` `(process.pid)`
## Process Exec events
Endpoint Process Exec events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.process and event.action: exec
```
### Sample rendered Process Exec event
![process_exec](https://user-images.githubusercontent.com/4459398/107989163-de447680-6f8e-11eb-88e9-d8c72d77bc2d.png)
`admin` @ `test-mac.local` executed process `mdworker_shared` (`4454`) `/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared` `-s` `mdworker` `-c` `MDSImporterWorker` `-m` `com.apple.mdworker.shared` via parent process `launchd` (`1`)
`4bc018ac461706496302d1faab0a8bb39aad974eb432758665103165f3a2dd2b`
### Fields in a Process Exec event
The following fields will be used to render a Process Exec event:
`user.name` @ `host.name` executed process `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`)
`process.hash.sha256`
## Process Fork events
Endpoint Process Fork events with the following `event.dataset` and `event.action` will be rendered in Timeline via row renderers:
```
event.dataset: endpoint.events.process and event.action: fork
```
### Sample rendered Process Fork event
![process_fork](https://user-images.githubusercontent.com/4459398/107990678-29ac5400-6f92-11eb-893f-59bafa79cd53.png)
`admin` @ `test-mac.local` forked process `zoom.us` (`4042`) `/Applications/zoom.us.app/Contents/MacOS/zoom.us` via parent process `zoom.us` (`3961`)
`cbf3d059cc9f9c0adff5ef15bf331b95ab381837fa0adecd965a41b5846f4bd4`
### Fields in a Process Fork event
The following fields will be used to render a Process Exec event:
`user.name` @ `host.name` forked process `process.name` (`process.pid`) `process.args` via parent process `process.parent.name` (`process.parent.pid`)
`process.hash.sha256`
---
.../common/ecs/file/index.ts | 10 +-
.../security_solution/common/ecs/index.ts | 2 +
.../common/ecs/process/index.ts | 1 +
.../common/ecs/registry/index.ts | 13 +
.../common/types/timeline/index.ts | 3 +
.../common/mock/mock_endgame_ecs_data.ts | 879 ++++++++++++++++++
.../public/common/mock/mock_timeline_data.ts | 183 ++++
.../public/graphql/introspection.json | 13 +
.../security_solution/public/graphql/types.ts | 3 +
.../row_renderers_browser/catalog/index.tsx | 24 +
.../catalog/translations.ts | 34 +
.../row_renderers_browser/examples/alerts.tsx | 34 +
.../row_renderers_browser/examples/index.tsx | 3 +
.../examples/library.tsx | 31 +
.../examples/registry.tsx | 31 +
.../body/renderers/file_draggable.test.tsx | 10 +-
.../body/renderers/file_draggable.tsx | 28 +-
.../body/renderers/file_hash.test.tsx | 60 ++
.../timeline/body/renderers/file_hash.tsx | 46 +
.../timeline/body/renderers/helpers.test.tsx | 14 +-
.../timeline/body/renderers/helpers.tsx | 26 +-
.../body/renderers/process_hash.test.tsx | 70 +-
.../timeline/body/renderers/process_hash.tsx | 70 +-
.../registry/registry_event_details.test.tsx | 48 +
.../registry/registry_event_details.tsx | 58 ++
.../registry_event_details_line.test.tsx | 135 +++
.../registry/registry_event_details_line.tsx | 133 +++
.../body/renderers/registry/translations.ts | 22 +
.../generic_file_details.test.tsx.snap | 2 +
.../system/generic_file_details.test.tsx | 244 ++---
.../renderers/system/generic_file_details.tsx | 68 +-
.../system/generic_row_renderer.test.tsx | 520 ++++++++++-
.../renderers/system/generic_row_renderer.tsx | 235 +++++
.../body/renderers/system/translations.ts | 126 +++
.../timeline/body/renderers/translations.ts | 7 +
.../server/graphql/timeline/schema.gql.ts | 3 +
.../security_solution/server/graphql/types.ts | 3 +
.../timeline/factory/events/all/constants.ts | 3 +
38 files changed, 2905 insertions(+), 290 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/ecs/registry/index.ts
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.ts
diff --git a/x-pack/plugins/security_solution/common/ecs/file/index.ts b/x-pack/plugins/security_solution/common/ecs/file/index.ts
index 06abc7fd8754..5e409b1095cf 100644
--- a/x-pack/plugins/security_solution/common/ecs/file/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/file/index.ts
@@ -5,14 +5,22 @@
* 2.0.
*/
+interface Original {
+ name?: string[];
+ path?: string[];
+}
+
export interface CodeSignature {
subject_name: string[];
trusted: string[];
}
export interface Ext {
- code_signature: CodeSignature[] | CodeSignature;
+ code_signature?: CodeSignature[] | CodeSignature;
+ original?: Original;
}
export interface Hash {
+ md5?: string[];
+ sha1?: string[];
sha256: string[];
}
diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts
index e3bcd11097cf..fcd7f7aa3364 100644
--- a/x-pack/plugins/security_solution/common/ecs/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/index.ts
@@ -15,6 +15,7 @@ import { FileEcs } from './file';
import { GeoEcs } from './geo';
import { HostEcs } from './host';
import { NetworkEcs } from './network';
+import { RegistryEcs } from './registry';
import { RuleEcs } from './rule';
import { SignalEcs } from './signal';
import { SourceEcs } from './source';
@@ -40,6 +41,7 @@ export interface Ecs {
geo?: GeoEcs;
host?: HostEcs;
network?: NetworkEcs;
+ registry?: RegistryEcs;
rule?: RuleEcs;
signal?: SignalEcs;
source?: SourceEcs;
diff --git a/x-pack/plugins/security_solution/common/ecs/process/index.ts b/x-pack/plugins/security_solution/common/ecs/process/index.ts
index 3a8ccc309aec..931adf2dd70b 100644
--- a/x-pack/plugins/security_solution/common/ecs/process/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/process/index.ts
@@ -28,6 +28,7 @@ export interface ProcessHashData {
export interface ProcessParentData {
name?: string[];
+ pid?: number[];
}
export interface Thread {
diff --git a/x-pack/plugins/security_solution/common/ecs/registry/index.ts b/x-pack/plugins/security_solution/common/ecs/registry/index.ts
new file mode 100644
index 000000000000..c756fb139199
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/ecs/registry/index.ts
@@ -0,0 +1,13 @@
+/*
+ * 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 interface RegistryEcs {
+ hive?: string[];
+ key?: string[];
+ path?: string[];
+ value?: string[];
+}
diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts
index 58e3b9824d8f..c954bc185908 100644
--- a/x-pack/plugins/security_solution/common/types/timeline/index.ts
+++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts
@@ -180,10 +180,13 @@ export type TimelineStatusLiteralWithNull = runtimeTypes.TypeOf<
>;
export enum RowRendererId {
+ alerts = 'alerts',
auditd = 'auditd',
auditd_file = 'auditd_file',
+ library = 'library',
netflow = 'netflow',
plain = 'plain',
+ registry = 'registry',
suricata = 'suricata',
system = 'system',
system_dns = 'system_dns',
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts b/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts
index 1082b5f9474e..3400844e671b 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts
@@ -343,6 +343,885 @@ export const mockEndpointFileDeletionEvent: Ecs = {
_id: 'mnXHO3cBPmkOXwyNlyv_',
};
+export const mockEndpointFileCreationMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['efca0a88adab8b92e4a333b56db5fbaa'],
+ sha256: ['8c177f6129dddbd36cae196ef9d9eb71f50cee44640068f24830e83d6a9dd1d0'],
+ sha1: ['e55e587058112c60d015994424f70a7a8e78afb1'],
+ },
+ parent: {
+ name: ['explorer.exe'],
+ pid: [1008],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTg5NDQtMTMyNDkwNjg0NzIuNzM4OTY4NTAw',
+ ],
+ executable: ['C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'],
+ name: ['chrome.exe'],
+ pid: [8944],
+ args: ['C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['creation'],
+ id: ['LsuMZVr+sdhvehVM++++Ic8J'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'creation', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ file: {
+ path: ['C:\\Users\\sean\\Downloads\\6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmp'],
+ owner: ['sean'],
+ hash: {
+ md5: ['c1f8d2b73b4c2488f95e7305f0421bdf'],
+ sha256: ['7cc42618e580f233fee47e82312cc5c3476cb5de9219ba3f9eb7f99ac0659c30'],
+ sha1: ['542b2796e9f57a92504f852b6698148bba9ff289'],
+ },
+ name: ['6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmp'],
+ extension: ['tmp'],
+ size: [196608],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-05T16:48:19.923Z',
+ message: ['Malware Prevention Alert'],
+ _id: 'dGZQmXUB-o9SpDeMqvln',
+};
+
+export const mockEndpointFileCreationMalwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['16d6a536bb2115dcbd16011e6991a9fd'],
+ sha256: ['6637eca55fedbabc510168f0c4696d41971c89e5d1fb440f2f9391e6ab0e8f54'],
+ sha1: ['05cc6d37603ca9076f3baf4dc421500c5cf69e4c'],
+ },
+ entity_id: [
+ 'Yjk3ZWYwODktNzYyZi00ZTljLTg3OWMtNmQ5MDM1ZjBmYTUzLTQ0MDAtMTMyNDM2MTgwMzIuMjA0MzMxMDA=',
+ ],
+ executable: ['C:\\Python27\\python.exe'],
+ parent: {
+ name: ['pythonservice.exe'],
+ pid: [2936],
+ },
+ name: ['python.exe'],
+ args: ['C:\\Python27\\python.exe', 'main.py', '-a,execute', '-p', 'c:\\temp'],
+ pid: [4400],
+ },
+ host: {
+ os: {
+ full: ['Windows 10 Pro 1903 (10.0.18362.1016)'],
+ name: ['Windows'],
+ version: ['1903 (10.0.18362.1016)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1903 (10.0.18362.1016)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ ip: ['10.1.2.3'],
+ id: ['c85e6c40-d4a1-db21-7458-2565a6b857f3'],
+ architecture: ['x86_64'],
+ name: ['DESKTOP-1'],
+ },
+ file: {
+ path: ['C:\\temp\\mimikatz_write.exe'],
+ owner: ['Administrators'],
+ hash: {
+ md5: ['cc52aebdf82048364119f117f52dbba0'],
+ sha256: ['263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'],
+ sha1: ['c929f6ff2d6d1085ee69625cd8efb92101a0e906'],
+ },
+ name: ['mimikatz_write.exe'],
+ extension: ['exe'],
+ size: [1265456],
+ },
+ event: {
+ id: ['Lp/73XQ38EF48a6i+++++5Ds'],
+ module: ['endpoint'],
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['creation'],
+ kind: ['signal'],
+ type: ['info', 'creation', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Malware Detection Alert'],
+ timestamp: '2020-09-03T15:51:50.209Z',
+ _id: '51e04f7dad15fe394a3f7ed582ad4528c8ce62948e315571fc3388befd9aa0e6',
+};
+
+export const mockEndpointFilesEncryptedRansomwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['85bc517e37fe24f909e4378a46a4b567'],
+ sha256: ['e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'],
+ sha1: ['10a3671c0fbc2bce14fc94891e87e2f4ba07e0df'],
+ },
+ parent: {
+ name: ['cmd.exe'],
+ pid: [10680],
+ },
+ entity_id: [
+ 'OTI1MTRiMTYtMWJkNi05NzljLWE2MDMtOTgwY2ZkNzQ4M2IwLTYwNTYtMTMyNTczODEzMzYuNzIxNTIxODAw',
+ ],
+ name: ['powershell.exe'],
+ pid: [6056],
+ args: ['powershell.exe', '-file', 'mock_ransomware_v3.ps1'],
+ },
+ host: {
+ os: {
+ full: ['Windows 7 Enterprise Service Pack 1 (6.1.7601)'],
+ name: ['Windows'],
+ version: ['Service Pack 1 (6.1.7601)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['Service Pack 1 (6.1.7601)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['c6bb2832-d58c-4c57-9d1f-3b102ea74d46'],
+ name: ['DESKTOP-1'],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'process', 'file'],
+ outcome: ['success'],
+ code: ['ransomware'],
+ action: ['files-encrypted'],
+ id: ['M0A1DXHIg6/kaeku+++++1Gv'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'start', 'change', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2021-02-09T21:55:48.941Z',
+ message: ['Ransomware Prevention Alert'],
+ _id: 'BfvLiHcBVXUk10dUK1Pk',
+};
+
+export const mockEndpointFilesEncryptedRansomwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['85bc517e37fe24f909e4378a46a4b567'],
+ sha256: ['e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'],
+ sha1: ['10a3671c0fbc2bce14fc94891e87e2f4ba07e0df'],
+ },
+ parent: {
+ name: ['cmd.exe'],
+ pid: [8616],
+ },
+ entity_id: [
+ 'MDAwODRkOTAtZDRhOC1kOTZhLWVmYWItZDU1ZWFhNDY1N2M2LTQ2ODQtMTMyNTc0NjE2MzEuNDM3NDUzMDA=',
+ ],
+ executable: ['C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'],
+ name: ['powershell.exe'],
+ pid: [4684],
+ args: ['powershell.exe', '-file', 'mock_ransomware_v3.ps1'],
+ },
+ host: {
+ os: {
+ full: ['Windows 7 Enterprise Service Pack 1 (6.1.7601)'],
+ name: ['Windows'],
+ version: ['Service Pack 1 (6.1.7601)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['Service Pack 1 (6.1.7601)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['c6bb2832-d58c-4c57-9d1f-3b102ea74d46'],
+ name: ['DESKTOP-1'],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'process', 'file'],
+ code: ['ransomware'],
+ action: ['files-encrypted'],
+ id: ['M0ExfR/BggxoHQ1e+++++1Zv'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'start', 'change', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2021-02-10T20:14:03.927Z',
+ message: ['Ransomware Detection Alert'],
+ _id: 'enyUjXcBxUk8qlINZEJr',
+};
+
+export const mockEndpointFileModificationMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['47ea9e07b7dbfbeba368bd95a3a2d25b'],
+ sha256: ['f45557c0b57dec4c000d8cb7d7068c8a4dccf392de740501b1046994460d77ea'],
+ sha1: ['da714f84a7bbaee2be9f1ca0262aca649657cf3e'],
+ },
+ parent: {
+ name: ['C:\\Windows\\System32\\userinit.exe'],
+ pid: [356],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTEwMDgtMTMyNDc1Njk3ODUuODA0NzQyMDA=',
+ ],
+ executable: ['C:\\Windows\\explorer.exe'],
+ name: ['explorer.exe'],
+ pid: [1008],
+ args: ['C:\\Windows\\Explorer.EXE'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ file: {
+ path: ['C:\\Users\\sean\\Downloads\\mimikatz_trunk (1)\\x64\\mimikatz - Copy.exe'],
+ owner: ['sean'],
+ hash: {
+ md5: ['a3cb3b02a683275f7e0a0f8a9a5c9e07'],
+ sha256: ['31eb1de7e840a342fd468e558e5ab627bcb4c542a8fe01aec4d5ba01d539a0fc'],
+ sha1: ['d241df7b9d2ec0b8194751cd5ce153e27cc40fa4'],
+ },
+ name: ['mimikatz - Copy.exe'],
+ extension: ['exe'],
+ size: [1309448],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['modification'],
+ id: ['LsuMZVr+sdhvehVM++++GvWi'],
+ kind: ['alert'],
+ created: ['2020-11-04T22:40:51.724Z'],
+ module: ['endpoint'],
+ type: ['info', 'change', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-04T22:40:51.724Z',
+ message: ['Malware Prevention Alert'],
+ _id: 'j0RtlXUB-o9SpDeMLdEE',
+};
+
+export const mockEndpointFileModificationMalwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['c93876879542fc4710ab1d3b52382d95'],
+ sha256: ['0ead4d0131ca81aa4820efdcd3c6053eab23179a46c5480c94d7c11eb8451d62'],
+ sha1: ['def88472b5d92022b6182bfe031c043ddfc5ff0f'],
+ },
+ parent: {
+ name: ['Python'],
+ pid: [97],
+ },
+ entity_id: [
+ 'ZGQ0NDBhNjMtZjcyNy00NGY4LWI5M2UtNzQzZWEzMDBiYTk2LTU5OTUtMTMyNDM2MTg1MzkuOTUyNjkwMDA=',
+ ],
+ executable: [
+ '/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python',
+ ],
+ name: ['Python'],
+ args: [
+ '/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python',
+ 'main.py',
+ '-a',
+ 'modify',
+ ],
+ pid: [5995],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.1'],
+ name: ['macOS'],
+ version: ['10.14.1'],
+ platform: ['macos'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.2.0: Fri Oct 5 19:40:55 PDT 2018; root:xnu-4903.221.2~1/RELEASE_X86_64',
+ ],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ ip: ['10.1.2.3'],
+ id: ['7d59b1a5-afa1-6531-07ea-691602558230'],
+ architecture: ['x86_64'],
+ name: ['mac-1.local'],
+ },
+ file: {
+ mtime: ['2020-09-03T14:55:42.842Z'],
+ path: ['/private/var/root/write_malware/modules/write_malware/aircrack'],
+ owner: ['root'],
+ hash: {
+ md5: ['59328cdab10fb4f25a026eb362440422'],
+ sha256: ['f0954d9673878b2223b00b7ec770c7b438d876a9bb44ec78457e5c618f31f52b'],
+ sha1: ['f10b043652da8c444e04aede3a9ce4a10ef9028e'],
+ },
+ name: ['aircrack'],
+ size: [240916],
+ },
+ event: {
+ id: ['Lp21aufnU2nkG+fO++++++7h'],
+ module: ['endpoint'],
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['modification'],
+ type: ['info', 'change', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Malware Detection Alert'],
+ timestamp: '2020-09-03T15:01:19.445Z',
+ _id: '04d309c7e4cf7c4e54b7e3d93c38399e51797eed2484078487f4d6661f94da2c',
+};
+
+export const mockEndpointFileRenameMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['47ea9e07b7dbfbeba368bd95a3a2d25b'],
+ sha256: ['f45557c0b57dec4c000d8cb7d7068c8a4dccf392de740501b1046994460d77ea'],
+ sha1: ['da714f84a7bbaee2be9f1ca0262aca649657cf3e'],
+ },
+ parent: {
+ name: ['C:\\Windows\\System32\\userinit.exe'],
+ pid: [356],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTEwMDgtMTMyNDc1Njk3ODUuODA0NzQyMDA=',
+ ],
+ executable: ['C:\\Windows\\explorer.exe'],
+ name: ['explorer.exe'],
+ pid: [1008],
+ args: ['C:\\Windows\\Explorer.EXE'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ file: {
+ mtime: ['2020-11-04T21:48:47.559Z'],
+ path: [
+ 'C:\\Users\\sean\\Downloads\\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe',
+ ],
+ owner: ['sean'],
+ hash: {
+ md5: ['9798063a1fe056ef2f1d6f5217e7b82b'],
+ sha256: ['23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97'],
+ sha1: ['ced72fe7fc3835385faea41c657efab7b9f883cd'],
+ },
+ name: ['23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe'],
+ extension: ['exe'],
+ size: [242010],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['rename'],
+ id: ['LsuMZVr+sdhvehVM++++GppA'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'change', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-04T21:48:57.847Z',
+ message: ['Malware Prevention Alert'],
+ _id: 'qtA9lXUBn9bLIbfPj-Tu',
+};
+
+export const mockEndpointFileRenameMalwareDetectionAlert: Ecs = {
+ ...mockEndpointFileRenameMalwarePreventionAlert,
+ event: {
+ ...mockEndpointFileRenameMalwarePreventionAlert.event,
+ type: ['info', 'change', 'allowed'],
+ },
+ message: ['Malware Detection Alert'],
+ _id: 'CD7B6A22-809C-4502-BB94-BC38901EC942',
+};
+
+// NOTE: see `mock_timeline_data.ts` for the mockEndpointProcessExecutionMalwarePreventionAlert
+
+export const mockEndpointProcessExecutionMalwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['cc52aebdf82048364119f117f52dbba0'],
+ sha256: ['263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'],
+ sha1: ['c929f6ff2d6d1085ee69625cd8efb92101a0e906'],
+ },
+ entity_id: [
+ 'Yjk3ZWYwODktNzYyZi00ZTljLTg3OWMtNmQ5MDM1ZjBmYTUzLTg2NjgtMTMyNDM2MTgwMzQuODU3Njg5MDA=',
+ ],
+ executable: ['C:\\temp\\mimikatz_write.exe'],
+ parent: {
+ name: ['python.exe'],
+ },
+ name: ['mimikatz_write.exe'],
+ args: ['c:\\temp\\mimikatz_write.exe'],
+ pid: [8668],
+ },
+ host: {
+ os: {
+ full: ['Windows 10 Pro 1903 (10.0.18362.1016)'],
+ name: ['Windows'],
+ version: ['1903 (10.0.18362.1016)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1903 (10.0.18362.1016)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ ip: ['10.1.2.3'],
+ id: ['c85e6c40-d4a1-db21-7458-2565a6b857f3'],
+ architecture: ['x86_64'],
+ name: ['DESKTOP-1'],
+ },
+ file: {
+ mtime: ['2020-09-03T14:47:14.647Z'],
+ path: ['C:\\temp\\mimikatz_write.exe'],
+ owner: ['Administrators'],
+ hash: {
+ md5: ['cc52aebdf82048364119f117f52dbba0'],
+ sha256: ['263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'],
+ sha1: ['c929f6ff2d6d1085ee69625cd8efb92101a0e906'],
+ },
+ name: ['mimikatz_write.exe'],
+ extension: ['exe'],
+ size: [1265456],
+ },
+ event: {
+ id: ['Lp/73XQ38EF48a6i+++++5Do'],
+ module: ['endpoint'],
+ category: ['malware', 'intrusion_detection', 'process'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['execution'],
+ kind: ['signal'],
+ type: ['info', 'start', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Malware Detection Alert'],
+ timestamp: '2020-09-03T15:51:50.209Z',
+ _id: '96b3db3079891faaf155f1ada645b7364a03018c65677ce002f18038e7ce1c47',
+};
+
+export const mockEndpointFileModificationEvent: Ecs = {
+ file: {
+ path: ['/Users/admin/Library/Application Support/CrashReporter/.dat.nosync01a5.6hoWv1'],
+ name: ['.dat.nosync01a5.6hoWv1'],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.6'],
+ name: ['macOS'],
+ version: ['10.14.6'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.7.0: Mon Aug 31 20:53:32 PDT 2020; root:xnu-4903.278.44~1/RELEASE_X86_64',
+ ],
+ platform: ['macos'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['test-Mac.local'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['fce6b9f1-5c09-d8f0-3d99-9ecb30f995df'],
+ },
+ event: {
+ category: ['file'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['modification'],
+ type: ['change'],
+ dataset: ['endpoint.events.file'],
+ },
+ process: {
+ name: ['diagnostics_agent'],
+ pid: [421],
+ entity_id: ['OTA1ZDkzMTctMjIxOS00ZjQ1LTg4NTMtYzNiYzk1NGU1ZGU4LTQyMS0xMzI0OTEwNTIwOC4w'],
+ executable: ['/System/Library/CoreServices/diagnostics_agent'],
+ },
+ user: {
+ id: ['501'],
+ name: ['admin'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint file event'],
+ timestamp: '2021-02-02T18:56:12.871Z',
+ _id: 'ulkWZHcBGrBB52F2vFf_',
+};
+
+export const mockEndpointFileOverwriteEvent: Ecs = {
+ file: {
+ path: ['C:\\Windows\\ServiceState\\EventLog\\Data\\lastalive0.dat'],
+ extension: ['dat'],
+ name: ['lastalive0.dat'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['windows-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['ce6fa3c3-fda1-4984-9bce-f6d602a5bd1a'],
+ },
+ event: {
+ category: ['file'],
+ kind: ['event'],
+ created: ['2021-02-02T21:40:14.400Z'],
+ module: ['endpoint'],
+ action: ['overwrite'],
+ type: ['change'],
+ id: ['Lzty2lsJxA05IUWg++++Icrn'],
+ dataset: ['endpoint.events.file'],
+ },
+ process: {
+ name: ['svchost.exe'],
+ pid: [1228],
+ entity_id: [
+ 'YjUwNDNiMTMtYTdjNi0xZGFlLTEyZWQtODQ1ZDlhNTRhZmQyLTEyMjgtMTMyNTQ5ODc1MDcuODc1MTIxNjAw',
+ ],
+ executable: ['C:\\Windows\\System32\\svchost.exe'],
+ },
+ user: {
+ id: ['S-1-5-19'],
+ name: ['LOCAL SERVICE'],
+ domain: ['NT AUTHORITY'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint file event'],
+ timestamp: '2021-02-02T21:40:14.400Z',
+ _id: 'LBmxZHcBtgfIO53sCImw',
+};
+
+export const mockEndpointFileRenameEvent: Ecs = {
+ file: {
+ path: ['C:\\Windows\\System32\\sru\\SRU.log'],
+ Ext: {
+ original: {
+ path: ['C:\\Windows\\System32\\sru\\SRUtmp.log'],
+ name: ['SRUtmp.log'],
+ },
+ },
+ extension: ['log'],
+ name: ['SRU.log'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['windows-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['ce6fa3c3-fda1-4984-9bce-f6d602a5bd1a'],
+ },
+ event: {
+ category: ['file'],
+ kind: ['event'],
+ created: ['2021-02-01T16:43:00.373Z'],
+ module: ['endpoint'],
+ action: ['rename'],
+ type: ['change'],
+ id: ['Lzty2lsJxA05IUWg++++I3jv'],
+ dataset: ['endpoint.events.file'],
+ },
+ process: {
+ name: ['svchost.exe'],
+ pid: [1204],
+ entity_id: [
+ 'YjUwNDNiMTMtYTdjNi0xZGFlLTEyZWQtODQ1ZDlhNTRhZmQyLTEyMDQtMTMyNTQ5ODc2NzQuNzQ5MjUzNzAw',
+ ],
+ executable: ['C:\\Windows\\System32\\svchost.exe'],
+ },
+ user: {
+ id: ['S-1-5-19'],
+ name: ['LOCAL SERVICE'],
+ domain: ['NT AUTHORITY'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint file event'],
+ timestamp: '2021-02-01T16:43:00.373Z',
+ _id: 'OlJ8XncBGrBB52F2Oga7',
+};
+
+// NOTE: see `mock_timeline_data.ts` for the mockEndpointRegistryModificationEvent
+
+// NOTE: see `mock_timeline_data.ts` for the mockEndpointLibraryLoadEvent
+
+export const mockEndpointNetworkHttpRequestEvent: Ecs = {
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['win2019-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ },
+ event: {
+ category: ['network'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['http_request'],
+ type: ['protocol'],
+ id: ['LzzWB9jjGmCwGMvk++++FD+p'],
+ dataset: ['endpoint.events.network'],
+ },
+ process: {
+ name: ['svchost.exe'],
+ pid: [2232],
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTIyMzItMTMyNTUwNzg2ODkuNTA1NzEzMDA=',
+ ],
+ executable: ['C:\\Windows\\System32\\svchost.exe'],
+ },
+ destination: {
+ geo: {
+ region_name: ['Arizona'],
+ continent_name: ['North America'],
+ city_name: ['Phoenix'],
+ country_name: ['United States'],
+ region_iso_code: ['US-AZ'],
+ country_iso_code: ['US'],
+ },
+ port: [80],
+ ip: ['10.11.12.13'],
+ },
+ source: {
+ ip: ['10.1.2.3'],
+ port: [51570],
+ },
+ http: {
+ request: {
+ body: {
+ content: [
+ 'GET /msdownload/update/v3/static/trustedr/en/authrootstl.cab?b3d6249cb8dde683 HTTP/1.1\r\nConnection: Keep-Alive\r\nAccept: */*\r\nIf-Modified-Since: Fri, 15 Jan 2021 00:46:38 GMT\r\nIf-None-Match: "0ebbae1d7ead61:0"\r\nUser-Agent: Microsoft-CryptoAPI/10.0\r\nHost: ctldl.windowsupdate.com\r\n\r\n',
+ ],
+ bytes: [281],
+ },
+ },
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ user: {
+ name: ['NETWORK SERVICE'],
+ domain: ['NT AUTHORITY'],
+ },
+ network: {
+ protocol: ['http'],
+ direction: ['outgoing'],
+ transport: ['tcp'],
+ },
+ message: ['Endpoint network event'],
+ timestamp: '2021-02-08T19:19:38.241Z',
+ _id: '5Qwdg3cBX5UUcOOY03W7',
+};
+
+export const mockEndpointProcessExecEvent: Ecs = {
+ process: {
+ hash: {
+ md5: ['fbc61bd19421211e341e6d9b3f65e334'],
+ sha256: ['4bc018ac461706496302d1faab0a8bb39aad974eb432758665103165f3a2dd2b'],
+ sha1: ['1dc525922869533265fbeac8f7d3021489b60129'],
+ },
+ name: ['mdworker_shared'],
+ parent: {
+ name: ['launchd'],
+ pid: [1],
+ },
+ pid: [4454],
+ entity_id: [
+ 'OTA1ZDkzMTctMjIxOS00ZjQ1LTg4NTMtYzNiYzk1NGU1ZGU4LTQ0NTQtMTMyNTY3NjYwMDEuNzIwMjkwMDA=',
+ ],
+ executable: [
+ '/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared',
+ ],
+ args: [
+ '/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared',
+ '-s',
+ 'mdworker',
+ '-c',
+ 'MDSImporterWorker',
+ '-m',
+ 'com.apple.mdworker.shared',
+ ],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.6'],
+ name: ['macOS'],
+ version: ['10.14.6'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.7.0: Mon Aug 31 20:53:32 PDT 2020; root:xnu-4903.278.44~1/RELEASE_X86_64',
+ ],
+ platform: ['macos'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['test-mac.local'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['fce6b9f1-5c09-d8f0-3d99-9ecb30f995df'],
+ },
+ event: {
+ category: ['process'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['exec'],
+ type: ['start'],
+ id: ['LuH/UjERrFf60dea+++++NW7'],
+ dataset: ['endpoint.events.process'],
+ },
+ user: {
+ id: ['501'],
+ name: ['admin'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint process event'],
+ timestamp: '2021-02-02T19:00:01.972Z',
+ _id: '8lkaZHcBGrBB52F2aN8c',
+};
+
+export const mockEndpointProcessForkEvent: Ecs = {
+ process: {
+ hash: {
+ md5: ['24a77cf54ab89f3d0772c65204074710'],
+ sha256: ['cbf3d059cc9f9c0adff5ef15bf331b95ab381837fa0adecd965a41b5846f4bd4'],
+ sha1: ['6cc7c36da55c7af0969539fae73768fbef11aa1a'],
+ },
+ name: ['zoom.us'],
+ parent: {
+ name: ['zoom.us'],
+ pid: [3961],
+ },
+ pid: [4042],
+ entity_id: [
+ 'OTA1ZDkzMTctMjIxOS00ZjQ1LTg4NTMtYzNiYzk1NGU1ZGU4LTQwNDItMTMyNTY2ODI5MjQuNzYxNDAwMA==',
+ ],
+ executable: ['/Applications/zoom.us.app/Contents/MacOS/zoom.us'],
+ args: ['/Applications/zoom.us.app/Contents/MacOS/zoom.us'],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.6'],
+ name: ['macOS'],
+ version: ['10.14.6'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.7.0: Mon Aug 31 20:53:32 PDT 2020; root:xnu-4903.278.44~1/RELEASE_X86_64',
+ ],
+ platform: ['macos'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['test-mac.local'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['fce6b9f1-5c09-d8f0-3d99-9ecb30f995df'],
+ },
+ event: {
+ category: ['process'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['fork'],
+ type: ['start'],
+ id: ['LuH/UjERrFf60dea+++++KYC'],
+ dataset: ['endpoint.events.process'],
+ },
+ user: {
+ id: ['501'],
+ name: ['admin'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint process event'],
+ timestamp: '2021-02-01T19:55:24.907Z',
+ _id: 'KXomX3cBGrBB52F2S9XY',
+};
+
export const mockEndgameIpv4ConnectionAcceptEvent: Ecs = {
_id: 'LsjPcG0BOpWiDweSCNfu',
user: {
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
index cc75518cf289..f016b6cc3453 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
@@ -1302,3 +1302,186 @@ export const mockDnsEvent: Ecs = {
ip: ['10.9.9.9'],
},
};
+
+export const mockEndpointProcessExecutionMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['177afc1eb0be88eb9983fb74111260c4'],
+ sha256: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'],
+ sha1: ['f573b85e9beb32121f1949217947b2adc6749e3d'],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTY5MjAtMTMyNDg5OTk2OTAuNDgzMzA3NzAw',
+ ],
+ executable: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ name: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ pid: [6920],
+ args: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ file: {
+ mtime: ['2020-11-04T21:40:51.494Z'],
+ path: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ owner: ['sean'],
+ hash: {
+ md5: ['177afc1eb0be88eb9983fb74111260c4'],
+ sha256: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'],
+ sha1: ['f573b85e9beb32121f1949217947b2adc6749e3d'],
+ },
+ name: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe'],
+ extension: ['exe'],
+ size: [1604112],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'process'],
+ outcome: ['success'],
+ severity: [73],
+ code: ['malicious_file'],
+ action: ['execution'],
+ id: ['LsuMZVr+sdhvehVM++++Gp2Y'],
+ kind: ['alert'],
+ created: ['2020-11-04T21:41:30.533Z'],
+ module: ['endpoint'],
+ type: ['info', 'start', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-04T21:41:30.533Z',
+ message: ['Malware Prevention Alert'],
+ _id: '0dA2lXUBn9bLIbfPkY7d',
+};
+
+export const mockEndpointLibraryLoadEvent: Ecs = {
+ file: {
+ path: ['C:\\Windows\\System32\\bcrypt.dll'],
+ hash: {
+ md5: ['00439016776de367bad087d739a03797'],
+ sha1: ['2c4ba5c1482987d50a182bad915f52cd6611ee63'],
+ sha256: ['e70f5d8f87aab14e3160227d38387889befbe37fa4f8f5adc59eff52804b35fd'],
+ },
+ name: ['bcrypt.dll'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['win2019-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ },
+ event: {
+ category: ['library'],
+ kind: ['event'],
+ created: ['2021-02-05T21:27:23.921Z'],
+ module: ['endpoint'],
+ action: ['load'],
+ type: ['start'],
+ id: ['LzzWB9jjGmCwGMvk++++Da5H'],
+ dataset: ['endpoint.events.library'],
+ },
+ process: {
+ name: ['sshd.exe'],
+ pid: [9644],
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTk2NDQtMTMyNTcwMzQwNDEuNzgyMTczODAw',
+ ],
+ executable: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ user: {
+ name: ['SYSTEM'],
+ domain: ['NT AUTHORITY'],
+ },
+ message: ['Endpoint DLL load event'],
+ timestamp: '2021-02-05T21:27:23.921Z',
+ _id: 'IAUYdHcBGrBB52F2zo8Q',
+};
+
+export const mockEndpointRegistryModificationEvent: Ecs = {
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['win2019-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ },
+ event: {
+ category: ['registry'],
+ kind: ['event'],
+ created: ['2021-02-04T13:44:31.559Z'],
+ module: ['endpoint'],
+ action: ['modification'],
+ type: ['change'],
+ id: ['LzzWB9jjGmCwGMvk++++CbOn'],
+ dataset: ['endpoint.events.registry'],
+ },
+ process: {
+ name: ['GoogleUpdate.exe'],
+ pid: [7408],
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTc0MDgtMTMyNTY5MTk4NDguODY4NTI0ODAw',
+ ],
+ executable: ['C:\\Program Files (x86)\\Google\\Update\\GoogleUpdate.exe'],
+ },
+ registry: {
+ hive: ['HKLM'],
+ key: [
+ 'SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState',
+ ],
+ path: [
+ 'HKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValue',
+ ],
+ value: ['StateValue'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ user: {
+ name: ['SYSTEM'],
+ domain: ['NT AUTHORITY'],
+ },
+ message: ['Endpoint registry event'],
+ timestamp: '2021-02-04T13:44:31.559Z',
+ _id: '4cxLbXcBGrBB52F2uOfF',
+};
diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json
index a2f1f222173d..73b931bea5ff 100644
--- a/x-pack/plugins/security_solution/public/graphql/introspection.json
+++ b/x-pack/plugins/security_solution/public/graphql/introspection.json
@@ -2618,6 +2618,7 @@
"inputFields": null,
"interfaces": null,
"enumValues": [
+ { "name": "alerts", "description": "", "isDeprecated": false, "deprecationReason": null },
{ "name": "auditd", "description": "", "isDeprecated": false, "deprecationReason": null },
{
"name": "auditd_file",
@@ -2625,6 +2626,12 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "library",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "netflow",
"description": "",
@@ -2632,6 +2639,12 @@
"deprecationReason": null
},
{ "name": "plain", "description": "", "isDeprecated": false, "deprecationReason": null },
+ {
+ "name": "registry",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "suricata",
"description": "",
diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts
index f70cd37b8da9..c8b7f8f02b9d 100644
--- a/x-pack/plugins/security_solution/public/graphql/types.ts
+++ b/x-pack/plugins/security_solution/public/graphql/types.ts
@@ -287,10 +287,13 @@ export enum DataProviderType {
}
export enum RowRendererId {
+ alerts = 'alerts',
auditd = 'auditd',
auditd_file = 'auditd_file',
+ library = 'library',
netflow = 'netflow',
plain = 'plain',
+ registry = 'registry',
suricata = 'suricata',
system = 'system',
system_dns = 'system_dns',
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
index 00c7e86b0e4a..65974a10c49c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
@@ -11,9 +11,12 @@ import { ExternalLinkIcon } from '../../../../common/components/external_link_ic
import { RowRendererId } from '../../../../../common/types/timeline';
import {
+ AlertsExample,
AuditdExample,
AuditdFileExample,
+ LibraryExample,
NetflowExample,
+ RegistryExample,
SuricataExample,
SystemExample,
SystemDnsExample,
@@ -47,6 +50,13 @@ export interface RowRendererOption {
}
export const renderers: RowRendererOption[] = [
+ {
+ id: RowRendererId.alerts,
+ name: i18n.ALERTS_NAME,
+ description: i18n.ALERTS_DESCRIPTION,
+ example: AlertsExample,
+ searchableDescription: i18n.ALERTS_DESCRIPTION,
+ },
{
id: RowRendererId.auditd,
name: i18n.AUDITD_NAME,
@@ -75,6 +85,13 @@ export const renderers: RowRendererOption[] = [
example: AuditdFileExample,
searchableDescription: `${i18n.AUDITD_FILE_NAME} ${i18n.AUDITD_FILE_DESCRIPTION_PART1}`,
},
+ {
+ id: RowRendererId.library,
+ name: i18n.LIBRARY_NAME,
+ description: i18n.LIBRARY_DESCRIPTION,
+ example: LibraryExample,
+ searchableDescription: i18n.LIBRARY_DESCRIPTION,
+ },
{
id: RowRendererId.system_security_event,
name: i18n.AUTHENTICATION_NAME,
@@ -140,6 +157,13 @@ export const renderers: RowRendererOption[] = [
example: SystemEndgameProcessExample,
searchableDescription: `${i18n.PROCESS_DESCRIPTION_PART1} ${i18n.PROCESS_DESCRIPTION_PART2}`,
},
+ {
+ id: RowRendererId.registry,
+ name: i18n.REGISTRY_NAME,
+ description: i18n.REGISTRY_DESCRIPTION,
+ example: RegistryExample,
+ searchableDescription: i18n.REGISTRY_DESCRIPTION,
+ },
{
id: RowRendererId.system_fim,
name: i18n.FIM_NAME,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
index ebfe178f14ac..a0d6d4e12189 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
@@ -7,6 +7,17 @@
import { i18n } from '@kbn/i18n';
+export const ALERTS_NAME = i18n.translate('xpack.securitySolution.eventRenderers.alertsName', {
+ defaultMessage: 'Alerts',
+});
+
+export const ALERTS_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.alertsDescription',
+ {
+ defaultMessage: 'Alerts are displayed when malware or ransomware is prevented and detected',
+ }
+);
+
export const AUDITD_NAME = i18n.translate('xpack.securitySolution.eventRenderers.auditdName', {
defaultMessage: 'Auditd',
});
@@ -112,6 +123,18 @@ export const FLOW_DESCRIPTION_PART2 = i18n.translate(
}
);
+export const LIBRARY_NAME = i18n.translate('xpack.securitySolution.eventRenderers.libraryName', {
+ defaultMessage: 'Library',
+});
+
+export const LIBRARY_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.libraryDescription',
+ {
+ defaultMessage:
+ 'Library events display a Dynamically Linked Library (DLL) being loaded by a process',
+ }
+);
+
export const PROCESS = i18n.translate('xpack.securitySolution.eventRenderers.processName', {
defaultMessage: 'Process',
});
@@ -132,6 +155,17 @@ export const PROCESS_DESCRIPTION_PART2 = i18n.translate(
}
);
+export const REGISTRY_NAME = i18n.translate('xpack.securitySolution.eventRenderers.registryName', {
+ defaultMessage: 'Registry',
+});
+
+export const REGISTRY_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.registryDescription',
+ {
+ defaultMessage: 'Registry events show updates to the Windows Registry',
+ }
+);
+
export const SOCKET_NAME = i18n.translate('xpack.securitySolution.eventRenderers.socketName', {
defaultMessage: 'Socket (Network)',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
new file mode 100644
index 000000000000..b0384155c5c1
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
@@ -0,0 +1,34 @@
+/*
+ * 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 { mockEndpointProcessExecutionMalwarePreventionAlert } from '../../../../common/mock/mock_timeline_data';
+import { createEndpointAlertsRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const AlertsExampleComponent: React.FC = () => {
+ const alertsRowRenderer = createEndpointAlertsRowRenderer({
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS,
+ });
+
+ return (
+ <>
+ {alertsRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndpointProcessExecutionMalwarePreventionAlert,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const AlertsExample = React.memo(AlertsExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
index 1b4e3b1723a2..6932ca01835c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
@@ -5,9 +5,12 @@
* 2.0.
*/
+export * from './alerts';
export * from './auditd';
export * from './auditd_file';
+export * from './library';
export * from './netflow';
+export * from './registry';
export * from './suricata';
export * from './system';
export * from './system_dns';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
new file mode 100644
index 000000000000..6198225fcb87
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
@@ -0,0 +1,31 @@
+/*
+ * 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 { mockEndpointLibraryLoadEvent } from '../../../../common/mock/mock_timeline_data';
+import { createEndpointLibraryRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { LOADED_LIBRARY } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const LibraryExampleComponent: React.FC = () => {
+ const libraryRowRenderer = createEndpointLibraryRowRenderer({
+ actionName: 'load',
+ text: LOADED_LIBRARY,
+ });
+
+ return (
+ <>
+ {libraryRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndpointLibraryLoadEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const LibraryExample = React.memo(LibraryExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
new file mode 100644
index 000000000000..f00db0d94eed
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
@@ -0,0 +1,31 @@
+/*
+ * 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 { mockEndpointRegistryModificationEvent } from '../../../../common/mock/mock_timeline_data';
+import { createEndpointRegistryRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { MODIFIED_REGISTRY_KEY } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const RegistryExampleComponent: React.FC = () => {
+ const registryRowRenderer = createEndpointRegistryRowRenderer({
+ actionName: 'modification',
+ text: MODIFIED_REGISTRY_KEY,
+ });
+
+ return (
+ <>
+ {registryRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndpointRegistryModificationEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const RegistryExample = React.memo(RegistryExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
index e6d79c4ba53b..d7274f0774fc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
@@ -33,12 +33,15 @@ describe('FileDraggable', () => {
endgameFileName="[endgameFileName]"
endgameFilePath="[endgameFilePath]"
eventId="1"
+ fileExtOriginalPath="[fileExtOriginalPath]"
fileName="[fileName]"
filePath="[filePath]"
/>
);
- expect(wrapper.text()).toEqual('[fileName]in[filePath]');
+ expect(wrapper.text()).toEqual(
+ '[fileName]in[filePath]from its original path[fileExtOriginalPath]'
+ );
});
test('it returns an empty string when none of the files or paths are provided', () => {
@@ -49,6 +52,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath={undefined}
/>
@@ -65,6 +69,7 @@ describe('FileDraggable', () => {
endgameFileName="[endgameFileName]"
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath={undefined}
/>
@@ -81,6 +86,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath="[endgameFilePath]"
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath={undefined}
/>
@@ -97,6 +103,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName="[fileName]"
filePath={undefined}
/>
@@ -113,6 +120,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath="[filePath]"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
index af118a1688ae..703b38e627e5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
@@ -19,10 +19,19 @@ interface Props {
eventId: string;
fileName: string | null | undefined;
filePath: string | null | undefined;
+ fileExtOriginalPath: string | null | undefined;
}
export const FileDraggable = React.memo(
- ({ contextId, endgameFileName, endgameFilePath, eventId, fileName, filePath }) => {
+ ({
+ contextId,
+ endgameFileName,
+ endgameFilePath,
+ eventId,
+ fileExtOriginalPath,
+ fileName,
+ filePath,
+ }) => {
if (
isNillEmptyOrNotFinite(fileName) &&
isNillEmptyOrNotFinite(endgameFileName) &&
@@ -86,6 +95,23 @@ export const FileDraggable = React.memo(
/>
) : null}
+
+ {!isNillEmptyOrNotFinite(fileExtOriginalPath) && (
+ <>
+
+ {i18n.FROM_ITS_ORIGINAL_PATH}
+
+
+
+
+ >
+ )}
>
);
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
new file mode 100644
index 000000000000..e7e6274942be
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 { TestProviders } from '../../../../../common/mock';
+import '../../../../../common/mock/match_media';
+import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+
+import { FileHash } from './file_hash';
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+ return {
+ ...original,
+ // eslint-disable-next-line react/display-name
+ EuiScreenReaderOnly: () => <>>,
+ };
+});
+
+describe('FileHash', () => {
+ const mount = useMountAppended();
+
+ const allProps = {
+ contextId: 'test',
+ eventId: '1',
+ fileHashSha256: undefined,
+ };
+
+ test('displays the fileHashSha256 when provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('[fileHashSha256]');
+ });
+
+ test('displays nothing when fileHashSha256 is null', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+
+ test('displays nothing when fileHashSha256 is undefined', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
new file mode 100644
index 000000000000..9e624ba17c92
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
@@ -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.
+ */
+
+import { EuiFlexGroup } from '@elastic/eui';
+import React from 'react';
+import styled from 'styled-components';
+
+import { DraggableBadge } from '../../../../../common/components/draggables';
+
+import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers';
+
+const HashFlexGroup = styled(EuiFlexGroup)`
+ margin: ${({ theme }) => theme.eui.euiSizeXS};
+`;
+
+interface Props {
+ contextId: string;
+ eventId: string;
+ fileHashSha256: string | null | undefined;
+}
+
+export const FileHash = React.memo(({ contextId, eventId, fileHashSha256 }) => {
+ if (isNillEmptyOrNotFinite(fileHashSha256)) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+});
+
+FileHash.displayName = 'FileHash';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx
index d32ec728d229..6fe3726c328f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx
@@ -193,7 +193,19 @@ describe('helpers', () => {
});
describe('valid values', () => {
- const validValues = ['file_create_event', 'created', 'file_delete_event', 'deleted'];
+ const validValues = [
+ 'created',
+ 'creation',
+ 'deleted',
+ 'deletion',
+ 'file_create_event',
+ 'file_delete_event',
+ 'files-encrypted',
+ 'load',
+ 'modification',
+ 'overwrite',
+ 'rename',
+ ];
validValues.forEach((eventAction) => {
test(`${eventAction} returns true`, () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx
index ea84dc19908f..e4644414fdc8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx
@@ -60,6 +60,26 @@ export const isProcessStoppedOrTerminationEvent = (
): boolean => ['process_stopped', 'termination_event'].includes(`${eventAction}`.toLowerCase());
export const showVia = (eventAction: string | null | undefined): boolean =>
- ['file_create_event', 'created', 'creation', 'file_delete_event', 'deleted', 'deletion'].includes(
- `${eventAction}`.toLowerCase()
- );
+ [
+ 'created',
+ 'creation',
+ 'deleted',
+ 'deletion',
+ 'file_create_event',
+ 'file_delete_event',
+ 'files-encrypted',
+ 'load',
+ 'modification',
+ 'overwrite',
+ 'rename',
+ ].includes(`${eventAction}`.toLowerCase());
+
+export const excludeFileNameAndPath = ({
+ eventAction,
+ eventCategory,
+ eventType,
+}: {
+ eventAction: string | null | undefined;
+ eventCategory: string | null | undefined;
+ eventType: string | null | undefined;
+}) => false;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
index 5dd30b84d2b7..9e90e061e94d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
@@ -22,81 +22,39 @@ jest.mock('@elastic/eui', () => {
};
});
+const allProps = {
+ contextId: 'test',
+ eventId: '1',
+ processHashSha256: undefined,
+};
+
describe('ProcessHash', () => {
const mount = useMountAppended();
- test('displays the processHashMd5, processHashSha1, and processHashSha256 when they are all provided', () => {
+ test('displays the processHashSha256 when provided', () => {
const wrapper = mount(
-
+
);
- expect(wrapper.text()).toEqual('[processHashSha256][processHashSha1][processHashMd5]');
+ expect(wrapper.text()).toEqual('[processHashSha256]');
});
- test('displays nothing when processHashMd5, processHashSha1, and processHashSha256 are all undefined', () => {
+ test('displays nothing when processHashSha256 is null', () => {
const wrapper = mount(
-
+
);
expect(wrapper.text()).toEqual('');
});
- test('displays just processHashMd5 when the other hashes are undefined', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.text()).toEqual('[processHashMd5]');
- });
-
- test('displays just processHashSha1 when the other hashes are undefined', () => {
+ test('displays nothing when processHashSha256 is undefined', () => {
const wrapper = mount(
-
+
);
- expect(wrapper.text()).toEqual('[processHashSha1]');
- });
-
- test('displays just processHashSha256 when the other hashes are undefined', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.text()).toEqual('[processHashSha256]');
+ expect(wrapper.text()).toEqual('');
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
index 1af2daad7fc5..32432afbf205 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
@@ -20,61 +20,27 @@ const HashFlexGroup = styled(EuiFlexGroup)`
interface Props {
contextId: string;
eventId: string;
- processHashMd5: string | null | undefined;
- processHashSha1: string | null | undefined;
processHashSha256: string | null | undefined;
}
-export const ProcessHash = React.memo(
- ({ contextId, eventId, processHashMd5, processHashSha1, processHashSha256 }) => {
- if (
- isNillEmptyOrNotFinite(processHashSha256) &&
- isNillEmptyOrNotFinite(processHashSha1) &&
- isNillEmptyOrNotFinite(processHashMd5)
- ) {
- return null;
- }
-
- return (
-
- {!isNillEmptyOrNotFinite(processHashSha256) && (
-
-
-
- )}
-
- {!isNillEmptyOrNotFinite(processHashSha1) && (
-
-
-
- )}
-
- {!isNillEmptyOrNotFinite(processHashMd5) && (
-
-
-
- )}
-
- );
+export const ProcessHash = React.memo(({ contextId, eventId, processHashSha256 }) => {
+ if (isNillEmptyOrNotFinite(processHashSha256)) {
+ return null;
}
-);
+
+ return (
+
+
+
+
+
+ );
+});
ProcessHash.displayName = 'ProcessHash';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
new file mode 100644
index 000000000000..f37adef7e73c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 { mockBrowserFields } from '../../../../../../common/containers/source/mock';
+import {
+ mockEndpointRegistryModificationEvent,
+ TestProviders,
+} from '../../../../../../common/mock';
+import '../../../../../../common/mock/match_media';
+import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+import { MODIFIED_REGISTRY_KEY } from '../system/translations';
+
+import { RegistryEventDetails } from './registry_event_details';
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+ return {
+ ...original,
+ // eslint-disable-next-line react/display-name
+ EuiScreenReaderOnly: () => <>>,
+ };
+});
+
+describe('RegistryEventDetails', () => {
+ const mount = useMountAppended();
+
+ test('it renders the expected text given an Endpoint Registry modification event', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1modified registry keySOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentStatewith new valueHKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValueviaGoogleUpdate.exe(7408)'
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
new file mode 100644
index 000000000000..0bfb03168019
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 { get } from 'lodash/fp';
+import React from 'react';
+
+import { BrowserFields } from '../../../../../../common/containers/source';
+import { Details, isNillEmptyOrNotFinite } from '../helpers';
+import { Ecs } from '../../../../../../../common/ecs';
+
+import { RegistryEventDetailsLine } from './registry_event_details_line';
+
+interface Props {
+ browserFields: BrowserFields;
+ contextId: string;
+ data: Ecs;
+ text: string;
+}
+
+const RegistryEventDetailsComponent: React.FC = ({ contextId, data, text }) => {
+ const hostName: string | null | undefined = get('host.name[0]', data);
+ const id = data._id;
+ const processName: string | null | undefined = get('process.name[0]', data);
+ const processPid: number | null | undefined = get('process.pid[0]', data);
+ const registryKey: string | null | undefined = get('registry.key[0]', data);
+ const registryPath: string | null | undefined = get('registry.path[0]', data);
+ const userDomain: string | null | undefined = get('user.domain[0]', data);
+ const userName: string | null | undefined = get('user.name[0]', data);
+
+ if (isNillEmptyOrNotFinite(registryKey)) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
+
+RegistryEventDetailsComponent.displayName = 'RegistryEventDetailsComponent';
+
+export const RegistryEventDetails = React.memo(RegistryEventDetailsComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
new file mode 100644
index 000000000000..6be152915252
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
@@ -0,0 +1,135 @@
+/*
+ * 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 { TestProviders } from '../../../../../../common/mock';
+import '../../../../../../common/mock/match_media';
+import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+import { RegistryEventDetailsLine } from './registry_event_details_line';
+import { MODIFIED_REGISTRY_KEY } from '../system/translations';
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+ return {
+ ...original,
+ // eslint-disable-next-line react/display-name
+ EuiScreenReaderOnly: () => <>>,
+ };
+});
+
+describe('DnsRequestEventDetailsLine', () => {
+ const mount = useMountAppended();
+
+ const allProps = {
+ contextId: 'test',
+ hostName: '[hostName]',
+ id: '1',
+ processName: '[processName]',
+ processPid: 123,
+ registryKey: '[registryKey]',
+ registryPath: '[registryPath]',
+ text: MODIFIED_REGISTRY_KEY,
+ userDomain: '[userDomain]',
+ userName: '[userName]',
+ };
+
+ test('it renders the expected text when all properties are provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+
+ test('it returns an empty string when when registryKey is null', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+
+ test('it returns an empty string when registryKey is undefined', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+
+ test('it renders the expected text when hostName is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+
+ test('it renders the expected text when processName is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]with new value[registryPath]via(123)'
+ );
+ });
+
+ test('it renders the expected text when processPid is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]with new value[registryPath]via[processName]'
+ );
+ });
+
+ test('it renders the expected text when registryPath is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]via[processName](123)'
+ );
+ });
+
+ test('it renders the expected text when userDomain is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]@[hostName]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+
+ test('it renders the expected text when userName is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '\\[userDomain][hostName]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
new file mode 100644
index 000000000000..b85ae25ed250
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
@@ -0,0 +1,133 @@
+/*
+ * 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 { EuiFlexGroup } from '@elastic/eui';
+import React, { useMemo } from 'react';
+
+import { DraggableBadge } from '../../../../../../common/components/draggables';
+import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers';
+import { ProcessDraggableWithNonExistentProcess } from '../process_draggable';
+import { UserHostWorkingDir } from '../user_host_working_dir';
+
+import * as i18n from './translations';
+
+interface Props {
+ contextId: string;
+ hostName: string | null | undefined;
+ id: string;
+ processName: string | null | undefined;
+ processPid: number | null | undefined;
+ registryKey: string | null | undefined;
+ registryPath: string | null | undefined;
+ text: string;
+ userDomain: string | null | undefined;
+ userName: string | null | undefined;
+}
+
+const RegistryEventDetailsLineComponent: React.FC = ({
+ contextId,
+ hostName,
+ id,
+ processName,
+ processPid,
+ registryKey,
+ registryPath,
+ text,
+ userDomain,
+ userName,
+}) => {
+ const registryKeyTooltipContent = useMemo(
+ () => (
+ <>
+ {'registry.key'}
+ {registryKey}
+ >
+ ),
+ [registryKey]
+ );
+
+ const registryPathTooltipContent = useMemo(
+ () => (
+ <>
+ {'registry.path'}
+ {registryPath}
+ >
+ ),
+ [registryPath]
+ );
+
+ if (isNillEmptyOrNotFinite(registryKey)) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+ {!isNillEmptyOrNotFinite(registryKey) && (
+ <>
+
+ {text}
+
+
+
+
+ >
+ )}
+
+ {!isNillEmptyOrNotFinite(registryPath) && (
+ <>
+
+ {i18n.WITH_NEW_VALUE}
+
+
+
+
+ >
+ )}
+
+
+ {i18n.VIA}
+
+
+
+
+
+
+ >
+ );
+};
+
+export const RegistryEventDetailsLine = React.memo(RegistryEventDetailsLineComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.ts
new file mode 100644
index 000000000000..f1c1c9487dfd
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.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 { i18n } from '@kbn/i18n';
+
+export const VIA = i18n.translate(
+ 'xpack.securitySolution.timeline.body.renderers.registry.viaDescription',
+ {
+ defaultMessage: 'via',
+ }
+);
+
+export const WITH_NEW_VALUE = i18n.translate(
+ 'xpack.securitySolution.timeline.body.renderers.registry.withNewValueDescription',
+ {
+ defaultMessage: 'with new value',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap
index 8045ce95ca27..b33d77714adc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap
@@ -11,6 +11,8 @@ exports[`SystemGenericFileDetails rendering it renders the default SystemGeneric
outcome="failure"
processPid={6278}
showMessage={true}
+ skipRedundantFileDetails={false}
+ skipRedundantProcessDetails={false}
text="[generic-text-123]"
userName="Evan"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
index a3932fde44c1..b660d823954e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
@@ -106,6 +106,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={789}
endgameProcessName="[endgameProcessName-123]"
eventAction="[eventAction-123]"
+ fileExtOriginalPath="[fileExtOriginalPath]"
+ fileHashSha256="[fileHashSha256-123]"
fileName="[fileName-123]"
filePath="[filePath-123]"
hostName="[hostname-123]"
@@ -118,8 +120,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable=123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -138,7 +138,7 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][generic-text-123][fileName-123]in[filePath-123][processName-123](123)[arg-1][arg-2][arg-3][some-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][generic-text-123][fileName-123]in[filePath-123]from its original path[fileExtOriginalPath][processName-123](123)[arg-1][arg-2][arg-3][some-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][fileHashSha256-123][processHashSha256-123][message-123]'
);
});
@@ -155,6 +155,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName={null}
@@ -166,8 +168,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -203,6 +203,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -214,8 +216,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -251,6 +251,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -262,8 +264,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -299,6 +299,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -310,8 +312,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -349,6 +349,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -360,8 +362,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -399,6 +399,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -410,8 +412,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -449,6 +449,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -460,8 +462,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -499,6 +499,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -510,8 +512,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -536,7 +536,7 @@ describe('SystemGenericFileDetails', () => {
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode', () => {
const wrapper = mount(
@@ -549,6 +549,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -560,8 +562,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -582,61 +582,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashMd5-123][message-123]'
- );
- });
-
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1', () => {
- const wrapper = mount(
-
-
-
-
-
- );
- expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256', () => {
const wrapper = mount(
@@ -649,6 +599,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -660,8 +612,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName={null}
processParentPid={null}
@@ -682,11 +632,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName', () => {
const wrapper = mount(
@@ -699,6 +649,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -710,8 +662,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={null}
@@ -732,11 +682,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid', () => {
const wrapper = mount(
@@ -749,6 +699,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -760,8 +712,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -782,11 +732,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid', () => {
const wrapper = mount(
@@ -799,6 +749,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -810,8 +762,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -832,11 +782,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123](123)with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123](123)with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName', () => {
const wrapper = mount(
@@ -849,6 +799,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -860,8 +812,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -882,11 +832,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processName-123](123)with exit code-1via parent process[processParentName-123](789)(456)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processName-123](123)with exit code-1via parent process[processParentName-123](789)(456)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod', () => {
const wrapper = mount(
@@ -899,6 +849,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -910,8 +862,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -932,11 +882,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature', () => {
const wrapper = mount(
@@ -949,6 +899,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -960,8 +912,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -982,11 +932,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text', () => {
const wrapper = mount(
@@ -999,6 +949,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1010,8 +962,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1032,11 +982,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain', () => {
const wrapper = mount(
@@ -1049,6 +999,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1060,8 +1012,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1082,11 +1032,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '\\[userDomain-123][hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '\\[userDomain-123][hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username', () => {
const wrapper = mount(
@@ -1099,6 +1049,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1110,8 +1062,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1132,11 +1082,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory', () => {
const wrapper = mount(
@@ -1149,6 +1099,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1160,8 +1112,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1182,11 +1132,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title', () => {
const wrapper = mount(
@@ -1199,6 +1149,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1210,8 +1162,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1232,11 +1182,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title, args', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title, args', () => {
const wrapper = mount(
@@ -1249,6 +1199,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1260,8 +1212,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1282,7 +1232,7 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[arg-1][arg-2][arg-3][process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[arg-1][arg-2][arg-3][process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
@@ -1299,6 +1249,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1310,8 +1262,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1347,6 +1297,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName="[fileName]"
filePath="[filePath]"
hostName={undefined}
@@ -1358,8 +1310,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1397,6 +1347,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1408,8 +1360,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1449,6 +1399,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1460,8 +1412,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1500,6 +1450,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1511,8 +1463,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1553,6 +1503,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1564,8 +1516,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1604,6 +1554,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1615,8 +1567,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1653,6 +1603,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1664,8 +1616,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1702,6 +1652,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1713,8 +1665,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1751,6 +1701,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={789}
endgameProcessName="[endgameProcessName]"
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1762,8 +1714,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1800,6 +1750,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={789}
endgameProcessName="[endgameProcessName]"
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1811,8 +1763,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
index 4026613ff7d0..6df583656ff2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
@@ -23,6 +23,7 @@ import { Args } from '../args';
import { AuthSsh } from './auth_ssh';
import { ExitCodeDraggable } from '../exit_code_draggable';
import { FileDraggable } from '../file_draggable';
+import { FileHash } from '../file_hash';
import { Package } from './package';
import { Badge } from '../../../../../../common/components/page';
import { ParentProcessDraggable } from '../parent_process_draggable';
@@ -38,6 +39,8 @@ interface Props {
endgamePid: number | null | undefined;
endgameProcessName: string | null | undefined;
eventAction: string | null | undefined;
+ fileExtOriginalPath: string | null | undefined;
+ fileHashSha256: string | null | undefined;
fileName: string | null | undefined;
filePath: string | null | undefined;
hostName: string | null | undefined;
@@ -54,11 +57,11 @@ interface Props {
processPid: number | null | undefined;
processPpid: number | null | undefined;
processExecutable: string | null | undefined;
- processHashMd5: string | null | undefined;
- processHashSha1: string | null | undefined;
processHashSha256: string | null | undefined;
processTitle: string | null | undefined;
showMessage: boolean;
+ skipRedundantFileDetails?: boolean;
+ skipRedundantProcessDetails?: boolean;
sshSignature: string | null | undefined;
sshMethod: string | null | undefined;
text: string | null | undefined;
@@ -78,6 +81,8 @@ export const SystemGenericFileLine = React.memo
(
endgamePid,
endgameProcessName,
eventAction,
+ fileExtOriginalPath,
+ fileHashSha256,
fileName,
filePath,
hostName,
@@ -91,14 +96,14 @@ export const SystemGenericFileLine = React.memo(
packageSummary,
packageVersion,
processExecutable,
- processHashMd5,
- processHashSha1,
processHashSha256,
processName,
processPid,
processPpid,
processTitle,
showMessage,
+ skipRedundantFileDetails = false,
+ skipRedundantProcessDetails = false,
sshSignature,
sshMethod,
text,
@@ -119,14 +124,18 @@ export const SystemGenericFileLine = React.memo(
{text}
-
+
+ {!skipRedundantFileDetails && (
+
+ )}
{showVia(eventAction) && (
{i18n.VIA}
@@ -190,13 +199,12 @@ export const SystemGenericFileLine = React.memo(
packageVersion={packageVersion}
/>
-
+ {!skipRedundantFileDetails && (
+
+ )}
+ {!skipRedundantProcessDetails && (
+
+ )}
{message != null && showMessage && (
<>
@@ -221,12 +229,22 @@ interface GenericDetailsProps {
data: Ecs;
contextId: string;
showMessage?: boolean;
+ skipRedundantFileDetails?: boolean;
+ skipRedundantProcessDetails?: boolean;
text: string;
timelineId: string;
}
export const SystemGenericFileDetails = React.memo(
- ({ data, contextId, showMessage = true, text, timelineId }) => {
+ ({
+ data,
+ contextId,
+ showMessage = true,
+ skipRedundantFileDetails = false,
+ skipRedundantProcessDetails = false,
+ text,
+ timelineId,
+ }) => {
const id = data._id;
const message: string | null = data.message != null ? data.message[0] : null;
const hostName: string | null | undefined = get('host.name[0]', data);
@@ -240,6 +258,8 @@ export const SystemGenericFileDetails = React.memo(
const endgamePid: number | null | undefined = get('endgame.pid[0]', data);
const endgameProcessName: string | null | undefined = get('endgame.process_name[0]', data);
const eventAction: string | null | undefined = get('event.action[0]', data);
+ const fileExtOriginalPath: string | null | undefined = get('file.Ext.original.path[0]', data);
+ const fileHashSha256: string | null | undefined = get('file.hash.sha256[0]', data);
const fileName: string | null | undefined = get('file.name[0]', data);
const filePath: string | null | undefined = get('file.path[0]', data);
const userDomain: string | null | undefined = get('user.domain[0]', data);
@@ -251,8 +271,6 @@ export const SystemGenericFileDetails = React.memo(
const processExitCode: number | null | undefined = get('process.exit_code[0]', data);
const processParentName: string | null | undefined = get('process.parent.name[0]', data);
const processParentPid: number | null | undefined = get('process.parent.pid[0]', data);
- const processHashMd5: string | null | undefined = get('process.hash.md5[0]', data);
- const processHashSha1: string | null | undefined = get('process.hash.sha1[0]', data);
const processHashSha256: string | null | undefined = get('process.hash.sha256[0]', data);
const processPid: number | null | undefined = get('process.pid[0]', data);
const processPpid: number | null | undefined = get('process.ppid[0]', data);
@@ -278,6 +296,8 @@ export const SystemGenericFileDetails = React.memo(
endgamePid={endgamePid}
endgameProcessName={endgameProcessName}
eventAction={eventAction}
+ fileExtOriginalPath={fileExtOriginalPath}
+ fileHashSha256={fileHashSha256}
fileName={fileName}
filePath={filePath}
userDomain={userDomain}
@@ -292,14 +312,14 @@ export const SystemGenericFileDetails = React.memo(
packageName={packageName}
packageSummary={packageSummary}
packageVersion={packageVersion}
- processHashMd5={processHashMd5}
- processHashSha1={processHashSha1}
processHashSha256={processHashSha256}
processName={processName}
processPid={processPid}
processPpid={processPpid}
processExecutable={processExecutable}
showMessage={showMessage}
+ skipRedundantFileDetails={skipRedundantFileDetails}
+ skipRedundantProcessDetails={skipRedundantProcessDetails}
sshSignature={sshSignature}
sshMethod={sshMethod}
outcome={outcome}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
index 4de9bcbdd9c1..b7857e6bf458 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
@@ -15,6 +15,9 @@ import { mockBrowserFields } from '../../../../../../common/containers/source/mo
import { Ecs } from '../../../../../../../common/ecs';
import {
mockDnsEvent,
+ mockEndpointProcessExecutionMalwarePreventionAlert,
+ mockEndpointLibraryLoadEvent,
+ mockEndpointRegistryModificationEvent,
mockFimFileCreatedEvent,
mockFimFileDeletedEvent,
mockSocketClosedEvent,
@@ -38,10 +41,25 @@ import {
mockEndgameUserLogon,
mockEndpointDisconnectReceivedEvent,
mockEndpointFileCreationEvent,
+ mockEndpointFileCreationMalwarePreventionAlert,
+ mockEndpointFileCreationMalwareDetectionAlert,
+ mockEndpointFilesEncryptedRansomwarePreventionAlert,
+ mockEndpointFilesEncryptedRansomwareDetectionAlert,
+ mockEndpointFileModificationMalwarePreventionAlert,
+ mockEndpointFileModificationMalwareDetectionAlert,
+ mockEndpointFileRenameMalwarePreventionAlert,
+ mockEndpointFileRenameMalwareDetectionAlert,
mockEndpointFileDeletionEvent,
+ mockEndpointFileModificationEvent,
+ mockEndpointFileOverwriteEvent,
+ mockEndpointFileRenameEvent,
mockEndpointNetworkConnectionAcceptedEvent,
+ mockEndpointNetworkHttpRequestEvent,
mockEndpointNetworkLookupRequestedEvent,
mockEndpointNetworkLookupResultEvent,
+ mockEndpointProcessExecEvent,
+ mockEndpointProcessExecutionMalwareDetectionAlert,
+ mockEndpointProcessForkEvent,
mockEndpointProcessStartEvent,
mockEndpointProcessEndEvent,
mockEndpointSecurityLogOnSuccessEvent,
@@ -53,11 +71,15 @@ import { RowRenderer } from '../row_renderer';
import {
createDnsRowRenderer,
createEndgameProcessRowRenderer,
+ createEndpointAlertsRowRenderer,
+ createEndpointLibraryRowRenderer,
+ createEndpointRegistryRowRenderer,
createFimRowRenderer,
createGenericSystemRowRenderer,
createGenericFileRowRenderer,
createSecurityEventRowRenderer,
createSocketRowRenderer,
+ EndpointAlertCriteria,
} from './generic_row_renderer';
import * as i18n from './translations';
@@ -197,7 +219,341 @@ describe('GenericRowRenderer', () => {
});
});
+ describe('#createEndpointAlertsRowRenderer', () => {
+ test('it renders a Malware File Creation Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_CREATING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileCreationMalwarePreventionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileCreationMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from creating a malicious file6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmpinC:\\Users\\sean\\Downloads\\6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmpviachrome.exe(8944)C:\\Program Files\\Google\\Chrome\\Application\\chrome.exevia parent processexplorer.exe(1008)with resultsuccess7cc42618e580f233fee47e82312cc5c3476cb5de9219ba3f9eb7f99ac0659c30'
+ );
+ });
+
+ test('it renders a Malware File Creation Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_CREATING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileCreationMalwareDetectionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileCreationMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1was detected creating a malicious filemimikatz_write.exeinC:\\temp\\mimikatz_write.exeviapython.exe(4400)C:\\Python27\\python.exemain.py-a,execute-pc:\\tempvia parent processpythonservice.exe(2936)with resultsuccess263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'
+ );
+ });
+
+ test('it renders a Ransomware Files Encrypted Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_PREVENTED_FROM_ENCRYPTING_FILES,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFilesEncryptedRansomwarePreventionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFilesEncryptedRansomwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1ransomware was prevented from encrypting filesviapowershell.exe(6056)powershell.exe-filemock_ransomware_v3.ps1via parent processcmd.exe(10680)with resultsuccesse9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'
+ );
+ });
+
+ test('it renders a Ransomware Files Encrypted Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_DETECTED_ENCRYPTING_FILES,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFilesEncryptedRansomwareDetectionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFilesEncryptedRansomwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1ransomware was detected encrypting filesviapowershell.exe(4684)powershell.exe-filemock_ransomware_v3.ps1via parent processcmd.exe(8616)e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'
+ );
+ });
+
+ test('it renders a Malware File Modification Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_MODIFYING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFileModificationMalwarePreventionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileModificationMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from modifying a malicious filemimikatz - Copy.exeinC:\\Users\\sean\\Downloads\\mimikatz_trunk (1)\\x64\\mimikatz - Copy.exeviaexplorer.exe(1008)C:\\Windows\\Explorer.EXEvia parent processC:\\Windows\\System32\\userinit.exe(356)with resultsuccess31eb1de7e840a342fd468e558e5ab627bcb4c542a8fe01aec4d5ba01d539a0fc'
+ );
+ });
+
+ test('it renders a Malware File Modification Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_MODIFYING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFileModificationMalwareDetectionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileModificationMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'mac-1.localwas detected modifying a malicious fileaircrackin/private/var/root/write_malware/modules/write_malware/aircrackviaPython(5995)/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Pythonmain.py-amodifyvia parent processPython(97)with resultsuccessf0954d9673878b2223b00b7ec770c7b438d876a9bb44ec78457e5c618f31f52b'
+ );
+ });
+
+ test('it renders a Malware File Rename Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_RENAMING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileRenameMalwarePreventionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileRenameMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from renaming a malicious file23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeinC:\\Users\\sean\\Downloads\\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeviaexplorer.exe(1008)C:\\Windows\\Explorer.EXEvia parent processC:\\Windows\\System32\\userinit.exe(356)with resultsuccess23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97'
+ );
+ });
+
+ test('it renders a Malware File Rename Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_RENAMING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileRenameMalwareDetectionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileRenameMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was detected renaming a malicious file23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeinC:\\Users\\sean\\Downloads\\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeviaexplorer.exe(1008)C:\\Windows\\Explorer.EXEvia parent processC:\\Windows\\System32\\userinit.exe(356)with resultsuccess23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97'
+ );
+ });
+
+ test('it renders a Malware Process Execution Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointProcessExecutionMalwarePreventionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessExecutionMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from executing a malicious processC:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe(6920)C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exewith resultsuccess3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'
+ );
+ });
+
+ test('it renders a Malware Process Execution Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_DETECTED_EXECUTING_A_MALICIOUS_PROCESS,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointProcessExecutionMalwareDetectionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessExecutionMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1was detected executing a malicious processmimikatz_write.exe(8668)c:\\temp\\mimikatz_write.exevia parent processpython.exewith resultsuccess263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'
+ );
+ });
+ });
+
describe('#createEndgameProcessRowRenderer', () => {
+ test('it renders an endpoint Process Exec event', () => {
+ const actionName = 'exec';
+ const text = i18n.EXECUTED_PROCESS;
+
+ const endpointProcessStartRowRenderer = createEndgameProcessRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointProcessStartRowRenderer.isInstance(mockEndpointProcessExecEvent) &&
+ endpointProcessStartRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessExecEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'admin@test-mac.localexecuted processmdworker_shared(4454)/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared-smdworker-cMDSImporterWorker-mcom.apple.mdworker.sharedvia parent processlaunchd(1)4bc018ac461706496302d1faab0a8bb39aad974eb432758665103165f3a2dd2b'
+ );
+ });
+
+ test('it renders an endpoint Process Fork event', () => {
+ const actionName = 'fork';
+ const text = i18n.FORKED_PROCESS;
+
+ const endpointProcessStartRowRenderer = createEndgameProcessRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointProcessStartRowRenderer.isInstance(mockEndpointProcessForkEvent) &&
+ endpointProcessStartRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessForkEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'admin@test-mac.localforked processzoom.us(4042)/Applications/zoom.us.app/Contents/MacOS/zoom.usvia parent processzoom.us(3961)cbf3d059cc9f9c0adff5ef15bf331b95ab381837fa0adecd965a41b5846f4bd4'
+ );
+ });
+
test('it renders an endpoint process start event', () => {
const actionName = 'start';
const text = i18n.PROCESS_STARTED;
@@ -219,7 +575,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1started processconhost.exe(3636)C:\\Windows\\system32\\conhost.exe,0xffffffff,-ForceV1697334c236cce7d4c9e223146ee683a1219adced9729d4ae771fd6a1502a6b63e19da2c35ba1c38adf12d1a472c1fcf1f1a811a71b0e9b5fcb62de0787235ecca560b610'
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1started processconhost.exe(3636)C:\\Windows\\system32\\conhost.exe,0xffffffff,-ForceV1697334c236cce7d4c9e223146ee683a1219adced9729d4ae771fd6a1502a6b63'
);
});
@@ -247,7 +603,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'Arun\\Anvi-Acer@HD-obe-8bf77f54started processMicrosoft.Photos.exe(441684)C:\\Program Files\\WindowsApps\\Microsoft.Windows.Photos_2018.18091.17210.0_x64__8wekyb3d8bbwe\\Microsoft.Photos.exe-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mcavia parent processsvchost.exe(8)d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee12563599116157778a22600d2a163d8112aed84562d06d7235b37895b68de56687895743'
+ 'Arun\\Anvi-Acer@HD-obe-8bf77f54started processMicrosoft.Photos.exe(441684)C:\\Program Files\\WindowsApps\\Microsoft.Windows.Photos_2018.18091.17210.0_x64__8wekyb3d8bbwe\\Microsoft.Photos.exe-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mcavia parent processsvchost.exe(8)d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee'
);
});
@@ -272,7 +628,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'SYSTEM\\NT AUTHORITY@win2019-endpointterminated processsvchost.exe(10392)C:\\Windows\\System32\\svchost.exe,-k,netsvcs,-p,-s,NetSetupSvcwith exit code-1via parent processservices.exe7fd065bac18c5278777ae44908101cdfed72d26fa741367f0ad4d02020787ab6a1385ce20ad79f55df235effd9780c31442aa2348a0a29438052faed8a2532da50455756'
+ 'SYSTEM\\NT AUTHORITY@win2019-endpointterminated processsvchost.exe(10392)C:\\Windows\\System32\\svchost.exe,-k,netsvcs,-p,-s,NetSetupSvcwith exit code-1via parent processservices.exe7fd065bac18c5278777ae44908101cdfed72d26fa741367f0ad4d02020787ab6'
);
});
@@ -300,7 +656,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'Arun\\Anvi-Acer@HD-obe-8bf77f54terminated processRuntimeBroker.exe(442384)with exit code087976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776797255e72d5ed5c058d4785950eba7abaa057653bd4401441a21bf1abce6404f4231db4d'
+ 'Arun\\Anvi-Acer@HD-obe-8bf77f54terminated processRuntimeBroker.exe(442384)with exit code087976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776'
);
});
@@ -470,6 +826,81 @@ describe('GenericRowRenderer', () => {
);
});
+ test('it renders an endpoint File (FIM) Modification event', () => {
+ const actionName = 'modification';
+ const text = i18n.MODIFIED_FILE;
+
+ const endpointFileModificationRowRenderer = createFimRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointFileModificationRowRenderer.isInstance(mockEndpointFileModificationEvent) &&
+ endpointFileModificationRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileModificationEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'admin@test-Mac.localmodified a file.dat.nosync01a5.6hoWv1in/Users/admin/Library/Application Support/CrashReporter/.dat.nosync01a5.6hoWv1viadiagnostics_agent(421)'
+ );
+ });
+
+ test('it renders an endpoint File (FIM) Overwrite event', () => {
+ const actionName = 'overwrite';
+ const text = i18n.OVERWROTE_FILE;
+
+ const endpointFileOverwriteRowRenderer = createFimRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointFileOverwriteRowRenderer.isInstance(mockEndpointFileOverwriteEvent) &&
+ endpointFileOverwriteRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileOverwriteEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'LOCAL SERVICE\\NT AUTHORITY@windows-endpoint-1overwrote a filelastalive0.datinC:\\Windows\\ServiceState\\EventLog\\Data\\lastalive0.datviasvchost.exe(1228)'
+ );
+ });
+
+ test('it renders an endpoint File (FIM) Rename event', () => {
+ const actionName = 'rename';
+ const text = i18n.RENAMED_FILE;
+
+ const endpointFileRenameRowRenderer = createFimRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointFileRenameRowRenderer.isInstance(mockEndpointFileRenameEvent) &&
+ endpointFileRenameRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileRenameEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'LOCAL SERVICE\\NT AUTHORITY@windows-endpoint-1renamed a fileSRU.loginC:\\Windows\\System32\\sru\\SRU.logfrom its original pathC:\\Windows\\System32\\sru\\SRUtmp.logviasvchost.exe(1204)'
+ );
+ });
+
test('it renders an endgame file_delete_event', () => {
const actionName = 'file_delete_event';
const text = i18n.DELETED_FILE;
@@ -667,6 +1098,87 @@ describe('GenericRowRenderer', () => {
);
});
+ describe('#createEndpointRegistryRowRenderer', () => {
+ test('it renders an endpoint Registry Modification event', () => {
+ const actionName = 'modification';
+ const text = i18n.MODIFIED_REGISTRY_KEY;
+
+ const endpointRegistryModificationRowRenderer = createEndpointRegistryRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointRegistryModificationRowRenderer.isInstance(
+ mockEndpointRegistryModificationEvent
+ ) &&
+ endpointRegistryModificationRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointRegistryModificationEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1modified registry keySOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentStatewith new valueHKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValueviaGoogleUpdate.exe(7408)'
+ );
+ });
+ });
+
+ describe('#createEndpointLibraryRowRenderer', () => {
+ test('it renders an endpoint Library Load event', () => {
+ const actionName = 'load';
+ const text = i18n.LOADED_LIBRARY;
+
+ const endpointLibraryLoadRowRenderer = createEndpointLibraryRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointLibraryLoadRowRenderer.isInstance(mockEndpointLibraryLoadEvent) &&
+ endpointLibraryLoadRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointLibraryLoadEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1loaded librarybcrypt.dllinC:\\Windows\\System32\\bcrypt.dllviasshd.exe(9644)e70f5d8f87aab14e3160227d38387889befbe37fa4f8f5adc59eff52804b35fd'
+ );
+ });
+ });
+
+ test('it renders an Endpoint network HTTP Request event', () => {
+ const actionName = 'http_request';
+ const text = i18n.MADE_A_HTTP_REQUEST_VIA;
+
+ const endpointHttpRequestEventRowRenderer = createSocketRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointHttpRequestEventRowRenderer.isInstance(mockEndpointNetworkHttpRequestEvent) &&
+ endpointHttpRequestEventRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointNetworkHttpRequestEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(removeExternalLinkText(wrapper.text())).toEqual(
+ 'NETWORK SERVICE\\NT AUTHORITY@win2019-endpoint-1made a http request viasvchost.exe(2232)Endpoint network eventoutgoinghttptcpSource10.1.2.3:51570Destination10.11.12.13:80North AmericaUnited States🇺🇸USArizonaPhoenix'
+ );
+ });
+
test('it renders an Endgame ipv4_connection_accept_event', () => {
const actionName = 'ipv4_connection_accept_event';
const text = i18n.ACCEPTED_A_CONNECTION_VIA;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
index 69a6317ebcd1..211fa9152dc8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
@@ -15,6 +15,7 @@ import { RowRendererId } from '../../../../../../../common/types/timeline';
import { DnsRequestEventDetails } from '../dns/dns_request_event_details';
import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details';
import { isFileEvent, isNillEmptyOrNotFinite } from '../helpers';
+import { RegistryEventDetails } from '../registry/registry_event_details';
import { RowRenderer, RowRendererContainer } from '../row_renderer';
import { SystemGenericDetails } from './generic_details';
@@ -115,6 +116,86 @@ export const createFimRowRenderer = ({
),
});
+export interface EndpointAlertCriteria {
+ eventAction: string;
+ eventCategory: string;
+ eventType: string;
+ skipRedundantFileDetails?: boolean;
+ skipRedundantProcessDetails?: boolean;
+ text: string;
+}
+
+export const createEndpointAlertsRowRenderer = ({
+ eventAction,
+ eventCategory,
+ eventType,
+ skipRedundantFileDetails = false,
+ skipRedundantProcessDetails = false,
+ text,
+}: EndpointAlertCriteria): RowRenderer => ({
+ id: RowRendererId.alerts,
+ isInstance: (ecs) => {
+ const action: string[] | null | undefined = get('event.action', ecs);
+ const category: string[] | null | undefined = get('event.category', ecs);
+ const dataset: string | null | undefined = get('event.dataset[0]', ecs);
+ const type: string[] | null | undefined = get('event.type', ecs);
+
+ const eventActionMatches = action?.includes(eventAction) ?? false;
+ const eventCategoryMatches = category?.includes(eventCategory) ?? false;
+ const eventTypeMatches = type?.includes(eventType) ?? false;
+
+ return (
+ dataset?.toLowerCase() === 'endpoint.alerts' &&
+ eventTypeMatches &&
+ eventCategoryMatches &&
+ eventActionMatches
+ );
+ },
+ renderRow: ({ browserFields, data, timelineId }) => (
+
+
+
+ ),
+});
+
+export const createEndpointLibraryRowRenderer = ({
+ actionName,
+ text,
+}: {
+ actionName: string;
+ text: string;
+}): RowRenderer => ({
+ id: RowRendererId.library,
+ isInstance: (ecs) => {
+ const action: string | null | undefined = get('event.action[0]', ecs);
+ const dataset: string | null | undefined = get('event.dataset[0]', ecs);
+ return (
+ dataset?.toLowerCase() === 'endpoint.events.library' && action?.toLowerCase() === actionName
+ );
+ },
+ renderRow: ({ browserFields, data, timelineId }) => (
+
+
+
+ ),
+});
+
export const createGenericFileRowRenderer = ({
actionName,
text,
@@ -218,6 +299,34 @@ export const createDnsRowRenderer = (): RowRenderer => ({
),
});
+export const createEndpointRegistryRowRenderer = ({
+ actionName,
+ text,
+}: {
+ actionName: string;
+ text: string;
+}): RowRenderer => ({
+ id: RowRendererId.registry,
+ isInstance: (ecs) => {
+ const action: string | null | undefined = get('event.action[0]', ecs);
+ const dataset: string | null | undefined = get('event.dataset[0]', ecs);
+
+ return (
+ dataset?.toLowerCase() === 'endpoint.events.registry' && action?.toLowerCase() === actionName
+ );
+ },
+ renderRow: ({ browserFields, data, timelineId }) => (
+
+
+
+ ),
+});
+
const systemLoginRowRenderer = createGenericSystemRowRenderer({
actionName: 'user_login',
text: i18n.ATTEMPTED_LOGIN,
@@ -238,6 +347,11 @@ const endpointProcessStartRowRenderer = createEndgameProcessRowRenderer({
text: i18n.PROCESS_STARTED,
});
+const endpointRegistryModificationRowRenderer = createEndpointRegistryRowRenderer({
+ actionName: 'modification',
+ text: i18n.MODIFIED_REGISTRY_KEY,
+});
+
const systemProcessStoppedRowRenderer = createGenericFileRowRenderer({
actionName: 'process_stopped',
text: i18n.PROCESS_STOPPED,
@@ -278,6 +392,21 @@ const endpointFileDeletionEventRowRenderer = createFimRowRenderer({
text: i18n.DELETED_FILE,
});
+const endpointModificationEventRowRenderer = createFimRowRenderer({
+ actionName: 'modification',
+ text: i18n.MODIFIED_FILE,
+});
+
+const endpointFileOverwriteEventRowRenderer = createFimRowRenderer({
+ actionName: 'overwrite',
+ text: i18n.OVERWROTE_FILE,
+});
+
+const endpointFileRenamedEventRowRenderer = createFimRowRenderer({
+ actionName: 'rename',
+ text: i18n.RENAMED_FILE,
+});
+
const fimFileDeletedEventRowRenderer = createFimRowRenderer({
actionName: 'deleted',
text: i18n.DELETED_FILE,
@@ -288,6 +417,88 @@ const systemExistingRowRenderer = createGenericFileRowRenderer({
text: i18n.EXISTING_PROCESS,
});
+const endpointAlertCriteria: EndpointAlertCriteria[] = [
+ {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_CREATING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_CREATING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_PREVENTED_FROM_ENCRYPTING_FILES,
+ },
+ {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_DETECTED_ENCRYPTING_FILES,
+ },
+ {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_MODIFYING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_MODIFYING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_RENAMING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_RENAMING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS,
+ },
+ {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_DETECTED_EXECUTING_A_MALICIOUS_PROCESS,
+ },
+];
+
+const endpointAlertsRowRenderers: RowRenderer[] = endpointAlertCriteria.map((criteria) =>
+ createEndpointAlertsRowRenderer(criteria)
+);
+
+const endpointLibraryLoadRowRenderer = createEndpointLibraryRowRenderer({
+ actionName: 'load',
+ text: i18n.LOADED_LIBRARY,
+});
+
const systemSocketOpenedRowRenderer = createSocketRowRenderer({
actionName: 'socket_opened',
text: i18n.SOCKET_OPENED,
@@ -308,6 +519,21 @@ const endpointConnectionAcceptedEventRowRenderer = createSocketRowRenderer({
text: i18n.ACCEPTED_A_CONNECTION_VIA,
});
+const endpointHttpRequestEventRowRenderer = createSocketRowRenderer({
+ actionName: 'http_request',
+ text: i18n.MADE_A_HTTP_REQUEST_VIA,
+});
+
+const endpointProcessExecRowRenderer = createEndgameProcessRowRenderer({
+ actionName: 'exec',
+ text: i18n.EXECUTED_PROCESS,
+});
+
+const endpointProcessForkRowRenderer = createEndgameProcessRowRenderer({
+ actionName: 'fork',
+ text: i18n.FORKED_PROCESS,
+});
+
const endgameIpv6ConnectionAcceptEventRowRenderer = createSocketRowRenderer({
actionName: 'ipv6_connection_accept_event',
text: i18n.ACCEPTED_A_CONNECTION_VIA,
@@ -448,8 +674,17 @@ export const systemRowRenderers: RowRenderer[] = [
endpointFileCreationEventRowRenderer,
endgameFileDeleteEventRowRenderer,
endpointFileDeletionEventRowRenderer,
+ endpointFileOverwriteEventRowRenderer,
+ endpointFileRenamedEventRowRenderer,
+ ...endpointAlertsRowRenderers,
+ endpointLibraryLoadRowRenderer,
+ endpointModificationEventRowRenderer,
+ endpointRegistryModificationRowRenderer,
endgameIpv4ConnectionAcceptEventRowRenderer,
endpointConnectionAcceptedEventRowRenderer,
+ endpointHttpRequestEventRowRenderer,
+ endpointProcessExecRowRenderer,
+ endpointProcessForkRowRenderer,
endgameIpv6ConnectionAcceptEventRowRenderer,
endgameIpv4DisconnectReceivedEventRowRenderer,
endpointDisconnectReceivedEventRowRenderer,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts
index e007746ea5cc..266579fbc228 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts
@@ -91,6 +91,62 @@ export const DELETED_FILE = i18n.translate('xpack.securitySolution.system.delete
defaultMessage: 'deleted a file',
});
+export const EXECUTED_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.executedProcessDescription',
+ {
+ defaultMessage: 'executed process',
+ }
+);
+
+export const FORKED_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.forkedProcessDescription',
+ {
+ defaultMessage: 'forked process',
+ }
+);
+
+export const LOADED_LIBRARY = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.loadedLibraryDescription',
+ {
+ defaultMessage: 'loaded library',
+ }
+);
+
+export const MADE_A_HTTP_REQUEST_VIA = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.madeAHttpRequestViaDescription',
+ {
+ defaultMessage: 'made a http request via',
+ }
+);
+
+export const MODIFIED_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.modifiedFileDescription',
+ {
+ defaultMessage: 'modified a file',
+ }
+);
+
+export const MODIFIED_REGISTRY_KEY = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.modifiedRegistryKeyDescription',
+ {
+ defaultMessage: 'modified registry key',
+ }
+);
+
+export const OVERWROTE_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.overwroteFileDescription',
+ {
+ defaultMessage: 'overwrote a file',
+ }
+);
+
+export const RENAMED_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.renamedFileDescription',
+ {
+ defaultMessage: 'renamed a file',
+ }
+);
+
export const EXISTING_PROCESS = i18n.translate(
'xpack.securitySolution.system.existingProcessDescription',
{
@@ -207,6 +263,76 @@ export const VIA_PARENT_PROCESS = i18n.translate(
}
);
+export const RANSOMWARE_WAS_PREVENTED_FROM_ENCRYPTING_FILES = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.ransomwareWasPreventedFromeEcryptingFilesDescription',
+ {
+ defaultMessage: 'ransomware was prevented from encrypting files',
+ }
+);
+
+export const RANSOMWARE_WAS_DETECTED_ENCRYPTING_FILES = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.ransomwareWasDetectedEcryptingFilesDescription',
+ {
+ defaultMessage: 'ransomware was detected encrypting files',
+ }
+);
+
+export const WAS_DETECTED_CREATING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedCreatingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was detected creating a malicious file',
+ }
+);
+
+export const WAS_PREVENTED_FROM_CREATING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromCreatingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was prevented from creating a malicious file',
+ }
+);
+
+export const WAS_DETECTED_MODIFYING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedModifyingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was detected modifying a malicious file',
+ }
+);
+
+export const WAS_PREVENTED_FROM_MODIFYING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromModifyingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was prevented from modifying a malicious file',
+ }
+);
+
+export const WAS_DETECTED_RENAMING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedRenamingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was detected renaming a malicious file',
+ }
+);
+
+export const WAS_PREVENTED_FROM_RENAMING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromRenamingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was prevented from renaming a malicious file',
+ }
+);
+
+export const WAS_DETECTED_EXECUTING_A_MALICIOUS_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedExecutingAMaliciousProcessDescription',
+ {
+ defaultMessage: 'was detected executing a malicious process',
+ }
+);
+
+export const WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromExecutingAMaliciousProcessDescription',
+ {
+ defaultMessage: 'was prevented from executing a malicious process',
+ }
+);
+
export const WITH_EXIT_CODE = i18n.translate(
'xpack.securitySolution.system.withExitCodeDescription',
{
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
index 132501a33d72..476fd77ce064 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
@@ -15,6 +15,13 @@ export const DESTINATION = i18n.translate('xpack.securitySolution.timeline.desti
defaultMessage: 'Destination',
});
+export const FROM_ITS_ORIGINAL_PATH = i18n.translate(
+ 'xpack.securitySolution.timeline.file.fromOriginalPathDescription',
+ {
+ defaultMessage: 'from its original path',
+ }
+);
+
export const PROTOCOL = i18n.translate('xpack.securitySolution.timeline.protocol', {
defaultMessage: 'Protocol',
});
diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts
index 41523c7869db..79da343cb273 100644
--- a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts
+++ b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts
@@ -144,10 +144,13 @@ export const timelineSchema = gql`
}
enum RowRendererId {
+ alerts
auditd
auditd_file
+ library
netflow
plain
+ registry
suricata
system
system_dns
diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts
index 0d6a0e63455b..c8f8b0f63445 100644
--- a/x-pack/plugins/security_solution/server/graphql/types.ts
+++ b/x-pack/plugins/security_solution/server/graphql/types.ts
@@ -289,10 +289,13 @@ export enum DataProviderType {
}
export enum RowRendererId {
+ alerts = 'alerts',
auditd = 'auditd',
auditd_file = 'auditd_file',
+ library = 'library',
netflow = 'netflow',
plain = 'plain',
+ registry = 'registry',
suricata = 'suricata',
system = 'system',
system_dns = 'system_dns',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts
index 5e9391df5b8a..5ed324496e60 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts
@@ -70,6 +70,7 @@ export const TIMELINE_EVENTS_FIELDS = [
'auditd.summary.how',
'auditd.summary.message_type',
'auditd.summary.sequence',
+ 'file.Ext.original.path',
'file.name',
'file.target_path',
'file.extension',
@@ -95,6 +96,8 @@ export const TIMELINE_EVENTS_FIELDS = [
'host.os.family',
'host.id',
'host.ip',
+ 'registry.key',
+ 'registry.path',
'rule.reference',
'source.bytes',
'source.packets',
From 8ce6ed42a39f6020ec1ba247e50fe158b42dc6bf Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Tue, 16 Feb 2021 20:58:52 -0700
Subject: [PATCH 19/23] [Security Solutions][Detection Engine] Adds a warning
banner when the alerts data has not been migrated yet. (#90258)
## Summary
Adds a warning banner for when the alerting/signals data has not been migrated to the new structure. Although we are planning on supporting some backwards compatibility where the rules don't completely blow up, this support of backwards compatibility is going to be best effort and not have explicit tests and checks against backwards compatibility. Hence the reason we need to alert any users of the system when we can that they should have an administrator visit the detections page to start a migration.
From previous reasons why we don't migrate on startup of Kibana is that there are multiple instances running and it might be a worse situation so we migrate on page visit by an administrator to reduce chances of issues. In the future we might revisit this decision but for now this is what we have moved forward with.
If the user does not have sufficient privileges such as t1 analyst to see if they have should upgrade, no message is shown to those users.
This PR adds the following banner which is non-dismissible to:
* Main detections page
* Manage rules page
* View/Edit rules page
If other dismissible alerts are on the page then you will get a stacked effect until you dismiss those messages. Again, this message cannot be dismissed intentionally to let the user know that they should contact an administrator to update/upgrade the alerting/signal data:
Other items of note:
* Added ability to remove the button from the callouts
* Consolidated in one area some types
* Removed one part of the callout that has branching logic we never activate. We can re-add that later if we do have a need for it
* e2e Cypress tests added to detect when the banner should be present
* Backfilled unit tests for enzyme for some of the callout code
Manual testing:
Bump this number in your dev env:
https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts#L11
Give yourself these permissions (or use one of the scripts for creating these roles):
Visit the page.
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---
.../security_solution/common/utility_types.ts | 6 +
..._detection_callouts_index_outdated.spec.ts | 196 ++++++++++++++++++
...alerts_detection_callouts_readonly.spec.ts | 147 ++++++++-----
.../cypress/tasks/common/callouts.ts | 6 +-
.../components/callouts/callout.test.tsx | 114 ++++++++++
.../common/components/callouts/callout.tsx | 14 +-
.../callouts/callout_description.tsx | 26 ---
.../callouts/callout_persistent_switcher.tsx | 24 +++
.../components/callouts/callout_types.ts | 4 +-
.../common/components/callouts/index.ts | 1 +
.../index.test.tsx | 195 +++++++++++++++++
.../need_admin_for_update_callout/index.tsx | 52 +++++
.../translations.tsx | 52 +++++
.../detection_engine/detection_engine.tsx | 2 +
.../detection_engine/rules/details/index.tsx | 2 +
.../pages/detection_engine/rules/index.tsx | 2 +
.../factory/hosts/all/query.all_hosts.dsl.ts | 2 +-
17 files changed, 759 insertions(+), 86 deletions(-)
create mode 100644 x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
create mode 100644 x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx
diff --git a/x-pack/plugins/security_solution/common/utility_types.ts b/x-pack/plugins/security_solution/common/utility_types.ts
index 3c13e6af837b..498b18dccaca 100644
--- a/x-pack/plugins/security_solution/common/utility_types.ts
+++ b/x-pack/plugins/security_solution/common/utility_types.ts
@@ -36,6 +36,12 @@ export const stringEnum = (enumObj: T, enumName = 'enum') =>
*
* Optionally you can avoid the use of this by using early returns and TypeScript will clear your type checking without complaints
* but there are situations and times where this function might still be needed.
+ *
+ * If you see an error, DO NOT cast "as never" such as:
+ * assertUnreachable(x as never) // BUG IN YOUR CODE NOW AND IT WILL THROW DURING RUNTIME
+ * If you see code like that remove it, as that deactivates the intent of this utility.
+ * If you need to do that, then you should remove assertUnreachable from your code and
+ * use a default at the end of the switch instead.
* @param x Unreachable field
* @param message Message of error thrown
*/
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
new file mode 100644
index 000000000000..1c6c604b84fb
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
@@ -0,0 +1,196 @@
+/*
+ * 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 { ROLES } from '../../../common/test';
+import { DETECTIONS_RULE_MANAGEMENT_URL, DETECTIONS_URL } from '../../urls/navigation';
+import { newRule } from '../../objects/rule';
+import { PAGE_TITLE } from '../../screens/common/page';
+
+import {
+ login,
+ loginAndWaitForPageWithoutDateRange,
+ waitForPageWithoutDateRange,
+} from '../../tasks/login';
+import { waitForAlertsIndexToBeCreated } from '../../tasks/alerts';
+import { goToRuleDetails } from '../../tasks/alerts_detection_rules';
+import { createCustomRule, deleteCustomRule } from '../../tasks/api_calls/rules';
+import { getCallOut, waitForCallOutToBeShown } from '../../tasks/common/callouts';
+import { cleanKibana } from '../../tasks/common';
+
+const loadPageAsPlatformEngineerUser = (url: string) => {
+ waitForPageWithoutDateRange(url, ROLES.soc_manager);
+ waitForPageTitleToBeShown();
+};
+
+const waitForPageTitleToBeShown = () => {
+ cy.get(PAGE_TITLE).should('be.visible');
+};
+
+describe('Detections > Need Admin Callouts indicating an admin is needed to migrate the alert data set', () => {
+ const NEED_ADMIN_FOR_UPDATE_CALLOUT = 'need-admin-for-update-rules';
+
+ before(() => {
+ // First, we have to open the app on behalf of a privileged user in order to initialize it.
+ // Otherwise the app will be disabled and show a "welcome"-like page.
+ cleanKibana();
+ loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer);
+ waitForAlertsIndexToBeCreated();
+
+ // After that we can login as a soc manager.
+ login(ROLES.soc_manager);
+ });
+
+ context(
+ 'The users index_mapping_outdated is "true" and their admin callouts should show up',
+ () => {
+ beforeEach(() => {
+ // Index mapping outdated is forced to return true as being outdated so that we get the
+ // need admin callouts being shown.
+ cy.intercept('GET', '/api/detection_engine/index', {
+ index_mapping_outdated: true,
+ name: '.siem-signals-default',
+ });
+ });
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_URL);
+ });
+
+ it('We show the need admin primary callout', () => {
+ waitForCallOutToBeShown(NEED_ADMIN_FOR_UPDATE_CALLOUT, 'primary');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show 1 primary callout of need admin', () => {
+ waitForCallOutToBeShown(NEED_ADMIN_FOR_UPDATE_CALLOUT, 'primary');
+ });
+ });
+
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+
+ it('We show 1 primary callout', () => {
+ waitForCallOutToBeShown(NEED_ADMIN_FOR_UPDATE_CALLOUT, 'primary');
+ });
+ });
+ }
+ );
+
+ context(
+ 'The users index_mapping_outdated is "false" and their admin callouts should not show up ',
+ () => {
+ beforeEach(() => {
+ // Index mapping outdated is forced to return true as being outdated so that we get the
+ // need admin callouts being shown.
+ cy.intercept('GET', '/api/detection_engine/index', {
+ index_mapping_outdated: false,
+ name: '.siem-signals-default',
+ });
+ });
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_URL);
+ });
+
+ it('We show the need admin primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show 1 primary callout of need admin', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+
+ it('We show 1 primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+ }
+ );
+
+ context(
+ 'The users index_mapping_outdated is "null" and their admin callouts should not show up ',
+ () => {
+ beforeEach(() => {
+ // Index mapping outdated is forced to return true as being outdated so that we get the
+ // need admin callouts being shown.
+ cy.intercept('GET', '/api/detection_engine/index', {
+ index_mapping_outdated: null,
+ name: '.siem-signals-default',
+ });
+ });
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_URL);
+ });
+
+ it('We show the need admin primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show 1 primary callout of need admin', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+
+ it('We show 1 primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+ }
+ );
+});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts
index 85257f7d9176..d807857cd72b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts
@@ -26,6 +26,11 @@ const loadPageAsReadOnlyUser = (url: string) => {
waitForPageTitleToBeShown();
};
+const loadPageAsPlatformEngineer = (url: string) => {
+ waitForPageWithoutDateRange(url, ROLES.platform_engineer);
+ waitForPageTitleToBeShown();
+};
+
const reloadPage = () => {
cy.reload();
waitForPageTitleToBeShown();
@@ -35,7 +40,7 @@ const waitForPageTitleToBeShown = () => {
cy.get(PAGE_TITLE).should('be.visible');
};
-describe('Detections > Callouts indicating read-only access to resources', () => {
+describe('Detections > Callouts', () => {
const ALERTS_CALLOUT = 'read-only-access-to-alerts';
const RULES_CALLOUT = 'read-only-access-to-rules';
@@ -50,75 +55,119 @@ describe('Detections > Callouts indicating read-only access to resources', () =>
login(ROLES.reader);
});
- context('On Detections home page', () => {
- beforeEach(() => {
- loadPageAsReadOnlyUser(DETECTIONS_URL);
- });
-
- it('We show one primary callout', () => {
- waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
- });
+ context('indicating read-only access to resources', () => {
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsReadOnlyUser(DETECTIONS_URL);
+ });
- context('When a user clicks Dismiss on the callout', () => {
- it('We hide it and persist the dismissal', () => {
+ it('We show one primary callout', () => {
waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
- dismissCallOut(ALERTS_CALLOUT);
- reloadPage();
- getCallOut(ALERTS_CALLOUT).should('not.exist');
});
- });
- });
- context('On Rules Management page', () => {
- beforeEach(() => {
- loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ context('When a user clicks Dismiss on the callout', () => {
+ it('We hide it and persist the dismissal', () => {
+ waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
+ dismissCallOut(ALERTS_CALLOUT);
+ reloadPage();
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ });
+ });
});
- it('We show one primary callout', () => {
- waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
- });
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
- context('When a user clicks Dismiss on the callout', () => {
- it('We hide it and persist the dismissal', () => {
+ it('We show one primary callout', () => {
waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
- dismissCallOut(RULES_CALLOUT);
- reloadPage();
- getCallOut(RULES_CALLOUT).should('not.exist');
});
- });
- });
- context('On Rule Details page', () => {
- beforeEach(() => {
- createCustomRule(newRule);
- loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
- waitForPageTitleToBeShown();
- goToRuleDetails();
+ context('When a user clicks Dismiss on the callout', () => {
+ it('We hide it and persist the dismissal', () => {
+ waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
+ dismissCallOut(RULES_CALLOUT);
+ reloadPage();
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
});
- afterEach(() => {
- deleteCustomRule();
- });
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
- it('We show two primary callouts', () => {
- waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
- waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
- });
+ afterEach(() => {
+ deleteCustomRule();
+ });
- context('When a user clicks Dismiss on the callouts', () => {
- it('We hide them and persist the dismissal', () => {
+ it('We show two primary callouts', () => {
waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
+ });
- dismissCallOut(ALERTS_CALLOUT);
- reloadPage();
+ context('When a user clicks Dismiss on the callouts', () => {
+ it('We hide them and persist the dismissal', () => {
+ waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
+ waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
+ dismissCallOut(ALERTS_CALLOUT);
+ reloadPage();
+
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ getCallOut(RULES_CALLOUT).should('be.visible');
+
+ dismissCallOut(RULES_CALLOUT);
+ reloadPage();
+
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
+ });
+ });
+
+ context('indicating read-write access to resources', () => {
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineer(DETECTIONS_URL);
+ });
+
+ it('We show no callout', () => {
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show no callout', () => {
getCallOut(ALERTS_CALLOUT).should('not.exist');
- getCallOut(RULES_CALLOUT).should('be.visible');
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
- dismissCallOut(RULES_CALLOUT);
- reloadPage();
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+ it('We show no callouts', () => {
getCallOut(ALERTS_CALLOUT).should('not.exist');
getCallOut(RULES_CALLOUT).should('not.exist');
});
diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts b/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts
index 4139c911e406..8440409f80f3 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts
@@ -12,13 +12,11 @@ export const getCallOut = (id: string, options?: Cypress.Timeoutable) => {
};
export const waitForCallOutToBeShown = (id: string, color: string) => {
- getCallOut(id, { timeout: 10000 })
- .should('be.visible')
- .should('have.class', `euiCallOut--${color}`);
+ getCallOut(id).should('be.visible').should('have.class', `euiCallOut--${color}`);
};
export const dismissCallOut = (id: string) => {
- getCallOut(id, { timeout: 10000 }).within(() => {
+ getCallOut(id).within(() => {
cy.get(CALLOUT_DISMISS_BTN).should('be.visible').click();
cy.root().should('not.exist');
});
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx
new file mode 100644
index 000000000000..f908a79361d0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx
@@ -0,0 +1,114 @@
+/*
+ * 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 { mount } from 'enzyme';
+import React from 'react';
+import { TestProviders } from '../../mock';
+import { CallOut } from './callout';
+import { CallOutMessage } from './callout_types';
+
+describe('callout', () => {
+ let message: CallOutMessage = {
+ type: 'primary',
+ id: 'some-id',
+ title: 'title',
+ description: <>{'some description'}>,
+ };
+
+ beforeEach(() => {
+ message = {
+ type: 'primary',
+ id: 'some-id',
+ title: 'title',
+ description: <>{'some description'}>,
+ };
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ test('renders the callout data-test-subj from the given id', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-some-id"]')).toEqual(true);
+ });
+
+ test('renders the callout dismiss button by default', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(true);
+ });
+
+ test('renders the callout dismiss button if given an explicit true to enable it', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(true);
+ });
+
+ test('Does NOT render the callout dismiss button if given an explicit false to disable it', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('onDismiss callback operates when dismiss button is clicked', () => {
+ const onDismiss = jest.fn();
+ const wrapper = mount(
+
+
+
+ );
+ wrapper.find('[data-test-subj="callout-dismiss-btn"]').first().simulate('click');
+ expect(onDismiss).toBeCalledWith(message);
+ });
+
+ test('dismissButtonText can be set', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="callout-dismiss-btn"]').first().text()).toEqual(
+ 'Some other text'
+ );
+ });
+
+ test('a default icon type of "iInCircle" will be chosen if no iconType is set and the message type is "primary"', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="callout-some-id"]').first().prop('iconType')).toEqual(
+ 'iInCircle'
+ );
+ });
+
+ test('icon type can be changed from the type within the message', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="callout-some-id"]').first().prop('iconType')).toEqual(
+ 'something_else'
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx
index f6e0c89cab26..2077e421c427 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx
@@ -8,8 +8,8 @@
import React, { FC, memo } from 'react';
import { EuiCallOut } from '@elastic/eui';
+import { assertUnreachable } from '../../../../common/utility_types';
import { CallOutType, CallOutMessage } from './callout_types';
-import { CallOutDescription } from './callout_description';
import { CallOutDismissButton } from './callout_dismiss_button';
export interface CallOutProps {
@@ -17,6 +17,7 @@ export interface CallOutProps {
iconType?: string;
dismissButtonText?: string;
onDismiss?: (message: CallOutMessage) => void;
+ showDismissButton?: boolean;
}
const CallOutComponent: FC = ({
@@ -24,8 +25,9 @@ const CallOutComponent: FC = ({
iconType,
dismissButtonText,
onDismiss,
+ showDismissButton = true,
}) => {
- const { type, id, title } = message;
+ const { type, id, title, description } = message;
const finalIconType = iconType ?? getDefaultIconType(type);
return (
@@ -36,8 +38,10 @@ const CallOutComponent: FC = ({
data-test-subj={`callout-${id}`}
data-test-messages={`[${id}]`}
>
-
-
+ {description}
+ {showDismissButton && (
+
+ )}
);
};
@@ -53,7 +57,7 @@ const getDefaultIconType = (type: CallOutType): string => {
case 'danger':
return 'alert';
default:
- return '';
+ return assertUnreachable(type);
}
};
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx
deleted file mode 100644
index dbb1267c7332..000000000000
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx
+++ /dev/null
@@ -1,26 +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, { FC } from 'react';
-import { EuiDescriptionList } from '@elastic/eui';
-import { CallOutMessage } from './callout_types';
-
-export interface CallOutDescriptionProps {
- messages: CallOutMessage | CallOutMessage[];
-}
-
-export const CallOutDescription: FC = ({ messages }) => {
- if (!Array.isArray(messages)) {
- return messages.description;
- }
-
- if (messages.length < 1) {
- return null;
- }
-
- return ;
-};
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx
new file mode 100644
index 000000000000..5b67410bb904
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { FC, memo } from 'react';
+
+import { CallOutMessage } from './callout_types';
+import { CallOut } from './callout';
+
+export interface CallOutPersistentSwitcherProps {
+ condition: boolean;
+ message: CallOutMessage;
+}
+
+const CallOutPersistentSwitcherComponent: FC = ({
+ condition,
+ message,
+}): JSX.Element | null =>
+ condition ? : null;
+
+export const CallOutPersistentSwitcher = memo(CallOutPersistentSwitcherComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts b/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts
index 604f7b3e61c7..e04638a57ad0 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts
@@ -5,7 +5,9 @@
* 2.0.
*/
-export type CallOutType = 'primary' | 'success' | 'warning' | 'danger';
+import { EuiCallOutProps } from '@elastic/eui';
+
+export type CallOutType = NonNullable;
export interface CallOutMessage {
type: CallOutType;
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/index.ts b/x-pack/plugins/security_solution/public/common/components/callouts/index.ts
index 222bf5daee6f..0b7ec42744a6 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/index.ts
@@ -8,3 +8,4 @@
export * from './callout_switcher';
export * from './callout_types';
export * from './callout';
+export * from './callout_persistent_switcher';
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx
new file mode 100644
index 000000000000..66b2bae98c1a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx
@@ -0,0 +1,195 @@
+/*
+ * 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 { mount } from 'enzyme';
+import React from 'react';
+import { NeedAdminForUpdateRulesCallOut } from './index';
+import { TestProviders } from '../../../../common/mock';
+import * as userInfo from '../../user_info';
+
+describe('need_admin_for_update_callout', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('hasIndexManage is "null"', () => {
+ const hasIndexManage = null;
+ test('Does NOT render when "signalIndexMappingOutdated" is true', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(
+ jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true, hasIndexManage }])
+ );
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does not render a button as this is always persistent', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is false', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: false }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is null', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: null }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+ });
+
+ describe('hasIndexManage is "false"', () => {
+ const hasIndexManage = false;
+ test('renders when "signalIndexMappingOutdated" is true', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(
+ jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true, hasIndexManage }])
+ );
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ true
+ );
+ });
+
+ test('Does not render a button as this is always persistent', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is false', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: false }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is null', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: null }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+ });
+
+ describe('hasIndexManage is "true"', () => {
+ const hasIndexManage = true;
+ test('Does not render when "signalIndexMappingOutdated" is true', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(
+ jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true, hasIndexManage }])
+ );
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does not render a button as this is always persistent', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is false', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: false }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is null', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: null }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx
new file mode 100644
index 000000000000..fd0be8e00219
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { memo } from 'react';
+import { CallOutMessage, CallOutPersistentSwitcher } from '../../../../common/components/callouts';
+import { useUserData } from '../../user_info';
+
+import * as i18n from './translations';
+
+const needAdminForUpdateRulesMessage: CallOutMessage = {
+ type: 'primary',
+ id: 'need-admin-for-update-rules',
+ title: i18n.NEED_ADMIN_CALLOUT_TITLE,
+ description: i18n.needAdminForUpdateCallOutBody(),
+};
+
+/**
+ * Callout component that lets the user know that an administrator is needed for performing
+ * and auto-update of signals or not. For this component to render the user must:
+ * - Have the permissions to be able to read "signalIndexMappingOutdated" and that condition is "true"
+ * - Have the permissions to be able to read "hasIndexManage" and that condition is "false"
+ *
+ * Some users do not have sufficient privileges to be able to determine if "signalIndexMappingOutdated"
+ * is outdated or not. Same could apply to "hasIndexManage". When users do not have enough permissions
+ * to determine if "signalIndexMappingOutdated" is true or false, the permissions system returns a "null"
+ * instead.
+ *
+ * If the user has the permissions to see that signalIndexMappingOutdated is true and that
+ * hasIndexManage is also true, then the user should be performing the update on the page which is
+ * why we do not show it for that condition.
+ */
+const NeedAdminForUpdateCallOutComponent = (): JSX.Element => {
+ const [{ signalIndexMappingOutdated, hasIndexManage }] = useUserData();
+
+ const signalIndexMappingIsOutdated =
+ signalIndexMappingOutdated != null && signalIndexMappingOutdated;
+
+ const userDoesntHaveIndexManage = hasIndexManage != null && !hasIndexManage;
+
+ return (
+
+ );
+};
+
+export const NeedAdminForUpdateRulesCallOut = memo(NeedAdminForUpdateCallOutComponent);
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx
new file mode 100644
index 000000000000..791093788b8e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ SecuritySolutionRequirementsLink,
+ DetectionsRequirementsLink,
+} from '../../../../common/components/links_to_docs';
+
+export const NEED_ADMIN_CALLOUT_TITLE = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.needAdminForUpdateCallOutBody.messageTitle',
+ {
+ defaultMessage: 'Administration permissions required for alert migration',
+ }
+);
+
+/**
+ * Returns the formatted message of the call out body as a JSX Element with both the message
+ * and two documentation links.
+ */
+export const needAdminForUpdateCallOutBody = (): JSX.Element => (
+
+
+
+ ),
+ docs: (
+
+ ),
+ }}
+ />
+);
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
index 0b3511ffe7c8..8d2f07e19b36 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
@@ -53,6 +53,7 @@ import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
+import { NeedAdminForUpdateRulesCallOut } from '../../components/callouts/need_admin_for_update_callout';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -193,6 +194,7 @@ const DetectionEnginePageComponent = () => {
<>
{hasEncryptionKey != null && !hasEncryptionKey && }
+
{indicesExist ? (
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
index ed88ca41146f..c4dc9b62c74c 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
@@ -103,6 +103,7 @@ import * as detectionI18n from '../../translations';
import * as ruleI18n from '../translations';
import * as i18n from './translations';
import { isTab } from '../../../../../common/components/accessibility/helpers';
+import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -468,6 +469,7 @@ const RuleDetailsPageComponent = () => {
return (
<>
+
{indicesExist ? (
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
index fee7f443e95a..89cec1685101 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
@@ -35,6 +35,7 @@ import * as i18n from './translations';
import { SecurityPageName } from '../../../../app/types';
import { LinkButton } from '../../../../common/components/links';
import { useFormatUrl } from '../../../../common/components/link_to';
+import { NeedAdminForUpdateRulesCallOut } from '../../../components/callouts/need_admin_for_update_callout';
type Func = () => Promise;
@@ -158,6 +159,7 @@ const RulesPageComponent: React.FC = () => {
return (
<>
+
): QueryOrder => {
case HostsFields.hostName:
return { _key: sort.direction };
default:
- return assertUnreachable(sort.field as never);
+ return assertUnreachable(sort.field);
}
};
From d7d2b15cdbbf13454753b62906a2d4ea1ddd8af3 Mon Sep 17 00:00:00 2001
From: Oliver Gupte
Date: Tue, 16 Feb 2021 23:21:47 -0500
Subject: [PATCH 20/23] [APM] Correlations Beta (#86477) (#89952)
* [APM] Correlations GA (#86477)
* polish and improvements to correlations UI
* more improvements and polish
* added impact bar
* added descriptions
* make custom field persistence be unique per service
* make custom threshold unique per service in latency correlations
* adds telemetry for apm correlations feature. Events:
- 'show_correlations_flyout'
- 'customize_correlations_fields'
- 'select_significant_term'
* adds more telemetry for correlations (#90622)
* removes the raw score column
* replaces experiemental callout with beta badge
* replaces threshold number input with percentile option selector
* improvements to latency correlations scoring and percentage reporting
* removes the 'apm:enableCorrelations' UI setting
* - rename useFieldNames.ts -> use_field_names.ts
- filter out fields that are not type 'keyword'
- feedback improvements
* Fixes casing issue for the 'correlations' dir
* [APM] Moves correlations button to service details tabslist row (#91080)
* [APM] Adds license check for correlations (#90766)
* [APM] Adds metrics tracking for correlations views and license prompts (#90622)
* Updated the API integration tests to check for new default fields and 15 buckets
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../server/collectors/management/schema.ts | 1 -
.../server/collectors/management/types.ts | 1 -
src/plugins/telemetry/schema/oss_plugins.json | 3 -
x-pack/plugins/apm/common/ui_settings_keys.ts | 1 -
.../components/app/Correlations/index.tsx | 108 ----------
.../correlations_table.tsx} | 117 ++++++++---
.../app/correlations/custom_fields.tsx | 165 +++++++++++++++
.../error_correlations.tsx} | 105 ++++++----
.../components/app/correlations/index.tsx | 191 ++++++++++++++++++
.../latency_correlations.tsx} | 145 +++++++------
.../app/correlations/use_field_names.ts | 74 +++++++
.../service_details/service_detail_tabs.tsx | 4 +
.../components/app/service_overview/index.tsx | 6 +-
.../components/app/trace_overview/index.tsx | 10 +-
.../app/transaction_details/index.tsx | 10 +-
.../app/transaction_overview/index.tsx | 6 +-
.../public/components/shared/search_bar.tsx | 8 +-
.../index.ts | 22 +-
.../get_latency_distribution.ts | 19 +-
.../index.ts | 37 +++-
.../process_significant_term_aggs.ts | 42 +++-
x-pack/plugins/apm/server/ui_settings.ts | 19 +-
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
.../tests/correlations/slow_transactions.ts | 48 +++--
25 files changed, 795 insertions(+), 351 deletions(-)
delete mode 100644 x-pack/plugins/apm/public/components/app/Correlations/index.tsx
rename x-pack/plugins/apm/public/components/app/{Correlations/SignificantTermsTable.tsx => correlations/correlations_table.tsx} (51%)
create mode 100644 x-pack/plugins/apm/public/components/app/correlations/custom_fields.tsx
rename x-pack/plugins/apm/public/components/app/{Correlations/ErrorCorrelations.tsx => correlations/error_correlations.tsx} (61%)
create mode 100644 x-pack/plugins/apm/public/components/app/correlations/index.tsx
rename x-pack/plugins/apm/public/components/app/{Correlations/LatencyCorrelations.tsx => correlations/latency_correlations.tsx} (62%)
create mode 100644 x-pack/plugins/apm/public/components/app/correlations/use_field_names.ts
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index 5d611c75cdb9..d632c3ad61a8 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -127,5 +127,4 @@ export const stackManagementSchema: MakeSchemaFrom = {
'securitySolution:rulesTableRefresh': { type: 'text' },
'apm:enableSignificantTerms': { type: 'boolean' },
'apm:enableServiceOverview': { type: 'boolean' },
- 'apm:enableCorrelations': { type: 'boolean' },
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index ee602fd51c32..698e7e711529 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -30,7 +30,6 @@ export interface UsageStats {
'securitySolution:rulesTableRefresh': string;
'apm:enableSignificantTerms': boolean;
'apm:enableServiceOverview': boolean;
- 'apm:enableCorrelations': boolean;
'visualize:enableLabs': boolean;
'visualization:heatmap:maxBuckets': number;
'visualization:colorMapping': string;
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index c129fd006ae1..566d10182b54 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -4384,9 +4384,6 @@
},
"apm:enableServiceOverview": {
"type": "boolean"
- },
- "apm:enableCorrelations": {
- "type": "boolean"
}
}
},
diff --git a/x-pack/plugins/apm/common/ui_settings_keys.ts b/x-pack/plugins/apm/common/ui_settings_keys.ts
index 83d358068905..427c30605e71 100644
--- a/x-pack/plugins/apm/common/ui_settings_keys.ts
+++ b/x-pack/plugins/apm/common/ui_settings_keys.ts
@@ -5,5 +5,4 @@
* 2.0.
*/
-export const enableCorrelations = 'apm:enableCorrelations';
export const enableServiceOverview = 'apm:enableServiceOverview';
diff --git a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx
deleted file mode 100644
index c5b2f265fac8..000000000000
--- a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useState } from 'react';
-import {
- EuiButtonEmpty,
- EuiFlyout,
- EuiFlyoutBody,
- EuiFlyoutHeader,
- EuiTitle,
- EuiPortal,
- EuiCode,
- EuiLink,
- EuiCallOut,
- EuiButton,
-} from '@elastic/eui';
-import { useHistory } from 'react-router-dom';
-import { EuiSpacer } from '@elastic/eui';
-import { isActivePlatinumLicense } from '../../../../common/license_check';
-import { enableCorrelations } from '../../../../common/ui_settings_keys';
-import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
-import { LatencyCorrelations } from './LatencyCorrelations';
-import { ErrorCorrelations } from './ErrorCorrelations';
-import { useUrlParams } from '../../../context/url_params_context/use_url_params';
-import { createHref } from '../../shared/Links/url_helpers';
-import { useLicenseContext } from '../../../context/license/use_license_context';
-
-export function Correlations() {
- const { uiSettings } = useApmPluginContext().core;
- const { urlParams } = useUrlParams();
- const license = useLicenseContext();
- const history = useHistory();
- const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
- if (
- !uiSettings.get(enableCorrelations) ||
- !isActivePlatinumLicense(license)
- ) {
- return null;
- }
-
- return (
- <>
- {
- setIsFlyoutVisible(true);
- }}
- >
- View correlations
-
-
-
-
- {isFlyoutVisible && (
-
- setIsFlyoutVisible(false)}
- >
-
-
-