diff --git a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
index 265af0bde547f..5f0509586fc81 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
@@ -14,8 +14,8 @@ export const CANCEL = i18n.translate('xpack.siem.case.caseView.cancel', {
defaultMessage: 'Cancel',
});
-export const CASE_TITLE = i18n.translate('xpack.siem.case.caseView.caseTitle', {
- defaultMessage: 'Case Title',
+export const NAME = i18n.translate('xpack.siem.case.caseView.name', {
+ defaultMessage: 'Name',
});
export const CREATED_AT = i18n.translate('xpack.siem.case.caseView.createdAt', {
@@ -45,6 +45,13 @@ export const DESCRIPTION_REQUIRED = i18n.translate(
}
);
+export const COMMENT_REQUIRED = i18n.translate(
+ 'xpack.siem.case.caseView.commentFieldRequiredError',
+ {
+ defaultMessage: 'A comment is required.',
+ }
+);
+
export const EDIT = i18n.translate('xpack.siem.case.caseView.edit', {
defaultMessage: 'Edit',
});
@@ -58,15 +65,11 @@ export const LAST_UPDATED = i18n.translate('xpack.siem.case.caseView.updatedAt',
});
export const PAGE_SUBTITLE = i18n.translate('xpack.siem.case.caseView.pageSubtitle', {
- defaultMessage: 'Case Workflow Management within the Elastic SIEM',
+ defaultMessage: 'Cases within the Elastic SIEM',
});
export const PAGE_TITLE = i18n.translate('xpack.siem.case.pageTitle', {
- defaultMessage: 'Case Workflows',
-});
-
-export const PREVIEW = i18n.translate('xpack.siem.case.caseView.preview', {
- defaultMessage: 'Preview',
+ defaultMessage: 'Cases',
});
export const STATE = i18n.translate('xpack.siem.case.caseView.state', {
@@ -77,6 +80,10 @@ export const SUBMIT = i18n.translate('xpack.siem.case.caseView.submit', {
defaultMessage: 'Submit',
});
+export const CREATE_CASE = i18n.translate('xpack.siem.case.caseView.createCase', {
+ defaultMessage: 'Create case',
+});
+
export const TAGS = i18n.translate('xpack.siem.case.caseView.tags', {
defaultMessage: 'Tags',
});
@@ -104,3 +111,18 @@ export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate(
export const CONFIGURE_CASES_BUTTON = i18n.translate('xpack.siem.case.configureCasesButton', {
defaultMessage: 'Configure cases',
});
+
+export const ADD_COMMENT = i18n.translate('xpack.siem.case.caseView.comment.addComment', {
+ defaultMessage: 'Add comment',
+});
+
+export const ADD_COMMENT_HELP_TEXT = i18n.translate(
+ 'xpack.siem.case.caseView.comment.addCommentHelpText',
+ {
+ defaultMessage: 'Add a new comment...',
+ }
+);
+
+export const SAVE = i18n.translate('xpack.siem.case.caseView.description.save', {
+ defaultMessage: 'Save',
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx
index cc5e9b38eb2f8..abbaa6d6192ee 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx
@@ -18,7 +18,7 @@ import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 're
import styled from 'styled-components';
import * as RuleI18n from '../../translations';
-import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports';
interface AddItemProps {
addText: string;
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx
index 1cc7bba5558db..f921c29c06ab0 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx
@@ -19,7 +19,7 @@ import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/searc
import { useKibana } from '../../../../../lib/kibana';
import { IMitreEnterpriseAttack } from '../../types';
import { FieldValueTimeline } from '../pick_timeline';
-import { FormSchema } from '../../../../shared_imports';
+import { FormSchema } from '../../../../../shared_imports';
import { ListItems } from './types';
import {
buildQueryBarDescription,
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx
index b49126c8c0fe0..e87dba251ed6d 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx
@@ -20,7 +20,7 @@ import styled from 'styled-components';
import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques';
import * as Rulei18n from '../../translations';
-import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports';
import { threatDefault } from '../step_about_rule/default_value';
import { IMitreEnterpriseAttack } from '../../types';
import { MyAddItemButton } from '../add_item_form';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx
index 56cb02c9ec817..923ec3a7f0066 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx
@@ -8,7 +8,7 @@ import { EuiFormRow } from '@elastic/eui';
import React, { useCallback, useEffect, useState } from 'react';
import { SearchTimelineSuperSelect } from '../../../../../components/timeline/search_super_select';
-import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports';
export interface FieldValueTimeline {
id: string | null;
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx
index fbe854c1ee346..5886a76182eec 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx
@@ -29,7 +29,7 @@ import { convertKueryToElasticSearchQuery } from '../../../../../lib/keury';
import { useKibana } from '../../../../../lib/kibana';
import { TimelineModel } from '../../../../../store/timeline/model';
import { useSavedQueryServices } from '../../../../../utils/saved_query_services';
-import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports';
import * as i18n from './translations';
export interface FieldValueQueryBar {
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx
index ffb6c4eda3243..1b7d17016f83c 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx
@@ -16,7 +16,7 @@ import { isEmpty } from 'lodash/fp';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
-import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports';
import * as I18n from './translations';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx
index 431d793d6e68a..d93c057506ca7 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx
@@ -30,7 +30,7 @@ import {
getUseField,
UseField,
useForm,
-} from '../../../../shared_imports';
+} from '../../../../../shared_imports';
import { defaultRiskScoreBySeverity, severityOptions, SeverityValue } from './data';
import { stepAboutDefaultValue } from './default_value';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx
index 27887bcbbe600..42cf1e0d95649 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx
@@ -13,7 +13,7 @@ import {
FormSchema,
ValidationFunc,
ERROR_CODE,
-} from '../../../../shared_imports';
+} from '../../../../../shared_imports';
import { isMitreAttackInvalid } from '../mitre/helpers';
import { OptionalFieldLabel } from '../optional_field_label';
import { isUrlInvalid } from './helpers';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
index 773eb44efb26c..837bc79e968e8 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
@@ -33,7 +33,7 @@ import {
getUseField,
UseField,
useForm,
-} from '../../../../shared_imports';
+} from '../../../../../shared_imports';
import { schema } from './schema';
import * as i18n from './translations';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx
index bb178d7197069..e202ff030cd90 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx
@@ -17,7 +17,7 @@ import {
fieldValidators,
FormSchema,
ValidationFunc,
-} from '../../../../shared_imports';
+} from '../../../../../shared_imports';
import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT } from './translations';
const { emptyField } = fieldValidators;
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx
index 2e2c7e068dd85..e9632966fdfaf 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx
@@ -12,7 +12,7 @@ import { setFieldValue } from '../../helpers';
import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types';
import { StepRuleDescription } from '../description_step';
import { ScheduleItem } from '../schedule_item_form';
-import { Form, UseField, useForm } from '../../../../shared_imports';
+import { Form, UseField, useForm } from '../../../../../shared_imports';
import { StepContentWrapper } from '../step_content_wrapper';
import { schema } from './schema';
import * as I18n from './translations';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx
index 9932e4f6ef435..8fbfdf5f25a51 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { OptionalFieldLabel } from '../optional_field_label';
-import { FormSchema } from '../../../../shared_imports';
+import { FormSchema } from '../../../../../shared_imports';
export const schema: FormSchema = {
interval: {
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx
index c985045b1897b..d816c7e867057 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx
@@ -17,7 +17,7 @@ import { displaySuccessToast, useStateToaster } from '../../../../components/toa
import { SpyRoute } from '../../../../utils/route/spy_routes';
import { useUserInfo } from '../../components/user_info';
import { AccordionTitle } from '../components/accordion_title';
-import { FormData, FormHook } from '../../../shared_imports';
+import { FormData, FormHook } from '../../../../shared_imports';
import { StepAboutRule } from '../components/step_about_rule';
import { StepDefineRule } from '../components/step_define_rule';
import { StepScheduleRule } from '../components/step_schedule_rule';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
index 0fac4641e54a7..5e0e4223e3e27 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
@@ -26,7 +26,7 @@ import { displaySuccessToast, useStateToaster } from '../../../../components/toa
import { SpyRoute } from '../../../../utils/route/spy_routes';
import { useUserInfo } from '../../components/user_info';
import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page';
-import { FormHook, FormData } from '../../../shared_imports';
+import { FormHook, FormData } from '../../../../shared_imports';
import { StepPanel } from '../components/step_panel';
import { StepAboutRule } from '../components/step_about_rule';
import { StepDefineRule } from '../components/step_define_rule';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
index 3fab456d856ca..85f3bcbd236e9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
@@ -11,7 +11,7 @@ import { useLocation } from 'react-router-dom';
import { Filter } from '../../../../../../../../src/plugins/data/public';
import { Rule } from '../../../containers/detection_engine/rules';
-import { FormData, FormHook, FormSchema } from '../../shared_imports';
+import { FormData, FormHook, FormSchema } from '../../../shared_imports';
import { AboutStepRule, DefineStepRule, IMitreEnterpriseAttack, ScheduleStepRule } from './types';
interface GetStepsData {
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts
index b2650dcc2b77e..34df20de1e461 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts
@@ -6,7 +6,7 @@
import { Filter } from '../../../../../../../../src/plugins/data/common';
import { FieldValueQueryBar } from './components/query_bar';
-import { FormData, FormHook } from '../../shared_imports';
+import { FormData, FormHook } from '../../../shared_imports';
import { FieldValueTimeline } from './components/pick_timeline';
export interface EuiBasicTableSortTypes {
diff --git a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts
index 581c81d9f98a0..f2bcaa07b1a25 100644
--- a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts
@@ -27,5 +27,5 @@ export const TIMELINES = i18n.translate('xpack.siem.navigation.timelines', {
});
export const CASE = i18n.translate('xpack.siem.navigation.case', {
- defaultMessage: 'Case',
+ defaultMessage: 'Cases',
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/shared_imports.ts b/x-pack/legacy/plugins/siem/public/shared_imports.ts
similarity index 52%
rename from x-pack/legacy/plugins/siem/public/pages/shared_imports.ts
rename to x-pack/legacy/plugins/siem/public/shared_imports.ts
index a41f121b36926..edd7812b3bd16 100644
--- a/x-pack/legacy/plugins/siem/public/pages/shared_imports.ts
+++ b/x-pack/legacy/plugins/siem/public/shared_imports.ts
@@ -17,7 +17,7 @@ export {
UseField,
useForm,
ValidationFunc,
-} from '../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
-export { Field } from '../../../../../../src/plugins/es_ui_shared/static/forms/components';
-export { fieldValidators } from '../../../../../../src/plugins/es_ui_shared/static/forms/helpers';
-export { ERROR_CODE } from '../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types';
+} from '../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
+export { Field } from '../../../../../src/plugins/es_ui_shared/static/forms/components';
+export { fieldValidators } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers';
+export { ERROR_CODE } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types';
diff --git a/x-pack/legacy/plugins/siem/public/store/timeline/defaults.ts b/x-pack/legacy/plugins/siem/public/store/timeline/defaults.ts
index bbaf2a3fb6e30..7f04bb4c4dad0 100644
--- a/x-pack/legacy/plugins/siem/public/store/timeline/defaults.ts
+++ b/x-pack/legacy/plugins/siem/public/store/timeline/defaults.ts
@@ -14,7 +14,7 @@ export const timelineDefaults: SubsetTimelineModel & Pick;
-export async function getServiceMap(): Promise {
- return [
- { data: { id: 'client', agentName: 'js-base' } },
- { data: { id: 'opbeans-node', agentName: 'nodejs' } },
- { data: { id: 'opbeans-python', agentName: 'python' } },
- { data: { id: 'opbeans-java', agentName: 'java' } },
- { data: { id: 'opbeans-ruby', agentName: 'ruby' } },
- { data: { id: 'opbeans-go', agentName: 'go' } },
- { data: { id: 'opbeans-go-2', agentName: 'go' } },
- { data: { id: 'opbeans-dotnet', agentName: 'dotnet' } },
- { data: { id: 'database', agentName: 'database' } },
- { data: { id: 'external API', agentName: 'external' } },
-
- {
- data: {
- id: 'opbeans-client~opbeans-node',
- source: 'client',
- target: 'opbeans-node'
- }
- },
- {
- data: {
- id: 'opbeans-client~opbeans-python',
- source: 'client',
- target: 'opbeans-python'
- }
- },
- {
- data: {
- id: 'opbeans-python~opbeans-go',
- source: 'opbeans-python',
- target: 'opbeans-go'
- }
- },
- {
- data: {
- id: 'opbeans-python~opbeans-go-2',
- source: 'opbeans-python',
- target: 'opbeans-go-2'
- }
- },
- {
- data: {
- id: 'opbeans-python~opbeans-dotnet',
- source: 'opbeans-python',
- target: 'opbeans-dotnet'
- }
- },
- {
- data: {
- id: 'opbeans-node~opbeans-java',
- source: 'opbeans-node',
- target: 'opbeans-java'
- }
- },
- {
- data: {
- id: 'opbeans-node~database',
- source: 'opbeans-node',
- target: 'database'
- }
- },
- {
- data: {
- id: 'opbeans-go-2~opbeans-ruby',
- source: 'opbeans-go-2',
- target: 'opbeans-ruby'
- }
- },
- {
- data: {
- id: 'opbeans-go-2~external API',
- source: 'opbeans-go-2',
- target: 'external API'
- }
- }
- ];
-}
diff --git a/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts
index 5bfd121691ab4..6b4e3c194eb82 100644
--- a/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts
+++ b/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts
@@ -28,6 +28,7 @@ describe('UPDATE comment', () => {
},
body: {
comment: 'Update my comment',
+ version: 'WzEsMV0=',
},
});
@@ -37,6 +38,24 @@ describe('UPDATE comment', () => {
expect(response.status).toEqual(200);
expect(response.payload.comment).toEqual('Update my comment');
});
+ it(`Fails with 409 if version does not match`, async () => {
+ const request = httpServerMock.createKibanaRequest({
+ path: '/api/cases/comment/{id}',
+ method: 'patch',
+ params: {
+ id: 'mock-comment-1',
+ },
+ body: {
+ comment: 'Update my comment',
+ version: 'badv=',
+ },
+ });
+
+ const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments));
+
+ const response = await routeHandler(theContext, request, kibanaResponseFactory);
+ expect(response.status).toEqual(409);
+ });
it(`Returns an error if updateComment throws`, async () => {
const request = httpServerMock.createKibanaRequest({
path: '/api/cases/comment/{id}',
diff --git a/x-pack/plugins/case/server/routes/api/schema.ts b/x-pack/plugins/case/server/routes/api/schema.ts
index 468abc8e7226f..765f9c722219f 100644
--- a/x-pack/plugins/case/server/routes/api/schema.ts
+++ b/x-pack/plugins/case/server/routes/api/schema.ts
@@ -15,6 +15,11 @@ export const NewCommentSchema = schema.object({
comment: schema.string(),
});
+export const UpdateCommentArguments = schema.object({
+ comment: schema.string(),
+ version: schema.string(),
+});
+
export const CommentSchema = schema.object({
comment: schema.string(),
created_at: schema.string(),
diff --git a/x-pack/plugins/case/server/routes/api/update_comment.ts b/x-pack/plugins/case/server/routes/api/update_comment.ts
index 815f44a14e2e7..9f99253f76629 100644
--- a/x-pack/plugins/case/server/routes/api/update_comment.ts
+++ b/x-pack/plugins/case/server/routes/api/update_comment.ts
@@ -5,9 +5,12 @@
*/
import { schema } from '@kbn/config-schema';
+import { SavedObject } from 'kibana/server';
+import Boom from 'boom';
import { wrapError } from './utils';
-import { NewCommentSchema } from './schema';
+import { UpdateCommentArguments } from './schema';
import { RouteDeps } from '.';
+import { CommentAttributes } from './types';
export function initUpdateCommentApi({ caseService, router }: RouteDeps) {
router.patch(
@@ -17,20 +20,45 @@ export function initUpdateCommentApi({ caseService, router }: RouteDeps) {
params: schema.object({
id: schema.string(),
}),
- body: NewCommentSchema,
+ body: UpdateCommentArguments,
},
},
async (context, request, response) => {
+ let theComment: SavedObject;
+ try {
+ theComment = await caseService.getComment({
+ client: context.core.savedObjects.client,
+ commentId: request.params.id,
+ });
+ } catch (error) {
+ return response.customError(wrapError(error));
+ }
+ if (request.body.version !== theComment.version) {
+ return response.customError(
+ wrapError(
+ Boom.conflict(
+ 'This comment has been updated. Please refresh before saving additional updates.'
+ )
+ )
+ );
+ }
+ if (request.body.comment === theComment.attributes.comment) {
+ return response.customError(
+ wrapError(Boom.notAcceptable('Comment is identical to current version.'))
+ );
+ }
try {
const updatedComment = await caseService.updateComment({
client: context.core.savedObjects.client,
commentId: request.params.id,
updatedAttributes: {
- ...request.body,
+ comment: request.body.comment,
updated_at: new Date().toISOString(),
},
});
- return response.ok({ body: updatedComment.attributes });
+ return response.ok({
+ body: { ...updatedComment.attributes, version: updatedComment.version },
+ });
} catch (error) {
return response.customError(wrapError(error));
}
diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts
index d3df972290759..d804350a9002d 100644
--- a/x-pack/plugins/endpoint/common/types.ts
+++ b/x-pack/plugins/endpoint/common/types.ts
@@ -96,6 +96,59 @@ export interface EndpointResultList {
request_page_index: number;
}
+export interface OSFields {
+ full: string;
+ name: string;
+ version: string;
+ variant: string;
+}
+export interface HostFields {
+ id: string;
+ hostname: string;
+ ip: string[];
+ mac: string[];
+ os: OSFields;
+}
+export interface HashFields {
+ md5: string;
+ sha1: string;
+ sha256: string;
+}
+export interface MalwareClassifierFields {
+ identifier: string;
+ score: number;
+ threshold: number;
+ version: string;
+}
+export interface PrivilegesFields {
+ description: string;
+ name: string;
+ enabled: boolean;
+}
+export interface ThreadFields {
+ id: number;
+ service_name: string;
+ start: number;
+ start_address: number;
+ start_address_module: string;
+}
+export interface DllFields {
+ pe: {
+ architecture: string;
+ imphash: string;
+ };
+ code_signature: {
+ subject_name: string;
+ trusted: boolean;
+ };
+ compile_time: number;
+ hash: HashFields;
+ malware_classifier: MalwareClassifierFields;
+ mapped_address: number;
+ mapped_size: number;
+ path: string;
+}
+
/**
* Describes an Alert Event.
* Should be in line with ECS schema.
@@ -109,23 +162,78 @@ export type AlertEvent = Immutable<{
event: {
id: string;
action: string;
+ category: string;
+ kind: string;
+ dataset: string;
+ module: string;
+ type: string;
};
- file_classification: {
- malware_classification: {
- score: number;
+ process: {
+ code_signature: {
+ subject_name: string;
+ trusted: boolean;
};
- };
- host: {
- hostname: string;
- ip: string;
- os: {
- name: string;
+ command_line: string;
+ domain: string;
+ pid: number;
+ ppid: number;
+ entity_id: string;
+ parent: {
+ pid: number;
+ entity_id: string;
};
+ name: string;
+ hash: HashFields;
+ pe: {
+ imphash: string;
+ };
+ executable: string;
+ sid: string;
+ start: number;
+ malware_classifier: MalwareClassifierFields;
+ token: {
+ domain: string;
+ type: string;
+ user: string;
+ sid: string;
+ integrity_level: number;
+ integrity_level_name: string;
+ privileges: PrivilegesFields[];
+ };
+ thread: ThreadFields[];
+ uptime: number;
+ user: string;
};
- process: {
- pid: number;
+ file: {
+ owner: string;
+ name: string;
+ path: string;
+ accessed: number;
+ mtime: number;
+ created: number;
+ size: number;
+ hash: HashFields;
+ pe: {
+ imphash: string;
+ };
+ code_signature: {
+ trusted: boolean;
+ subject_name: string;
+ };
+ malware_classifier: {
+ features: {
+ data: {
+ buffer: string;
+ decompressed_size: number;
+ encoding: string;
+ };
+ };
+ } & MalwareClassifierFields;
+ temp_file_path: string;
};
+ host: HostFields;
thread: {};
+ dll: DllFields[];
}>;
/**
@@ -158,18 +266,7 @@ export interface EndpointMetadata {
id: string;
name: string;
};
- host: {
- id: string;
- hostname: string;
- ip: string[];
- mac: string[];
- os: {
- name: string;
- full: string;
- version: string;
- variant: string;
- };
- };
+ host: HostFields;
}
/**
@@ -186,22 +283,34 @@ export interface ESTotal {
export type AlertHits = SearchResponse['hits']['hits'];
export interface LegacyEndpointEvent {
- '@timestamp': Date;
+ '@timestamp': number;
endgame: {
- event_type_full: string;
- event_subtype_full: string;
+ pid?: number;
+ ppid?: number;
+ event_type_full?: string;
+ event_subtype_full?: string;
+ event_timestamp?: number;
+ event_type?: number;
unique_pid: number;
- unique_ppid: number;
- serial_event_id: number;
+ unique_ppid?: number;
+ machine_id?: string;
+ process_name?: string;
+ process_path?: string;
+ timestamp_utc?: string;
+ serial_event_id?: number;
};
agent: {
id: string;
type: string;
+ version: string;
};
+ process?: object;
+ rule?: object;
+ user?: object;
}
export interface EndpointEvent {
- '@timestamp': Date;
+ '@timestamp': number;
event: {
category: string;
type: string;
@@ -216,6 +325,7 @@ export interface EndpointEvent {
};
};
agent: {
+ id: string;
type: string;
};
}
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
index 7ab66817a0888..296587706e6ac 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
@@ -11,6 +11,7 @@ import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
import { Route, Switch, BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { Store } from 'redux';
+import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { RouteCapture } from './view/route_capture';
import { appStoreFactory } from './store';
import { AlertIndex } from './view/alerts';
@@ -24,9 +25,7 @@ import { HeaderNavigation } from './components/header_nav';
export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMountParameters) {
coreStart.http.get('/api/endpoint/hello-world');
const store = appStoreFactory(coreStart);
-
- ReactDOM.render(, element);
-
+ ReactDOM.render(, element);
return () => {
ReactDOM.unmountComponentAtNode(element);
};
@@ -35,35 +34,46 @@ export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMou
interface RouterProps {
basename: string;
store: Store;
+ coreStart: CoreStart;
}
-const AppRoot: React.FunctionComponent = React.memo(({ basename, store }) => (
-
-
-
-
-
-
- (
-
-
-
- )}
- />
-
- } />
-
- (
-
- )}
- />
-
-
-
-
-
-));
+const AppRoot: React.FunctionComponent = React.memo(
+ ({ basename, store, coreStart: { http } }) => (
+
+
+
+
+
+
+
+ (
+
+
+
+ )}
+ />
+
+
+
+ (
+
+ )}
+ />
+
+
+
+
+
+
+ )
+);
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts
index a628a95003a7f..6c6310a7349ed 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Immutable } from '../../../../../common/types';
+import { Immutable, AlertData } from '../../../../../common/types';
import { AlertListData } from '../../types';
interface ServerReturnedAlertsData {
@@ -12,4 +12,9 @@ interface ServerReturnedAlertsData {
readonly payload: Immutable;
}
-export type AlertAction = ServerReturnedAlertsData;
+interface ServerReturnedAlertDetailsData {
+ readonly type: 'serverReturnedAlertDetailsData';
+ readonly payload: Immutable;
+}
+
+export type AlertAction = ServerReturnedAlertsData | ServerReturnedAlertDetailsData;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts
new file mode 100644
index 0000000000000..4edc31831eb14
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Store, createStore, applyMiddleware } from 'redux';
+import { History } from 'history';
+import { alertListReducer } from './reducer';
+import { AlertListState } from '../../types';
+import { alertMiddlewareFactory } from './middleware';
+import { AppAction } from '../action';
+import { coreMock } from 'src/core/public/mocks';
+import { createBrowserHistory } from 'history';
+
+describe('alert details tests', () => {
+ let store: Store;
+ let coreStart: ReturnType;
+ let history: History;
+ /**
+ * A function that waits until a selector returns true.
+ */
+ let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise;
+ beforeEach(() => {
+ coreStart = coreMock.createStart();
+ history = createBrowserHistory();
+ const middleware = alertMiddlewareFactory(coreStart);
+ store = createStore(alertListReducer, applyMiddleware(middleware));
+
+ selectorIsTrue = async selector => {
+ // If the selector returns true, we're done
+ while (selector(store.getState()) !== true) {
+ // otherwise, wait til the next state change occurs
+ await new Promise(resolve => {
+ const unsubscribe = store.subscribe(() => {
+ unsubscribe();
+ resolve();
+ });
+ });
+ }
+ };
+ });
+ describe('when the user is on the alert list page with a selected alert in the url', () => {
+ beforeEach(() => {
+ const firstResponse: Promise = Promise.resolve(1);
+ const secondResponse: Promise = Promise.resolve(2);
+ coreStart.http.get.mockReturnValueOnce(firstResponse).mockReturnValueOnce(secondResponse);
+
+ // Simulates user navigating to the /alerts page
+ store.dispatch({
+ type: 'userChangedUrl',
+ payload: {
+ ...history.location,
+ pathname: '/alerts',
+ search: '?selected_alert=q9ncfh4q9ctrmc90umcq4',
+ },
+ });
+ });
+
+ it('should return alert details data', async () => {
+ // wait for alertDetails to be defined
+ await selectorIsTrue(state => state.alertDetails !== undefined);
+ });
+ });
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts
index 76a6867418bd8..2cb381e901b4e 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { AlertResultList } from '../../../../../common/types';
+import { AlertResultList, AlertData } from '../../../../../common/types';
import { AppAction } from '../action';
import { MiddlewareFactory, AlertListState } from '../../types';
-import { isOnAlertPage, apiQueryParams } from './selectors';
+import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors';
export const alertMiddlewareFactory: MiddlewareFactory = coreStart => {
return api => next => async (action: AppAction) => {
@@ -19,5 +19,12 @@ export const alertMiddlewareFactory: MiddlewareFactory = coreSta
});
api.dispatch({ type: 'serverReturnedAlertsData', payload: response });
}
+ if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) {
+ const uiParams = uiQueryParams(state);
+ const response: AlertData = await coreStart.http.get(
+ `/api/endpoint/alerts/${uiParams.selected_alert}`
+ );
+ api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response });
+ }
};
};
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts
index b90f897ea2229..7db94fc9d4266 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts
@@ -32,28 +32,152 @@ export const mockAlertResultList: (options?: {
id: 'ced9c68e-b94a-4d66-bb4c-6106514f0a2f',
version: '3.0.0',
},
- event: {
- id: '2f1c0928-3876-4e11-acbb-9199257c7b1c',
- action: 'open',
- },
- file_classification: {
- malware_classification: {
- score: 3,
- },
- },
- process: {
- pid: 107,
- },
host: {
+ id: 'xrctvybuni',
hostname: 'HD-c15-bc09190a',
- ip: '10.179.244.14',
+ ip: ['10.179.244.14'],
+ mac: ['xsertcyvbunimkn56edtyf'],
os: {
- name: 'Windows',
+ full: 'Windows 10',
+ name: 'windows',
+ version: '10',
+ variant: '3',
},
},
thread: {},
prev: null,
next: null,
+ event: {
+ id: '2f1c0928-3876-4e11-acbb-9199257c7b1c',
+ action: 'creation',
+ category: 'malware',
+ dataset: 'endpoint',
+ kind: 'alert',
+ module: 'endpoint',
+ type: 'creation',
+ },
+ file: {
+ accessed: 1542789400,
+ created: 1542789400,
+ hash: {
+ md5: '4ace3baaa509d08510405e1b169e325b',
+ sha1: '27fb21cf5db95ffca43b234affa99becc4023b9d',
+ sha256: '6ed1c836dbf099be7845bdab7671def2c157643761b52251e04e9b6ee109ec75',
+ },
+ pe: {
+ imphash: '835d619dfdf3cc727cebd91300ab3462',
+ },
+ mtime: 1542789400,
+ owner: 'Administrators',
+ name: 'test name',
+ path: 'C:\\Windows\\TEMP\\tmp0000008f\\tmp00001be5',
+ size: 188416,
+ code_signature: {
+ subject_name: 'Cybereason Inc',
+ trusted: false,
+ },
+ malware_classifier: {
+ features: {
+ data: {
+ buffer:
+ 'eAHtnU1oHHUUwHsQ7MGDiIIUD4sH8WBBxJtopiLoUY0pYo2ZTbJJ0yQ17m4+ms/NRzeVWpuUWCL4sWlEYvFQ8KJQ6NCTEA8eRD30sIo3PdSriLi7837Pko3LbHZ2M5m+XObHm/d/X////83O7jCZvzacHBpPplNdfalkdjSdyty674Ft59dN71Dpb9v5eKh8LMEHjsCF2wIfVlRKsHROYPGkQO5+gY2vBSYYdWZFYGwEO/cITHMqkxPYnBBY+07gtCuQ9gSGigJ5lPPYGXcE+jA4z3Ad1ZtAUiDUyrEEPYzqRnIKgxd/Rgc7gygPo5wn95PouN7OeEYJ1UXiJgRmvscgp/LOziIkkSyT+xRVnXhZ4DKh5goCkzidRHkGO4uvCyw9LDDtCay8ILCAzrJOJaGuZwUuvSewivJVIPsklq8JbL4qMJsTSCcExrGs83WKU295ZFo5lr2TaZbcUw5FeJy8tgTeLpCy2iGeS67ABXzlgbEi1UC5FxcZnA4y/CLK82Qxi847FGGZRTLsCUxR1aWEwOp1AmOjDRYYzgwusL9WfqBiGJxnVAanixTq7Dp22LBdlWMJzlOx8wmBK2Rx5WmBLJIRwtAijOQE+ooCb2B5xBOYRtlfNeXpLpA7oyZRTqHzGenkmIJPnhBIMrzTwSA6H93CO5l+c1NA99f6IwLH8fUKdjTmDpTbgS50+gGVnECnE4PpooC2guPoaPADSHrcncNHmEHtAFkq3+EI+A37zsrrTvH3WTkvJLoOTyBp10wx2JcgVCRahA4NrICE4a+hrMXsA3qAHItW188E8ejO7XV3eh/KCYwxlamEwCgL8lN2wTntfrhY/U0g/5KAdvUpT+AszWqBdqH7VLeeZrExK9Cv1UgIDKA8g/cx7QAEP+AhAfRaMKB2HOJh+BSFSqKjSytNGBlc6PrpxvK7lCVDxbSG3Z7AhCMwx6gelwgLAltXBXJUTH29j+U1LHdipx/QprfKfGnF0sBpdBYxmEQyTzW0h6/0khcuhhJYRufym+i4VKMocJMs/KvfoW3/UJb4PeZOSZVONThZz4djP/75TAXa/CVfOvX3RgVLIDreLPN1pP1osW7lGmHsEhjBOzf+EPBE4vndvWz5xb/cChxGcv1LAb+tluALKnZ47isf1MXvz1ZMlsCXbXtPceqhrcp1ps6YHwQeBXLEPCf7q23tl9uJui0bGBgYRAccv7uXr/g5Af+2oNTrpgTa/vnpjBvpLAwM4gRBPvIZGBgYGBgYGBgYGBgYGBgYGBgYGBgYNAOc9oMXs4GBgYFBcNBnww5QzDXgRtPSaZ5lg/itsRaslgZ3bnWEEVnhMetIBwiiVnlbCbWrEftrt11zdwWnseFW1QO63w1is3ptD1pV9xG0t+zvfUrzrvh380qwXWAVCw6h78GIfG7ZlzltXu6hd+y92fECRFhjuH3bXG8N43oXEHperdzvUbteaDxhVTUeq25fqhG1X6Ai8mtF6BDXz2wR+dzSgg4Qsxls5T11XMG+82y8GkG+b7kL69xg7mF1SFvhBgYGsYH/Xi7HE+PVkiB2jt1bNZxT+k4558jR53ydz5//1m1KOgYGBgYGBgYGEQfnsYaG2z1sdPJS79XQSu91ndobOAHCaN5vNzUk1bceQVzUpbw3iOuT+UFmR18bHrp3gyhDC56lCd1y85w2+HSNUwVhhdGC7blLf+bV/fqtvhMg1NDjCcugB1QXswbs8ekj/v1BgzFHBIIsyP+HfwFdMpzu',
+ decompressed_size: 27831,
+ encoding: 'zlib',
+ },
+ },
+ identifier: 'endpointpe',
+ score: 1,
+ threshold: 0.66,
+ version: '3.0.33',
+ },
+ temp_file_path: 'C:\\Windows\\TEMP\\1bb9abfc-ca14-47b2-9f2c-10c323df42f9',
+ },
+ process: {
+ pid: 1076,
+ ppid: 432,
+ entity_id: 'wertqwer',
+ parent: {
+ pid: 432,
+ entity_id: 'adsfsdaf',
+ },
+ name: 'test name',
+ code_signature: {
+ subject_name: 'Cybereason Inc',
+ trusted: true,
+ },
+ command_line: '"C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe"',
+ domain: 'NT AUTHORITY',
+ executable: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
+ hash: {
+ md5: '1f2d082566b0fc5f2c238a5180db7451',
+ sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
+ sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
+ },
+ pe: {
+ imphash: 'c30d230b81c734e82e86e2e2fe01cd01',
+ },
+ malware_classifier: {
+ identifier: 'Whitelisted',
+ score: 0,
+ threshold: 0,
+ version: '3.0.0',
+ },
+ thread: [
+ {
+ id: 1652,
+ service_name: 'CybereasonAntiMalware',
+ start: 1542788400,
+ start_address: 8791698721056,
+ start_address_module: 'C:\\Program Files\\Cybereason ActiveProbe\\gzfltum.dll',
+ },
+ ],
+ sid: 'S-1-5-18',
+ start: 1542788400,
+ token: {
+ domain: 'NT AUTHORITY',
+ integrity_level: 16384,
+ integrity_level_name: 'system',
+ privileges: [
+ {
+ description: 'Replace a process level token',
+ enabled: false,
+ name: 'SeAssignPrimaryTokenPrivilege',
+ },
+ ],
+ sid: 'S-1-5-18',
+ type: 'tokenPrimary',
+ user: 'SYSTEM',
+ },
+ uptime: 1025,
+ user: 'SYSTEM',
+ },
+ dll: [
+ {
+ pe: {
+ architecture: 'x64',
+ imphash: 'c30d230b81c734e82e86e2e2fe01cd01',
+ },
+ code_signature: {
+ subject_name: 'Cybereason Inc',
+ trusted: true,
+ },
+ compile_time: 1534424710,
+ hash: {
+ md5: '1f2d082566b0fc5f2c238a5180db7451',
+ sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
+ sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
+ },
+ malware_classifier: {
+ identifier: 'Whitelisted',
+ score: 0,
+ threshold: 0,
+ version: '3.0.0',
+ },
+ mapped_address: 5362483200,
+ mapped_size: 0,
+ path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
+ },
+ ],
});
}
const mock: AlertResultList = {
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts
index 77d7397d72581..ee172fa80f1fe 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts
@@ -11,6 +11,7 @@ import { AppAction } from '../action';
const initialState = (): AlertListState => {
return {
alerts: [],
+ alertDetails: undefined,
pageSize: 10,
pageIndex: 0,
total: 0,
@@ -43,6 +44,11 @@ export const alertListReducer: Reducer = (
...state,
location: action.payload,
};
+ } else if (action.type === 'serverReturnedAlertDetailsData') {
+ return {
+ ...state,
+ alertDetails: action.payload,
+ };
}
return state;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts
index 54add85f0fe04..7ce7c2d08691e 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts
@@ -9,13 +9,13 @@ import {
createSelector,
createStructuredSelector as createStructuredSelectorWithBadType,
} from 'reselect';
-import { Immutable } from '../../../../../common/types';
import {
AlertListState,
AlertingIndexUIQueryParams,
AlertsAPIQueryParams,
CreateStructuredSelector,
} from '../../types';
+import { Immutable } from '../../../../../common/types';
const createStructuredSelector: CreateStructuredSelector = createStructuredSelectorWithBadType;
/**
@@ -23,6 +23,8 @@ const createStructuredSelector: CreateStructuredSelector = createStructuredSelec
*/
export const alertListData = (state: AlertListState) => state.alerts;
+export const selectedAlertDetailsData = (state: AlertListState) => state.alertDetails;
+
/**
* Returns the alert list pagination data from state
*/
@@ -92,3 +94,15 @@ export const hasSelectedAlert: (state: AlertListState) => boolean = createSelect
uiQueryParams,
({ selected_alert: selectedAlert }) => selectedAlert !== undefined
);
+
+/**
+ * Determine if the alert event is most likely compatible with LegacyEndpointEvent.
+ */
+export const selectedAlertIsLegacyEndpointEvent: (
+ state: AlertListState
+) => boolean = createSelector(selectedAlertDetailsData, function(event) {
+ if (event === undefined) {
+ return false;
+ }
+ return 'endgame' in event;
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
index 6498462a8fc06..b46785d3190e5 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
@@ -93,19 +93,22 @@ export type AlertListData = AlertResultList;
export interface AlertListState {
/** Array of alert items. */
- alerts: ImmutableArray;
+ readonly alerts: ImmutableArray;
/** The total number of alerts on the page. */
- total: number;
+ readonly total: number;
/** Number of alerts per page. */
- pageSize: number;
+ readonly pageSize: number;
/** Page number, starting at 0. */
- pageIndex: number;
+ readonly pageIndex: number;
/** Current location object from React Router history. */
readonly location?: Immutable;
+
+ /** Specific Alert data to be shown in the details view */
+ readonly alertDetails?: Immutable;
}
/**
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/index.ts
new file mode 100644
index 0000000000000..1c78309474737
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { AlertDetailsOverview } from './overview';
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx
new file mode 100644
index 0000000000000..ac67e54f38779
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
+import { Immutable, AlertData } from '../../../../../../../common/types';
+import { FormattedDate } from '../../formatted_date';
+
+export const FileAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+ const columns = useMemo(() => {
+ return [
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileName', {
+ defaultMessage: 'File Name',
+ }),
+ description: alertData.file.name,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.filePath', {
+ defaultMessage: 'File Path',
+ }),
+ description: alertData.file.path,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileSize', {
+ defaultMessage: 'File Size',
+ }),
+ description: alertData.file.size,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileCreated', {
+ defaultMessage: 'File Created',
+ }),
+ description: ,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileModified', {
+ defaultMessage: 'File Modified',
+ }),
+ description: ,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileAccessed', {
+ defaultMessage: 'File Accessed',
+ }),
+ description: ,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.signer', {
+ defaultMessage: 'Signer',
+ }),
+ description: alertData.file.code_signature.subject_name,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.owner', {
+ defaultMessage: 'Owner',
+ }),
+ description: alertData.file.owner,
+ },
+ ];
+ }, [alertData]);
+
+ return (
+
+
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx
new file mode 100644
index 0000000000000..070c78c968585
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
+import { Immutable, AlertData } from '../../../../../../../common/types';
+import { FormattedDate } from '../../formatted_date';
+
+export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+ const columns = useMemo(() => {
+ return [
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.alertType', {
+ defaultMessage: 'Alert Type',
+ }),
+ description: alertData.event.category,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.eventType', {
+ defaultMessage: 'Event Type',
+ }),
+ description: alertData.event.kind,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', {
+ defaultMessage: 'Status',
+ }),
+ description: 'TODO',
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.dateCreated', {
+ defaultMessage: 'Date Created',
+ }),
+ description: ,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.malwareScore', {
+ defaultMessage: 'MalwareScore',
+ }),
+ description: alertData.file.malware_classifier.score,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileName', {
+ defaultMessage: 'File Name',
+ }),
+ description: alertData.file.name,
+ },
+ ];
+ }, [alertData]);
+ return (
+
+
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx
new file mode 100644
index 0000000000000..b2be083ce8f59
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
+import { Immutable, AlertData } from '../../../../../../../common/types';
+
+export const HashAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+ const columns = useMemo(() => {
+ return [
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.md5', {
+ defaultMessage: 'MD5',
+ }),
+ description: alertData.file.hash.md5,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha1', {
+ defaultMessage: 'SHA1',
+ }),
+ description: alertData.file.hash.sha1,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha256', {
+ defaultMessage: 'SHA256',
+ }),
+ description: alertData.file.hash.sha256,
+ },
+ ];
+ }, [alertData]);
+
+ return (
+
+
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
new file mode 100644
index 0000000000000..4108781f0a79b
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
+import { Immutable, AlertData } from '../../../../../../../common/types';
+
+export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+ const columns = useMemo(() => {
+ return [
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostName', {
+ defaultMessage: 'Host Name',
+ }),
+ description: alertData.host.hostname,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIP', {
+ defaultMessage: 'Host IP',
+ }),
+ description: alertData.host.ip.join(', '),
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', {
+ defaultMessage: 'Status',
+ }),
+ description: 'TODO',
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.os', {
+ defaultMessage: 'OS',
+ }),
+ description: alertData.host.os.name,
+ },
+ ];
+ }, [alertData]);
+
+ return (
+
+
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/index.ts
new file mode 100644
index 0000000000000..1eb755242d701
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { GeneralAccordion } from './general_accordion';
+export { HostAccordion } from './host_accordion';
+export { HashAccordion } from './hash_accordion';
+export { FileAccordion } from './file_accordion';
+export { SourceProcessAccordion } from './source_process_accordion';
+export { SourceProcessTokenAccordion } from './source_process_token_accordion';
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx
new file mode 100644
index 0000000000000..4c961ad4b4964
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
+import { Immutable, AlertData } from '../../../../../../../common/types';
+
+export const SourceProcessAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+ const columns = useMemo(() => {
+ return [
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.processID', {
+ defaultMessage: 'Process ID',
+ }),
+ description: alertData.process.pid,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.processName', {
+ defaultMessage: 'Process Name',
+ }),
+ description: alertData.process.name,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.processPath', {
+ defaultMessage: 'Process Path',
+ }),
+ description: alertData.process.executable,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.md5', {
+ defaultMessage: 'MD5',
+ }),
+ description: alertData.process.hash.md5,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha1', {
+ defaultMessage: 'SHA1',
+ }),
+ description: alertData.process.hash.sha1,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha256', {
+ defaultMessage: 'SHA256',
+ }),
+ description: alertData.process.hash.sha256,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.malwareScore', {
+ defaultMessage: 'MalwareScore',
+ }),
+ description: alertData.process.malware_classifier.score,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.parentProcessID', {
+ defaultMessage: 'Parent Process ID',
+ }),
+ description: alertData.process.parent.pid,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.signer', {
+ defaultMessage: 'Signer',
+ }),
+ description: alertData.process.code_signature.subject_name,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.username', {
+ defaultMessage: 'Username',
+ }),
+ description: alertData.process.token.user,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.domain', {
+ defaultMessage: 'Domain',
+ }),
+ description: alertData.process.token.domain,
+ },
+ ];
+ }, [alertData]);
+
+ return (
+
+
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx
new file mode 100644
index 0000000000000..7d75d4478afb3
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
+import { Immutable, AlertData } from '../../../../../../../common/types';
+
+export const SourceProcessTokenAccordion = memo(
+ ({ alertData }: { alertData: Immutable }) => {
+ const columns = useMemo(() => {
+ return [
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sid', {
+ defaultMessage: 'SID',
+ }),
+ description: alertData.process.token.sid,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.integrityLevel', {
+ defaultMessage: 'Integrity Level',
+ }),
+ description: alertData.process.token.integrity_level,
+ },
+ ];
+ }, [alertData]);
+
+ return (
+
+
+
+ );
+ }
+);
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx
new file mode 100644
index 0000000000000..080c70ca43bae
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx
@@ -0,0 +1,94 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiSpacer, EuiTitle, EuiText, EuiHealth, EuiTabbedContent } from '@elastic/eui';
+import { useAlertListSelector } from '../../hooks/use_alerts_selector';
+import * as selectors from '../../../../store/alerts/selectors';
+import { MetadataPanel } from './metadata_panel';
+import { FormattedDate } from '../../formatted_date';
+import { AlertDetailResolver } from '../../resolver';
+
+export const AlertDetailsOverview = memo(() => {
+ const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
+ if (alertDetailsData === undefined) {
+ return null;
+ }
+ const selectedAlertIsLegacyEndpointEvent = useAlertListSelector(
+ selectors.selectedAlertIsLegacyEndpointEvent
+ );
+
+ const tabs = useMemo(() => {
+ return [
+ {
+ id: 'overviewMetadata',
+ name: i18n.translate(
+ 'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview',
+ {
+ defaultMessage: 'Overview',
+ }
+ ),
+ content: (
+ <>
+
+
+ >
+ ),
+ },
+ {
+ id: 'overviewResolver',
+ name: i18n.translate(
+ 'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.resolver',
+ {
+ defaultMessage: 'Resolver',
+ }
+ ),
+ content: (
+ <>
+
+ {selectedAlertIsLegacyEndpointEvent && }
+ >
+ ),
+ },
+ ];
+ }, [selectedAlertIsLegacyEndpointEvent]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ ,
+ }}
+ />
+
+
+
+
+ Endpoint Status: Online
+
+ Alert Status: Open
+
+
+
+ >
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/metadata_panel.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/metadata_panel.tsx
new file mode 100644
index 0000000000000..556d7bea2e310
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/metadata_panel.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo } from 'react';
+import { EuiSpacer } from '@elastic/eui';
+import { useAlertListSelector } from '../../hooks/use_alerts_selector';
+import * as selectors from '../../../../store/alerts/selectors';
+import {
+ GeneralAccordion,
+ HostAccordion,
+ HashAccordion,
+ FileAccordion,
+ SourceProcessAccordion,
+ SourceProcessTokenAccordion,
+} from '../metadata';
+
+export const MetadataPanel = memo(() => {
+ const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
+ if (alertDetailsData === undefined) {
+ return null;
+ }
+ return (
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/formatted_date.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/formatted_date.tsx
new file mode 100644
index 0000000000000..731bd31b26cef
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/formatted_date.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { memo } from 'react';
+import { FormattedDate as ReactIntlFormattedDate } from '@kbn/i18n/react';
+
+export const FormattedDate = memo(({ timestamp }: { timestamp: number }) => {
+ const date = new Date(timestamp);
+ return (
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx
index 37847553d512a..aae44824c3164 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx
@@ -11,6 +11,7 @@ import { I18nProvider } from '@kbn/i18n/react';
import { AlertIndex } from './index';
import { appStoreFactory } from '../../store';
import { coreMock } from 'src/core/public/mocks';
+import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { fireEvent, waitForElement, act } from '@testing-library/react';
import { RouteCapture } from '../route_capture';
import { createMemoryHistory, MemoryHistory } from 'history';
@@ -44,6 +45,7 @@ describe('when on the alerting page', () => {
* Create a store, with the middleware disabled. We don't want side effects being created by our code in this test.
*/
store = appStoreFactory(coreMock.createStart(), true);
+
/**
* Render the test component, use this after setting up anything in `beforeEach`.
*/
@@ -56,13 +58,15 @@ describe('when on the alerting page', () => {
*/
return reactTestingLibrary.render(
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx
index 6f88727575557..5d405f8c6fbae 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx
@@ -17,14 +17,21 @@ import {
EuiFlyoutBody,
EuiTitle,
EuiBadge,
+ EuiLoadingSpinner,
+ EuiPageContentHeader,
+ EuiPageContentHeaderSection,
+ EuiPageContentBody,
+ EuiLink,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { useHistory, Link } from 'react-router-dom';
-import { FormattedDate } from 'react-intl';
+import { useHistory } from 'react-router-dom';
+import { FormattedMessage } from '@kbn/i18n/react';
import { urlFromQueryParams } from './url_from_query_params';
import { AlertData } from '../../../../../common/types';
import * as selectors from '../../store/alerts/selectors';
import { useAlertListSelector } from './hooks/use_alerts_selector';
+import { AlertDetailsOverview } from './details';
+import { FormattedDate } from './formatted_date';
export const AlertIndex = memo(() => {
const history = useHistory();
@@ -117,10 +124,10 @@ export const AlertIndex = memo(() => {
history.push(urlFromQueryParams(paramsWithoutSelectedAlert));
}, [history, queryParams]);
- const datesForRows: Map = useMemo(() => {
+ const timestampForRows: Map = useMemo(() => {
return new Map(
alertListData.map(alertData => {
- return [alertData, new Date(alertData['@timestamp'])];
+ return [alertData, alertData['@timestamp']];
})
);
}, [alertListData]);
@@ -132,12 +139,13 @@ export const AlertIndex = memo(() => {
}
const row = alertListData[rowIndex % pageSize];
-
if (columnId === 'alert_type') {
return (
-
+ history.push(urlFromQueryParams({ ...queryParams, selected_alert: row.id }))
+ }
>
{i18n.translate(
'xpack.endpoint.application.endpoint.alerts.alertType.maliciousFileDescription',
@@ -145,7 +153,7 @@ export const AlertIndex = memo(() => {
defaultMessage: 'Malicious File',
}
)}
-
+
);
} else if (columnId === 'event_type') {
return row.event.action;
@@ -156,19 +164,9 @@ export const AlertIndex = memo(() => {
} else if (columnId === 'host_name') {
return row.host.hostname;
} else if (columnId === 'timestamp') {
- const date = datesForRows.get(row)!;
- if (date && isFinite(date.getTime())) {
- return (
-
- );
+ const timestamp = timestampForRows.get(row)!;
+ if (timestamp) {
+ return ;
} else {
return (
@@ -184,11 +182,11 @@ export const AlertIndex = memo(() => {
} else if (columnId === 'archived') {
return null;
} else if (columnId === 'malware_score') {
- return row.file_classification.malware_classification.score;
+ return row.file.malware_classifier.score;
}
return null;
};
- }, [alertListData, datesForRows, pageSize, queryParams, total]);
+ }, [total, alertListData, pageSize, history, queryParams, timestampForRows]);
const pagination = useMemo(() => {
return {
@@ -200,6 +198,16 @@ export const AlertIndex = memo(() => {
};
}, [onChangeItemsPerPage, onChangePage, pageIndex, pageSize]);
+ const columnVisibility = useMemo(
+ () => ({
+ visibleColumns,
+ setVisibleColumns,
+ }),
+ [setVisibleColumns, visibleColumns]
+ );
+
+ const selectedAlertData = useAlertListSelector(selectors.selectedAlertDetailsData);
+
return (
<>
{hasSelectedAlert && (
@@ -213,28 +221,38 @@ export const AlertIndex = memo(() => {
-
+
+ {selectedAlertData ? : }
+
)}
- ({
- visibleColumns,
- setVisibleColumns,
- }),
- [setVisibleColumns, visibleColumns]
- )}
- renderCellValue={renderCellValue}
- pagination={pagination}
- data-test-subj="alertListGrid"
- data-testid="alertListGrid"
- />
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx
new file mode 100644
index 0000000000000..ec1dab45d50ab
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+import { Provider } from 'react-redux';
+import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
+import { Resolver } from '../../../../embeddables/resolver/view';
+import { EndpointPluginServices } from '../../../../plugin';
+import { LegacyEndpointEvent } from '../../../../../common/types';
+import { storeFactory } from '../../../../embeddables/resolver/store';
+
+export const AlertDetailResolver = styled(
+ React.memo(
+ ({ className, selectedEvent }: { className?: string; selectedEvent?: LegacyEndpointEvent }) => {
+ const context = useKibana();
+ const { store } = storeFactory(context);
+
+ return (
+
+ );
+ }
+ )
+)`
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-grow: 1;
+`;
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/models/indexed_process_tree.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/models/indexed_process_tree.ts
index 0eb3505096b4a..6892bf11ecff2 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/models/indexed_process_tree.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/models/indexed_process_tree.ts
@@ -5,15 +5,16 @@
*/
import { uniquePidForProcess, uniqueParentPidForProcess } from './process_event';
-import { IndexedProcessTree, ProcessEvent } from '../types';
+import { IndexedProcessTree } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
import { levelOrder as baseLevelOrder } from '../lib/tree_sequencers';
/**
* Create a new IndexedProcessTree from an array of ProcessEvents
*/
-export function factory(processes: ProcessEvent[]): IndexedProcessTree {
- const idToChildren = new Map();
- const idToValue = new Map();
+export function factory(processes: LegacyEndpointEvent[]): IndexedProcessTree {
+ const idToChildren = new Map();
+ const idToValue = new Map();
for (const process of processes) {
idToValue.set(uniquePidForProcess(process), process);
@@ -35,7 +36,10 @@ export function factory(processes: ProcessEvent[]): IndexedProcessTree {
/**
* Returns an array with any children `ProcessEvent`s of the passed in `process`
*/
-export function children(tree: IndexedProcessTree, process: ProcessEvent): ProcessEvent[] {
+export function children(
+ tree: IndexedProcessTree,
+ process: LegacyEndpointEvent
+): LegacyEndpointEvent[] {
const id = uniquePidForProcess(process);
const processChildren = tree.idToChildren.get(id);
return processChildren === undefined ? [] : processChildren;
@@ -46,8 +50,8 @@ export function children(tree: IndexedProcessTree, process: ProcessEvent): Proce
*/
export function parent(
tree: IndexedProcessTree,
- childProcess: ProcessEvent
-): ProcessEvent | undefined {
+ childProcess: LegacyEndpointEvent
+): LegacyEndpointEvent | undefined {
const uniqueParentPid = uniqueParentPidForProcess(childProcess);
if (uniqueParentPid === undefined) {
return undefined;
@@ -70,7 +74,7 @@ export function root(tree: IndexedProcessTree) {
if (size(tree) === 0) {
return null;
}
- let current: ProcessEvent = tree.idToProcess.values().next().value;
+ let current: LegacyEndpointEvent = tree.idToProcess.values().next().value;
while (parent(tree, current) !== undefined) {
current = parent(tree, current)!;
}
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.test.ts
index 3177671a30001..3916396f7402c 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.test.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.test.ts
@@ -4,22 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { eventType } from './process_event';
-import { ProcessEvent } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
import { mockProcessEvent } from './process_event_test_helpers';
describe('process event', () => {
describe('eventType', () => {
- let event: ProcessEvent;
+ let event: LegacyEndpointEvent;
beforeEach(() => {
event = mockProcessEvent({
- data_buffer: {
- node_id: 1,
+ endgame: {
+ unique_pid: 1,
event_type_full: 'process_event',
},
});
});
it("returns the right value when the subType is 'creation_event'", () => {
- event.data_buffer.event_subtype_full = 'creation_event';
+ event.endgame.event_subtype_full = 'creation_event';
expect(eventType(event)).toEqual('processCreated');
});
});
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.ts
index c8496b8e6e7a5..876168d2ed96a 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event.ts
@@ -4,23 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ProcessEvent } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
/**
* Returns true if the process's eventType is either 'processCreated' or 'processRan'.
* Resolver will only render 'graphable' process events.
*/
-export function isGraphableProcess(event: ProcessEvent) {
- return eventType(event) === 'processCreated' || eventType(event) === 'processRan';
+export function isGraphableProcess(passedEvent: LegacyEndpointEvent) {
+ return eventType(passedEvent) === 'processCreated' || eventType(passedEvent) === 'processRan';
}
/**
* Returns a custom event type for a process event based on the event's metadata.
*/
-export function eventType(event: ProcessEvent) {
+export function eventType(passedEvent: LegacyEndpointEvent) {
const {
- data_buffer: { event_type_full: type, event_subtype_full: subType },
- } = event;
+ endgame: { event_type_full: type, event_subtype_full: subType },
+ } = passedEvent;
if (type === 'process_event') {
if (subType === 'creation_event' || subType === 'fork_event' || subType === 'exec_event') {
@@ -41,13 +41,13 @@ export function eventType(event: ProcessEvent) {
/**
* Returns the process event's pid
*/
-export function uniquePidForProcess(event: ProcessEvent) {
- return event.data_buffer.node_id;
+export function uniquePidForProcess(event: LegacyEndpointEvent) {
+ return event.endgame.unique_pid;
}
/**
* Returns the process event's parent pid
*/
-export function uniqueParentPidForProcess(event: ProcessEvent) {
- return event.data_buffer.source_id;
+export function uniqueParentPidForProcess(event: LegacyEndpointEvent) {
+ return event.endgame.unique_ppid;
}
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event_test_helpers.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event_test_helpers.ts
index 9a6f19adcc101..e88837d325108 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event_test_helpers.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/models/process_event_test_helpers.ts
@@ -4,33 +4,46 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ProcessEvent } from '../types';
-type DeepPartial = { [K in keyof T]?: DeepPartial };
+import { LegacyEndpointEvent } from '../../../../common/types';
+type DeepPartial = { [K in keyof T]?: DeepPartial };
/**
* Creates a mock process event given the 'parts' argument, which can
* include all or some process event fields as determined by the ProcessEvent type.
* The only field that must be provided is the event's 'node_id' field.
* The other fields are populated by the function unless provided in 'parts'
*/
-export function mockProcessEvent(
- parts: {
- data_buffer: { node_id: ProcessEvent['data_buffer']['node_id'] };
- } & DeepPartial
-): ProcessEvent {
- const { data_buffer: dataBuffer } = parts;
+export function mockProcessEvent(parts: {
+ endgame: {
+ unique_pid: LegacyEndpointEvent['endgame']['unique_pid'];
+ unique_ppid?: LegacyEndpointEvent['endgame']['unique_ppid'];
+ process_name?: LegacyEndpointEvent['endgame']['process_name'];
+ event_subtype_full?: LegacyEndpointEvent['endgame']['event_subtype_full'];
+ event_type_full?: LegacyEndpointEvent['endgame']['event_type_full'];
+ } & DeepPartial;
+}): LegacyEndpointEvent {
+ const { endgame: dataBuffer } = parts;
return {
- event_timestamp: 1,
- event_type: 1,
- machine_id: '',
- ...parts,
- data_buffer: {
- timestamp_utc: '2019-09-24 01:47:47Z',
+ endgame: {
+ ...dataBuffer,
+ event_timestamp: 1,
+ event_type: 1,
+ unique_ppid: 0,
+ unique_pid: 1,
+ machine_id: '',
event_subtype_full: 'creation_event',
event_type_full: 'process_event',
process_name: '',
process_path: '',
- ...dataBuffer,
+ timestamp_utc: '',
+ serial_event_id: 1,
+ },
+ '@timestamp': 1582233383000,
+ agent: {
+ type: '',
+ id: '',
+ version: '',
},
+ ...parts,
};
}
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts
index 25f196c76a290..ecba0ec404d44 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts
@@ -3,9 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { ProcessEvent } from '../types';
import { CameraAction } from './camera';
import { DataAction } from './data';
+import { LegacyEndpointEvent } from '../../../../common/types';
/**
* When the user wants to bring a process node front-and-center on the map.
@@ -16,7 +16,7 @@ interface UserBroughtProcessIntoView {
/**
* Used to identify the process node that should be brought into view.
*/
- readonly process: ProcessEvent;
+ readonly process: LegacyEndpointEvent;
/**
* The time (since epoch in milliseconds) when the action was dispatched.
*/
@@ -24,4 +24,29 @@ interface UserBroughtProcessIntoView {
};
}
-export type ResolverAction = CameraAction | DataAction | UserBroughtProcessIntoView;
+/**
+ * Used when the alert list selects an alert and the flyout shows resolver.
+ */
+interface UserChangedSelectedEvent {
+ readonly type: 'userChangedSelectedEvent';
+ readonly payload: {
+ /**
+ * Optional because they could have unselected the event.
+ */
+ selectedEvent?: LegacyEndpointEvent;
+ };
+}
+
+/**
+ * Triggered by middleware when the data for resolver needs to be loaded. Used to set state in redux to 'loading'.
+ */
+interface AppRequestedResolverData {
+ readonly type: 'appRequestedResolverData';
+}
+
+export type ResolverAction =
+ | CameraAction
+ | DataAction
+ | UserBroughtProcessIntoView
+ | UserChangedSelectedEvent
+ | AppRequestedResolverData;
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/__snapshots__/graphing.test.ts.snap b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/__snapshots__/graphing.test.ts.snap
index 1dc17054b9f47..b88652097eb5c 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/__snapshots__/graphing.test.ts.snap
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/__snapshots__/graphing.test.ts.snap
@@ -12,17 +12,18 @@ Object {
"edgeLineSegments": Array [],
"processNodePositions": Map {
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 0,
"process_name": "",
- "process_path": "",
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 0,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
0,
-0.8164965809277259,
@@ -167,136 +168,137 @@ Object {
],
"processNodePositions": Map {
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 0,
"process_name": "",
- "process_path": "",
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 0,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
0,
-0.8164965809277259,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "already_running",
"event_type_full": "process_event",
- "node_id": 1,
- "process_name": "",
- "process_path": "",
- "source_id": 0,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 1,
+ "unique_ppid": 0,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
0,
-82.46615467370032,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 2,
- "process_name": "",
- "process_path": "",
- "source_id": 0,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 2,
+ "unique_ppid": 0,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
141.4213562373095,
-0.8164965809277259,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 3,
- "process_name": "",
- "process_path": "",
- "source_id": 1,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 3,
+ "unique_ppid": 1,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
35.35533905932738,
-143.70339824327976,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 4,
- "process_name": "",
- "process_path": "",
- "source_id": 1,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 4,
+ "unique_ppid": 1,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
106.06601717798213,
-102.87856919689347,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 5,
- "process_name": "",
- "process_path": "",
- "source_id": 2,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 5,
+ "unique_ppid": 2,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
176.7766952966369,
-62.053740150507174,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 6,
- "process_name": "",
- "process_path": "",
- "source_id": 2,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 6,
+ "unique_ppid": 2,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
247.48737341529164,
-21.228911104120883,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 7,
- "process_name": "",
- "process_path": "",
- "source_id": 6,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 7,
+ "unique_ppid": 6,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
318.1980515339464,
-62.05374015050717,
@@ -321,34 +323,35 @@ Object {
],
"processNodePositions": Map {
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "creation_event",
"event_type_full": "process_event",
- "node_id": 0,
"process_name": "",
- "process_path": "",
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 0,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
0,
-0.8164965809277259,
],
Object {
- "data_buffer": Object {
+ "@timestamp": 1582233383000,
+ "agent": Object {
+ "id": "",
+ "type": "",
+ "version": "",
+ },
+ "endgame": Object {
"event_subtype_full": "already_running",
"event_type_full": "process_event",
- "node_id": 1,
- "process_name": "",
- "process_path": "",
- "source_id": 0,
- "timestamp_utc": "2019-09-24 01:47:47Z",
+ "unique_pid": 1,
+ "unique_ppid": 0,
},
- "event_timestamp": 1,
- "event_type": 1,
- "machine_id": "",
} => Array [
70.71067811865476,
-41.641325627314025,
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts
index 900b9bda571da..f34d7c08ce08c 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts
@@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ProcessEvent } from '../../types';
+import { LegacyEndpointEvent } from '../../../../../common/types';
interface ServerReturnedResolverData {
readonly type: 'serverReturnedResolverData';
readonly payload: {
readonly data: {
readonly result: {
- readonly search_results: readonly ProcessEvent[];
+ readonly search_results: readonly LegacyEndpointEvent[];
};
};
};
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/graphing.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/graphing.test.ts
index fac70433f14b2..f01136fe20ebf 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/graphing.test.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/graphing.test.ts
@@ -7,20 +7,21 @@
import { Store, createStore } from 'redux';
import { DataAction } from './action';
import { dataReducer } from './reducer';
-import { DataState, ProcessEvent } from '../../types';
+import { DataState } from '../../types';
+import { LegacyEndpointEvent } from '../../../../../common/types';
import { graphableProcesses, processNodePositionsAndEdgeLineSegments } from './selectors';
import { mockProcessEvent } from '../../models/process_event_test_helpers';
describe('resolver graph layout', () => {
- let processA: ProcessEvent;
- let processB: ProcessEvent;
- let processC: ProcessEvent;
- let processD: ProcessEvent;
- let processE: ProcessEvent;
- let processF: ProcessEvent;
- let processG: ProcessEvent;
- let processH: ProcessEvent;
- let processI: ProcessEvent;
+ let processA: LegacyEndpointEvent;
+ let processB: LegacyEndpointEvent;
+ let processC: LegacyEndpointEvent;
+ let processD: LegacyEndpointEvent;
+ let processE: LegacyEndpointEvent;
+ let processF: LegacyEndpointEvent;
+ let processG: LegacyEndpointEvent;
+ let processH: LegacyEndpointEvent;
+ let processI: LegacyEndpointEvent;
let store: Store;
beforeEach(() => {
@@ -37,75 +38,75 @@ describe('resolver graph layout', () => {
*
*/
processA = mockProcessEvent({
- data_buffer: {
+ endgame: {
process_name: '',
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 0,
+ unique_pid: 0,
},
});
processB = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'already_running',
- node_id: 1,
- source_id: 0,
+ unique_pid: 1,
+ unique_ppid: 0,
},
});
processC = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 2,
- source_id: 0,
+ unique_pid: 2,
+ unique_ppid: 0,
},
});
processD = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 3,
- source_id: 1,
+ unique_pid: 3,
+ unique_ppid: 1,
},
});
processE = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 4,
- source_id: 1,
+ unique_pid: 4,
+ unique_ppid: 1,
},
});
processF = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 5,
- source_id: 2,
+ unique_pid: 5,
+ unique_ppid: 2,
},
});
processG = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 6,
- source_id: 2,
+ unique_pid: 6,
+ unique_ppid: 2,
},
});
processH = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'creation_event',
- node_id: 7,
- source_id: 6,
+ unique_pid: 7,
+ unique_ppid: 6,
},
});
processI = mockProcessEvent({
- data_buffer: {
+ endgame: {
event_type_full: 'process_event',
event_subtype_full: 'termination_event',
- node_id: 8,
- source_id: 0,
+ unique_pid: 8,
+ unique_ppid: 0,
},
});
store = createStore(dataReducer, undefined);
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts
index 848d814808bac..a3184389a794e 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts
@@ -6,11 +6,11 @@
import { Reducer } from 'redux';
import { DataState, ResolverAction } from '../../types';
-import { sampleData } from './sample';
function initialState(): DataState {
return {
- results: sampleData.data.result.search_results,
+ results: [],
+ isLoading: false,
};
}
@@ -24,6 +24,12 @@ export const dataReducer: Reducer = (state = initialS
return {
...state,
results: search_results,
+ isLoading: false,
+ };
+ } else if (action.type === 'appRequestedResolverData') {
+ return {
+ ...state,
+ isLoading: true,
};
} else {
return state;
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts
index 75b477dd7c7fc..304abbb06880b 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts
@@ -7,7 +7,6 @@
import { createSelector } from 'reselect';
import {
DataState,
- ProcessEvent,
IndexedProcessTree,
ProcessWidths,
ProcessPositions,
@@ -15,6 +14,7 @@ import {
ProcessWithWidthMetadata,
Matrix3,
} from '../../types';
+import { LegacyEndpointEvent } from '../../../../../common/types';
import { Vector2 } from '../../types';
import { add as vector2Add, applyMatrix3 } from '../../lib/vector2';
import { isGraphableProcess } from '../../models/process_event';
@@ -29,6 +29,10 @@ import {
const unit = 100;
const distanceBetweenNodesInUnits = 1;
+export function isLoading(state: DataState) {
+ return state.isLoading;
+}
+
/**
* An isometric projection is a method for representing three dimensional objects in 2 dimensions.
* More information about isometric projections can be found here https://en.wikipedia.org/wiki/Isometric_projection.
@@ -108,7 +112,7 @@ export const graphableProcesses = createSelector(
*
*/
function widthsOfProcessSubtrees(indexedProcessTree: IndexedProcessTree): ProcessWidths {
- const widths = new Map();
+ const widths = new Map();
if (size(indexedProcessTree) === 0) {
return widths;
@@ -309,13 +313,13 @@ function processPositions(
indexedProcessTree: IndexedProcessTree,
widths: ProcessWidths
): ProcessPositions {
- const positions = new Map();
+ const positions = new Map();
/**
* This algorithm iterates the tree in level order. It keeps counters that are reset for each parent.
* By keeping track of the last parent node, we can know when we are dealing with a new set of siblings and
* reset the counters.
*/
- let lastProcessedParentNode: ProcessEvent | undefined;
+ let lastProcessedParentNode: LegacyEndpointEvent | undefined;
/**
* Nodes are positioned relative to their siblings. We walk this in level order, so we handle
* children left -> right.
@@ -420,7 +424,7 @@ export const processNodePositionsAndEdgeLineSegments = createSelector(
* Transform the positions of nodes and edges so they seem like they are on an isometric grid.
*/
const transformedEdgeLineSegments: EdgeLineSegment[] = [];
- const transformedPositions = new Map();
+ const transformedPositions = new Map();
for (const [processEvent, position] of positions) {
transformedPositions.set(processEvent, applyMatrix3(position, isometricTransformMatrix));
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/index.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/index.ts
index b17572bbc4ab4..2a20c73347348 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/index.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/index.ts
@@ -6,17 +6,21 @@
import { createStore, applyMiddleware, Store } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
+import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public';
import { ResolverAction, ResolverState } from '../types';
+import { EndpointPluginServices } from '../../../plugin';
import { resolverReducer } from './reducer';
+import { resolverMiddlewareFactory } from './middleware';
-export const storeFactory = (): { store: Store } => {
+export const storeFactory = (
+ context?: KibanaReactContextValue
+): { store: Store } => {
const actionsBlacklist: Array = ['userMovedPointer'];
const composeEnhancers = composeWithDevTools({
name: 'Resolver',
actionsBlacklist,
});
-
- const middlewareEnhancer = applyMiddleware();
+ const middlewareEnhancer = applyMiddleware(resolverMiddlewareFactory(context));
const store = createStore(resolverReducer, composeEnhancers(middlewareEnhancer));
return {
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/methods.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/methods.ts
index 8808160c9c631..9f06643626f50 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/methods.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/methods.ts
@@ -6,7 +6,8 @@
import { animatePanning } from './camera/methods';
import { processNodePositionsAndEdgeLineSegments } from './selectors';
-import { ResolverState, ProcessEvent } from '../types';
+import { ResolverState } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
const animationDuration = 1000;
@@ -16,7 +17,7 @@ const animationDuration = 1000;
export function animateProcessIntoView(
state: ResolverState,
startTime: number,
- process: ProcessEvent
+ process: LegacyEndpointEvent
): ResolverState {
const { processNodePositions } = processNodePositionsAndEdgeLineSegments(state);
const position = processNodePositions.get(process);
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts
new file mode 100644
index 0000000000000..900aece60618d
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Dispatch, MiddlewareAPI } from 'redux';
+import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public';
+import { EndpointPluginServices } from '../../../plugin';
+import { ResolverState, ResolverAction } from '../types';
+
+type MiddlewareFactory = (
+ context?: KibanaReactContextValue
+) => (
+ api: MiddlewareAPI, S>
+) => (next: Dispatch) => (action: ResolverAction) => unknown;
+
+export const resolverMiddlewareFactory: MiddlewareFactory = context => {
+ return api => next => async (action: ResolverAction) => {
+ next(action);
+ if (action.type === 'userChangedSelectedEvent') {
+ if (context?.services.http) {
+ api.dispatch({ type: 'appRequestedResolverData' });
+ const uniquePid = action.payload.selectedEvent?.endgame?.unique_pid;
+ const legacyEndpointID = action.payload.selectedEvent?.agent?.id;
+ const [{ lifecycle }, { children }, { events: relatedEvents }] = await Promise.all([
+ context.services.http.get(`/api/endpoint/resolver/${uniquePid}`, {
+ query: { legacyEndpointID },
+ }),
+ context.services.http.get(`/api/endpoint/resolver/${uniquePid}/children`, {
+ query: { legacyEndpointID },
+ }),
+ context.services.http.get(`/api/endpoint/resolver/${uniquePid}/related`, {
+ query: { legacyEndpointID },
+ }),
+ ]);
+ const response = [...lifecycle, ...children, ...relatedEvents];
+ api.dispatch({
+ type: 'serverReturnedResolverData',
+ payload: { data: { result: { search_results: response } } },
+ });
+ }
+ }
+ };
+};
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts
index 25d08a8c347ed..708eb684ebd3e 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts
@@ -68,6 +68,11 @@ function dataStateSelector(state: ResolverState) {
return state.data;
}
+/**
+ * Whether or not the resolver is pending fetching data
+ */
+export const isLoading = composeSelectors(dataStateSelector, dataSelectors.isLoading);
+
/**
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
* concern-specific selector. `selector` should return the concern-specific state.
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts
index 6c6936d377dea..4c2a1ea5ac21f 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts
@@ -8,6 +8,7 @@ import { Store } from 'redux';
import { ResolverAction } from './store/actions';
export { ResolverAction } from './store/actions';
+import { LegacyEndpointEvent } from '../../../common/types';
/**
* Redux state for the Resolver feature. Properties on this interface are populated via multiple reducers using redux's `combineReducers`.
@@ -114,7 +115,8 @@ export type CameraState = {
* State for `data` reducer which handles receiving Resolver data from the backend.
*/
export interface DataState {
- readonly results: readonly ProcessEvent[];
+ readonly results: readonly LegacyEndpointEvent[];
+ isLoading: boolean;
}
export type Vector2 = readonly [number, number];
@@ -182,21 +184,21 @@ export interface IndexedProcessTree {
/**
* Map of ID to a process's children
*/
- idToChildren: Map;
+ idToChildren: Map;
/**
* Map of ID to process
*/
- idToProcess: Map;
+ idToProcess: Map;
}
/**
* A map of ProcessEvents (representing process nodes) to the 'width' of their subtrees as calculated by `widthsOfProcessSubtrees`
*/
-export type ProcessWidths = Map;
+export type ProcessWidths = Map;
/**
* Map of ProcessEvents (representing process nodes) to their positions. Calculated by `processPositions`
*/
-export type ProcessPositions = Map;
+export type ProcessPositions = Map;
/**
* An array of vectors2 forming an polyline. Used to connect process nodes in the graph.
*/
@@ -206,11 +208,11 @@ export type EdgeLineSegment = Vector2[];
* Used to provide precalculated info from `widthsOfProcessSubtrees`. These 'width' values are used in the layout of the graph.
*/
export type ProcessWithWidthMetadata = {
- process: ProcessEvent;
+ process: LegacyEndpointEvent;
width: number;
} & (
| {
- parent: ProcessEvent;
+ parent: LegacyEndpointEvent;
parentWidth: number;
isOnlyChild: boolean;
firstChildWidth: number;
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx
index d71a4d87b7eab..52a0872f269f5 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx
@@ -4,15 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { useSelector } from 'react-redux';
+import React, { useLayoutEffect } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
import styled from 'styled-components';
+import { EuiLoadingSpinner } from '@elastic/eui';
import * as selectors from '../store/selectors';
import { EdgeLine } from './edge_line';
import { Panel } from './panel';
import { GraphControls } from './graph_controls';
import { ProcessEventDot } from './process_event_dot';
import { useCamera } from './use_camera';
+import { ResolverAction } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
const StyledPanel = styled(Panel)`
position: absolute;
@@ -31,35 +34,57 @@ const StyledGraphControls = styled(GraphControls)`
`;
export const Resolver = styled(
- React.memo(function Resolver({ className }: { className?: string }) {
+ React.memo(function Resolver({
+ className,
+ selectedEvent,
+ }: {
+ className?: string;
+ selectedEvent?: LegacyEndpointEvent;
+ }) {
const { processNodePositions, edgeLineSegments } = useSelector(
selectors.processNodePositionsAndEdgeLineSegments
);
+ const dispatch: (action: ResolverAction) => unknown = useDispatch();
const { projectionMatrix, ref, onMouseDown } = useCamera();
+ const isLoading = useSelector(selectors.isLoading);
+ useLayoutEffect(() => {
+ dispatch({
+ type: 'userChangedSelectedEvent',
+ payload: { selectedEvent },
+ });
+ }, [dispatch, selectedEvent]);
return (
-
- {Array.from(processNodePositions).map(([processEvent, position], index) => (
-
- ))}
- {edgeLineSegments.map(([startPosition, endPosition], index) => (
-
- ))}
-
-
-
+ {isLoading ? (
+
+
+
+ ) : (
+ <>
+
+ {Array.from(processNodePositions).map(([processEvent, position], index) => (
+
+ ))}
+ {edgeLineSegments.map(([startPosition, endPosition], index) => (
+
+ ))}
+
+
+
+ >
+ )}
);
})
@@ -72,6 +97,12 @@ export const Resolver = styled(
display: flex;
flex-grow: 1;
}
+ .loading-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-grow: 1;
+ }
/**
* The placeholder components use absolute positioning.
*/
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/panel.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/panel.tsx
index c75b73b4bceaf..84c299698bb32 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/panel.tsx
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/panel.tsx
@@ -11,7 +11,7 @@ import euiVars from '@elastic/eui/dist/eui_theme_light.json';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { SideEffectContext } from './side_effect_context';
-import { ProcessEvent } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
import { useResolverDispatch } from './use_resolver_dispatch';
import * as selectors from '../store/selectors';
@@ -38,7 +38,7 @@ export const Panel = memo(function Event({ className }: { className?: string })
interface ProcessTableView {
name: string;
timestamp?: Date;
- event: ProcessEvent;
+ event: LegacyEndpointEvent;
}
const { processNodePositions } = useSelector(selectors.processNodePositionsAndEdgeLineSegments);
@@ -47,11 +47,16 @@ export const Panel = memo(function Event({ className }: { className?: string })
const processTableView: ProcessTableView[] = useMemo(
() =>
[...processNodePositions.keys()].map(processEvent => {
- const { data_buffer } = processEvent;
- const date = new Date(data_buffer.timestamp_utc);
+ let dateTime;
+ if (processEvent.endgame.timestamp_utc) {
+ const date = new Date(processEvent.endgame.timestamp_utc);
+ if (isFinite(date.getTime())) {
+ dateTime = date;
+ }
+ }
return {
- name: data_buffer.process_name,
- timestamp: isFinite(date.getTime()) ? date : undefined,
+ name: processEvent.endgame.process_name ? processEvent.endgame.process_name : '',
+ timestamp: dateTime,
event: processEvent,
};
}),
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx
index 384fbf90ed984..034780c7ba14c 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx
@@ -7,7 +7,8 @@
import React from 'react';
import styled from 'styled-components';
import { applyMatrix3 } from '../lib/vector2';
-import { Vector2, ProcessEvent, Matrix3 } from '../types';
+import { Vector2, Matrix3 } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
/**
* A placeholder view for a process node.
@@ -31,7 +32,7 @@ export const ProcessEventDot = styled(
/**
* An event which contains details about the process node.
*/
- event: ProcessEvent;
+ event: LegacyEndpointEvent;
/**
* projectionMatrix which can be used to convert `position` to screen coordinates.
*/
@@ -48,7 +49,7 @@ export const ProcessEventDot = styled(
};
return (
- name: {event.data_buffer.process_name}
+ name: {event.endgame.process_name}
x: {position[0]}
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx
index f4abb51f062f2..1948c6cae505b 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx
@@ -10,16 +10,12 @@ import { useCamera } from './use_camera';
import { Provider } from 'react-redux';
import * as selectors from '../store/selectors';
import { storeFactory } from '../store';
-import {
- Matrix3,
- ResolverAction,
- ResolverStore,
- ProcessEvent,
- SideEffectSimulator,
-} from '../types';
+import { Matrix3, ResolverAction, ResolverStore, SideEffectSimulator } from '../types';
+import { LegacyEndpointEvent } from '../../../../common/types';
import { SideEffectContext } from './side_effect_context';
import { applyMatrix3 } from '../lib/vector2';
import { sideEffectSimulator } from './side_effect_simulator';
+import { mockProcessEvent } from '../models/process_event_test_helpers';
describe('useCamera on an unpainted element', () => {
let element: HTMLElement;
@@ -28,6 +24,7 @@ describe('useCamera on an unpainted element', () => {
let reactRenderResult: RenderResult;
let store: ResolverStore;
let simulator: SideEffectSimulator;
+
beforeEach(async () => {
({ store } = storeFactory());
@@ -136,17 +133,45 @@ describe('useCamera on an unpainted element', () => {
expect(simulator.mock.requestAnimationFrame).not.toHaveBeenCalled();
});
describe('when the camera begins animation', () => {
- let process: ProcessEvent;
+ let process: LegacyEndpointEvent;
beforeEach(() => {
- // At this time, processes are provided via mock data. In the future, this test will have to provide those mocks.
- const processes: ProcessEvent[] = [
+ const events: LegacyEndpointEvent[] = [];
+ const numberOfEvents: number = Math.floor(Math.random() * 10 + 1);
+
+ for (let index = 0; index < numberOfEvents; index++) {
+ const uniquePpid = index === 0 ? undefined : index - 1;
+ events.push(
+ mockProcessEvent({
+ endgame: {
+ unique_pid: index,
+ unique_ppid: uniquePpid,
+ event_type_full: 'process_event',
+ event_subtype_full: 'creation_event',
+ },
+ })
+ );
+ }
+ const serverResponseAction: ResolverAction = {
+ type: 'serverReturnedResolverData',
+ payload: {
+ data: {
+ result: {
+ search_results: events,
+ },
+ },
+ },
+ };
+ act(() => {
+ store.dispatch(serverResponseAction);
+ });
+ const processes: LegacyEndpointEvent[] = [
...selectors
.processNodePositionsAndEdgeLineSegments(store.getState())
.processNodePositions.keys(),
];
process = processes[processes.length - 1];
simulator.controls.time = 0;
- const action: ResolverAction = {
+ const cameraAction: ResolverAction = {
type: 'userBroughtProcessIntoView',
payload: {
time: simulator.controls.time,
@@ -154,7 +179,7 @@ describe('useCamera on an unpainted element', () => {
},
};
act(() => {
- store.dispatch(action);
+ store.dispatch(cameraAction);
});
});
diff --git a/x-pack/plugins/endpoint/public/plugin.ts b/x-pack/plugins/endpoint/public/plugin.ts
index 355364253b2a5..0e10fe680e9f0 100644
--- a/x-pack/plugins/endpoint/public/plugin.ts
+++ b/x-pack/plugins/endpoint/public/plugin.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
+import { Plugin, CoreSetup, AppMountParameters, CoreStart } from 'kibana/public';
import { IEmbeddableSetup } from 'src/plugins/embeddable/public';
import { i18n } from '@kbn/i18n';
import { ResolverEmbeddableFactory } from './embeddables/resolver';
@@ -17,6 +17,15 @@ export interface EndpointPluginSetupDependencies {
export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface
+/**
+ * Functionality that the endpoint plugin uses from core.
+ */
+export interface EndpointPluginServices extends Partial {
+ http: CoreStart['http'];
+ overlays: CoreStart['overlays'] | undefined;
+ notifications: CoreStart['notifications'] | undefined;
+}
+
export class EndpointPlugin
implements
Plugin<
diff --git a/x-pack/plugins/endpoint/server/routes/resolver/queries/children.test.ts b/x-pack/plugins/endpoint/server/routes/resolver/queries/children.test.ts
index 2dd2e0c2d1d5f..08a906e2884d6 100644
--- a/x-pack/plugins/endpoint/server/routes/resolver/queries/children.test.ts
+++ b/x-pack/plugins/endpoint/server/routes/resolver/queries/children.test.ts
@@ -8,7 +8,7 @@ import { EndpointAppConstants } from '../../../../common/types';
describe('children events query', () => {
it('generates the correct legacy queries', () => {
- const timestamp = new Date();
+ const timestamp = new Date().getTime();
expect(
new ChildrenQuery('awesome-id', { size: 1, timestamp, eventID: 'foo' }).build('5')
).toStrictEqual({
@@ -38,7 +38,7 @@ describe('children events query', () => {
},
},
},
- search_after: [timestamp.getTime(), 'foo'],
+ search_after: [timestamp, 'foo'],
size: 1,
sort: [{ '@timestamp': 'asc' }, { 'endgame.serial_event_id': 'asc' }],
},
@@ -47,7 +47,7 @@ describe('children events query', () => {
});
it('generates the correct non-legacy queries', () => {
- const timestamp = new Date();
+ const timestamp = new Date().getTime();
expect(
new ChildrenQuery(undefined, { size: 1, timestamp, eventID: 'bar' }).build('baz')
@@ -84,7 +84,7 @@ describe('children events query', () => {
},
},
},
- search_after: [timestamp.getTime(), 'bar'],
+ search_after: [timestamp, 'bar'],
size: 1,
sort: [{ '@timestamp': 'asc' }, { 'event.id': 'asc' }],
},
diff --git a/x-pack/plugins/endpoint/server/routes/resolver/queries/related_events.test.ts b/x-pack/plugins/endpoint/server/routes/resolver/queries/related_events.test.ts
index 8ef680a168310..a91c87274b8dd 100644
--- a/x-pack/plugins/endpoint/server/routes/resolver/queries/related_events.test.ts
+++ b/x-pack/plugins/endpoint/server/routes/resolver/queries/related_events.test.ts
@@ -8,7 +8,7 @@ import { EndpointAppConstants } from '../../../../common/types';
describe('related events query', () => {
it('generates the correct legacy queries', () => {
- const timestamp = new Date();
+ const timestamp = new Date().getTime();
expect(
new RelatedEventsQuery('awesome-id', { size: 1, timestamp, eventID: 'foo' }).build('5')
).toStrictEqual({
@@ -39,7 +39,7 @@ describe('related events query', () => {
},
},
},
- search_after: [timestamp.getTime(), 'foo'],
+ search_after: [timestamp, 'foo'],
size: 1,
sort: [{ '@timestamp': 'asc' }, { 'endgame.serial_event_id': 'asc' }],
},
@@ -48,7 +48,7 @@ describe('related events query', () => {
});
it('generates the correct non-legacy queries', () => {
- const timestamp = new Date();
+ const timestamp = new Date().getTime();
expect(
new RelatedEventsQuery(undefined, { size: 1, timestamp, eventID: 'bar' }).build('baz')
@@ -86,7 +86,7 @@ describe('related events query', () => {
},
},
},
- search_after: [timestamp.getTime(), 'bar'],
+ search_after: [timestamp, 'bar'],
size: 1,
sort: [{ '@timestamp': 'asc' }, { 'event.id': 'asc' }],
},
diff --git a/x-pack/plugins/endpoint/server/routes/resolver/utils/pagination.ts b/x-pack/plugins/endpoint/server/routes/resolver/utils/pagination.ts
index 33eb698479308..5a64f3ff9ddb6 100644
--- a/x-pack/plugins/endpoint/server/routes/resolver/utils/pagination.ts
+++ b/x-pack/plugins/endpoint/server/routes/resolver/utils/pagination.ts
@@ -11,12 +11,12 @@ import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/public
export interface PaginationParams {
size: number;
- timestamp?: Date;
+ timestamp?: number;
eventID?: string;
}
interface PaginationCursor {
- timestamp: Date;
+ timestamp: number;
eventID: string;
}
@@ -35,7 +35,7 @@ function urlDecodeCursor(value: string): PaginationCursor {
const { timestamp, eventID } = JSON.parse(data);
// take some extra care to only grab the things we want
// convert the timestamp string to date object
- return { timestamp: new Date(timestamp), eventID };
+ return { timestamp, eventID };
}
export function getPaginationParams(limit: number, after?: string): PaginationParams {
@@ -62,7 +62,7 @@ export function paginate(pagination: PaginationParams, field: string, query: Jso
query.aggs = { total: { value_count: { field } } };
query.size = size;
if (timestamp && eventID) {
- query.search_after = [timestamp.getTime(), eventID] as Array;
+ query.search_after = [timestamp, eventID] as Array;
}
return query;
}
diff --git a/x-pack/plugins/remote_clusters/kibana.json b/x-pack/plugins/remote_clusters/kibana.json
index 27ae6802966dd..609d0f67f2c7b 100644
--- a/x-pack/plugins/remote_clusters/kibana.json
+++ b/x-pack/plugins/remote_clusters/kibana.json
@@ -7,7 +7,8 @@
],
"requiredPlugins": [
"licensing",
- "management"
+ "management",
+ "indexManagement"
],
"optionalPlugins": [
"usageCollection"
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index b99a54160bb65..a97cf608abc71 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5734,7 +5734,6 @@
"xpack.graph.listing.table.entityNamePlural": "グラフ",
"xpack.graph.listing.table.titleColumnName": "タイトル",
"xpack.graph.loadWorkspace.missingIndexPatternErrorMessage": "インデックスパターンが見つかりませんでした",
- "xpack.graph.missingWorkspaceErrorMessage": "ワークスペースがありません",
"xpack.graph.newGraphTitle": "保存されていないグラフ",
"xpack.graph.noDataSourceNotificationMessageText": "データソースが見つかりませんでした。{managementIndexPatternsLink} に移動して Elasticsearch インデックスのインデックスパターンを作成してください。",
"xpack.graph.noDataSourceNotificationMessageText.managementIndexPatternLinkText": "管理>インデックスパターン",
@@ -10987,7 +10986,6 @@
"xpack.siem.editDataProvider.doesNotExistLabel": "存在しません",
"xpack.siem.editDataProvider.existsLabel": "存在する",
"xpack.siem.editDataProvider.fieldLabel": "フィールド",
- "xpack.siem.editDataProvider.fieldPlaceholder": "フィールドを選択",
"xpack.siem.editDataProvider.isLabel": "が",
"xpack.siem.editDataProvider.isNotLabel": "is not",
"xpack.siem.editDataProvider.operatorLabel": "演算子",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index bae8fef5ff280..e6055680e1240 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -5734,7 +5734,6 @@
"xpack.graph.listing.table.entityNamePlural": "图表",
"xpack.graph.listing.table.titleColumnName": "标题",
"xpack.graph.loadWorkspace.missingIndexPatternErrorMessage": "未找到索引模式",
- "xpack.graph.missingWorkspaceErrorMessage": "缺少工作空间",
"xpack.graph.newGraphTitle": "未保存图表",
"xpack.graph.noDataSourceNotificationMessageText": "未找到数据源。前往 {managementIndexPatternsLink},为您的 Elasticsearch 索引创建索引模式。",
"xpack.graph.noDataSourceNotificationMessageText.managementIndexPatternLinkText": "管理 > 索引模式",
@@ -10987,7 +10986,6 @@
"xpack.siem.editDataProvider.doesNotExistLabel": "不存在",
"xpack.siem.editDataProvider.existsLabel": "存在",
"xpack.siem.editDataProvider.fieldLabel": "字段",
- "xpack.siem.editDataProvider.fieldPlaceholder": "选择字段",
"xpack.siem.editDataProvider.isLabel": "是",
"xpack.siem.editDataProvider.isNotLabel": "不是",
"xpack.siem.editDataProvider.operatorLabel": "运算符",
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts
new file mode 100644
index 0000000000000..b7bc197fbd162
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ AccessForbidden,
+ IndexNotFound,
+ CannotCreateIndex,
+ ReindexTaskCannotBeDeleted,
+ ReindexTaskFailed,
+ ReindexAlreadyInProgress,
+ MultipleReindexJobsFound,
+} from './error_symbols';
+
+export class ReindexError extends Error {
+ constructor(message: string, public readonly symbol: symbol) {
+ super(message);
+ }
+}
+
+export const createErrorFactory = (symbol: symbol) => (message: string) => {
+ return new ReindexError(message, symbol);
+};
+
+export const error = {
+ indexNotFound: createErrorFactory(IndexNotFound),
+ accessForbidden: createErrorFactory(AccessForbidden),
+ cannotCreateIndex: createErrorFactory(CannotCreateIndex),
+ reindexTaskFailed: createErrorFactory(ReindexTaskFailed),
+ reindexTaskCannotBeDeleted: createErrorFactory(ReindexTaskCannotBeDeleted),
+ reindexAlreadyInProgress: createErrorFactory(ReindexAlreadyInProgress),
+ multipleReindexJobsFound: createErrorFactory(MultipleReindexJobsFound),
+};
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts
new file mode 100644
index 0000000000000..9e49d280d1be2
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const AccessForbidden = Symbol('AccessForbidden');
+export const IndexNotFound = Symbol('IndexNotFound');
+export const CannotCreateIndex = Symbol('CannotCreateIndex');
+
+export const ReindexTaskFailed = Symbol('ReindexTaskFailed');
+export const ReindexTaskCannotBeDeleted = Symbol('ReindexTaskCannotBeDeleted');
+export const ReindexAlreadyInProgress = Symbol('ReindexAlreadyInProgress');
+
+export const MultipleReindexJobsFound = Symbol('MultipleReindexJobsFound');
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
index 8f1df5b34372b..b274743bdf279 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
@@ -3,8 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
-import Boom from 'boom';
import { APICaller, Logger } from 'src/core/server';
import { first } from 'rxjs/operators';
@@ -24,6 +22,8 @@ import {
import { ReindexActions } from './reindex_actions';
import { LicensingPluginSetup } from '../../../../licensing/server';
+import { error } from './error';
+
const VERSION_REGEX = new RegExp(/^([1-9]+)\.([0-9]+)\.([0-9]+)/);
const ML_INDICES = ['.ml-state', '.ml-anomalies', '.ml-config'];
const WATCHER_INDICES = ['.watches', '.triggered-watches'];
@@ -284,7 +284,7 @@ export const reindexServiceFactory = (
const flatSettings = await actions.getFlatSettings(indexName);
if (!flatSettings) {
- throw Boom.notFound(`Index ${indexName} does not exist.`);
+ throw error.indexNotFound(`Index ${indexName} does not exist.`);
}
const { settings, mappings } = transformFlatSettings(flatSettings);
@@ -298,7 +298,7 @@ export const reindexServiceFactory = (
});
if (!createIndex.acknowledged) {
- throw Boom.badImplementation(`Index could not be created: ${newIndexName}`);
+ throw error.cannotCreateIndex(`Index could not be created: ${newIndexName}`);
}
return actions.updateReindexOp(reindexOp, {
@@ -363,7 +363,7 @@ export const reindexServiceFactory = (
if (taskResponse.task.status.created < count) {
// Include the entire task result in the error message. This should be guaranteed
// to be JSON-serializable since it just came back from Elasticsearch.
- throw Boom.badData(`Reindexing failed: ${JSON.stringify(taskResponse)}`);
+ throw error.reindexTaskFailed(`Reindexing failed: ${JSON.stringify(taskResponse)}`);
}
// Update the status
@@ -380,7 +380,7 @@ export const reindexServiceFactory = (
});
if (deleteTaskResp.result !== 'deleted') {
- throw Boom.badImplementation(`Could not delete reindexing task ${taskId}`);
+ throw error.reindexTaskCannotBeDeleted(`Could not delete reindexing task ${taskId}`);
}
return reindexOp;
@@ -414,7 +414,7 @@ export const reindexServiceFactory = (
});
if (!aliasResponse.acknowledged) {
- throw Boom.badImplementation(`Index aliases could not be created.`);
+ throw error.cannotCreateIndex(`Index aliases could not be created.`);
}
return actions.updateReindexOp(reindexOp, {
@@ -520,7 +520,7 @@ export const reindexServiceFactory = (
async createReindexOperation(indexName: string) {
const indexExists = await callAsUser('indices.exists', { index: indexName });
if (!indexExists) {
- throw Boom.notFound(`Index ${indexName} does not exist in this cluster.`);
+ throw error.indexNotFound(`Index ${indexName} does not exist in this cluster.`);
}
const existingReindexOps = await actions.findReindexOperations(indexName);
@@ -533,7 +533,9 @@ export const reindexServiceFactory = (
// Delete the existing one if it failed or was cancelled to give a chance to retry.
await actions.deleteReindexOp(existingOp);
} else {
- throw Boom.badImplementation(`A reindex operation already in-progress for ${indexName}`);
+ throw error.reindexAlreadyInProgress(
+ `A reindex operation already in-progress for ${indexName}`
+ );
}
}
@@ -547,7 +549,9 @@ export const reindexServiceFactory = (
if (findResponse.total === 0) {
return null;
} else if (findResponse.total > 1) {
- throw Boom.badImplementation(`More than one reindex operation found for ${indexName}`);
+ throw error.multipleReindexJobsFound(
+ `More than one reindex operation found for ${indexName}`
+ );
}
return findResponse.saved_objects[0];
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts
index a910145474061..72c2f2c29b72e 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts
@@ -5,7 +5,12 @@
*/
import { schema } from '@kbn/config-schema';
-import { Logger, ElasticsearchServiceSetup, SavedObjectsClient } from 'src/core/server';
+import {
+ Logger,
+ ElasticsearchServiceSetup,
+ SavedObjectsClient,
+ kibanaResponseFactory,
+} from '../../../../../src/core/server';
import { ReindexStatus } from '../../common/types';
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
import { reindexServiceFactory, ReindexWorker } from '../lib/reindexing';
@@ -13,6 +18,16 @@ import { CredentialStore } from '../lib/reindexing/credential_store';
import { reindexActionsFactory } from '../lib/reindexing/reindex_actions';
import { RouteDependencies } from '../types';
import { LicensingPluginSetup } from '../../../licensing/server';
+import { ReindexError } from '../lib/reindexing/error';
+import {
+ AccessForbidden,
+ IndexNotFound,
+ CannotCreateIndex,
+ ReindexAlreadyInProgress,
+ ReindexTaskCannotBeDeleted,
+ ReindexTaskFailed,
+ MultipleReindexJobsFound,
+} from '../lib/reindexing/error_symbols';
interface CreateReindexWorker {
logger: Logger;
@@ -33,6 +48,29 @@ export function createReindexWorker({
return new ReindexWorker(savedObjects, credentialStore, adminClient, logger, licensing);
}
+const mapAnyErrorToKibanaHttpResponse = (e: any) => {
+ if (e instanceof ReindexError) {
+ switch (e.symbol) {
+ case AccessForbidden:
+ return kibanaResponseFactory.forbidden({ body: e.message });
+ case IndexNotFound:
+ return kibanaResponseFactory.notFound({ body: e.message });
+ case CannotCreateIndex:
+ case ReindexTaskCannotBeDeleted:
+ return kibanaResponseFactory.internalError({ body: e.message });
+ case ReindexTaskFailed:
+ // Bad data
+ return kibanaResponseFactory.customError({ body: e.message, statusCode: 422 });
+ case ReindexAlreadyInProgress:
+ case MultipleReindexJobsFound:
+ return kibanaResponseFactory.badRequest({ body: e.message });
+ default:
+ // nothing matched
+ }
+ }
+ return kibanaResponseFactory.internalError({ body: e });
+};
+
export function registerReindexIndicesRoutes(
{ credentialStore, router, licensing, log }: RouteDependencies,
getWorker: () => ReindexWorker
@@ -94,7 +132,7 @@ export function registerReindexIndicesRoutes(
return response.ok({ body: reindexOp.attributes });
} catch (e) {
- return response.internalError({ body: e });
+ return mapAnyErrorToKibanaHttpResponse(e);
}
}
)
@@ -150,15 +188,7 @@ export function registerReindexIndicesRoutes(
},
});
} catch (e) {
- if (!e.isBoom) {
- return response.internalError({ body: e });
- }
- return response.customError({
- body: {
- message: e.message,
- },
- statusCode: e.statusCode,
- });
+ return mapAnyErrorToKibanaHttpResponse(e);
}
}
)
@@ -201,15 +231,7 @@ export function registerReindexIndicesRoutes(
return response.ok({ body: { acknowledged: true } });
} catch (e) {
- if (!e.isBoom) {
- return response.internalError({ body: e });
- }
- return response.customError({
- body: {
- message: e.message,
- },
- statusCode: e.statusCode,
- });
+ return mapAnyErrorToKibanaHttpResponse(e);
}
}
)
diff --git a/x-pack/test/functional/apps/endpoint/policy_list.ts b/x-pack/test/functional/apps/endpoint/policy_list.ts
index 658e4dcd13e1e..382963bc2b0c7 100644
--- a/x-pack/test/functional/apps/endpoint/policy_list.ts
+++ b/x-pack/test/functional/apps/endpoint/policy_list.ts
@@ -11,10 +11,11 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
// FLAKY: https://github.com/elastic/kibana/issues/57946
- describe.skip('Endpoint Policy List', function() {
+ describe('Endpoint Policy List', function() {
this.tags(['ciGroup7']);
before(async () => {
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/policy');
+ await pageObjects.endpoint.waitForTableToHaveData('policyTable');
});
it('loads the Policy List Page', async () => {
@@ -26,7 +27,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
});
it('shows policy count total', async () => {
const policyTotal = await testSubjects.getVisibleText('policyTotalCount');
- expect(policyTotal).to.equal('0 Policies');
+ expect(policyTotal).to.equal('100 Policies');
});
it('includes policy list table', async () => {
await testSubjects.existOrFail('policyTable');
diff --git a/x-pack/test/functional/apps/maps/discover.js b/x-pack/test/functional/apps/maps/discover.js
new file mode 100644
index 0000000000000..ce33596476755
--- /dev/null
+++ b/x-pack/test/functional/apps/maps/discover.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+
+export default function({ getService, getPageObjects }) {
+ const queryBar = getService('queryBar');
+ const PageObjects = getPageObjects(['common', 'discover', 'header', 'maps', 'timePicker']);
+
+ describe('discover visualize button', () => {
+ beforeEach(async () => {
+ await PageObjects.common.navigateToApp('discover');
+ });
+
+ it('should link geo_shape fields to Maps application', async () => {
+ await PageObjects.discover.selectIndexPattern('geo_shapes*');
+ await PageObjects.discover.clickFieldListItem('geometry');
+ await PageObjects.discover.clickFieldListItemVisualize('geometry');
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.maps.waitForLayersToLoad();
+ const doesLayerExist = await PageObjects.maps.doesLayerExist('geo_shapes*');
+ expect(doesLayerExist).to.equal(true);
+ const hits = await PageObjects.maps.getHits();
+ expect(hits).to.equal('4');
+ });
+
+ it('should link geo_point fields to Maps application with time and query context', async () => {
+ await PageObjects.discover.selectIndexPattern('logstash-*');
+ await PageObjects.timePicker.setAbsoluteRange(
+ 'Sep 22, 2015 @ 00:00:00.000',
+ 'Sep 22, 2015 @ 04:00:00.000'
+ );
+ await queryBar.setQuery('machine.os.raw : "ios"');
+ await queryBar.submitQuery();
+ await PageObjects.header.waitUntilLoadingHasFinished();
+
+ await PageObjects.discover.clickFieldListItem('geo.coordinates');
+ await PageObjects.discover.clickFieldListItemVisualize('geo.coordinates');
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.maps.waitForLayersToLoad();
+ const doesLayerExist = await PageObjects.maps.doesLayerExist('logstash-*');
+ expect(doesLayerExist).to.equal(true);
+ const hits = await PageObjects.maps.getHits();
+ expect(hits).to.equal('7');
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js
index 0545fcd1b6453..e8a9d7ba54bc5 100644
--- a/x-pack/test/functional/apps/maps/index.js
+++ b/x-pack/test/functional/apps/maps/index.js
@@ -45,6 +45,7 @@ export default function({ loadTestFile, getService }) {
loadTestFile(require.resolve('./import_geojson'));
loadTestFile(require.resolve('./layer_errors'));
loadTestFile(require.resolve('./embeddable'));
+ loadTestFile(require.resolve('./discover'));
});
});
}
diff --git a/x-pack/test/functional/page_objects/endpoint_page.ts b/x-pack/test/functional/page_objects/endpoint_page.ts
index 185b95b00527d..6350f51f707f4 100644
--- a/x-pack/test/functional/page_objects/endpoint_page.ts
+++ b/x-pack/test/functional/page_objects/endpoint_page.ts
@@ -9,6 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function EndpointPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
return {
/**
@@ -58,5 +59,13 @@ export function EndpointPageProvider({ getService }: FtrProviderContext) {
)
);
},
+
+ async waitForTableToHaveData(dataTestSubj: string) {
+ await retry.waitForWithTimeout('table to have data', 2000, async () => {
+ const tableData = await this.getEndpointAppTableData(dataTestSubj);
+ if (tableData[1][0] === 'No items found') return false;
+ return true;
+ });
+ },
};
}
diff --git a/yarn.lock b/yarn.lock
index 7f38495c20f4a..e4d5dcce5bca0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4864,6 +4864,11 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
+"@types/normalize-path@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/normalize-path/-/normalize-path-3.0.0.tgz#bb5c46cab77b93350b4cf8d7ff1153f47189ae31"
+ integrity sha512-Nd8y/5t/7CRakPYiyPzr/IAfYusy1FkcZYFEAcoMZkwpJv2n4Wm+olW+e7xBdHEXhOnWdG9ddbar0gqZWS4x5Q==
+
"@types/numeral@^0.0.25":
version "0.0.25"
resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.25.tgz#b6f55062827a4787fe4ab151cf3412a468e65271"
@@ -7121,10 +7126,10 @@ aws4@^1.6.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=
-axe-core@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.3.2.tgz#7baf3c55a5cf1621534a2c38735f5a1bf2f7e1a8"
- integrity sha512-lRdxsRt7yNhqpcXQk1ao1BL73OZDzmFCWOG0mC4tGR/r14ohH2payjHwCMQjHGbBKm924eDlmG7utAGHiX/A6g==
+axe-core@^3.4.1:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.1.tgz#d8d5aaef73f003e8b766ea28bb078343f3622201"
+ integrity sha512-mwpDgPwWB+5kMHyLjlxh4w25ClJfqSxi+c6LQ4ix349TdCUctMwJNPTkhPD1qP9SYIjFgjeVpVZWCvK9oBGwCg==
axios@^0.18.0, axios@^0.18.1:
version "0.18.1"