diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index ab60d87973983..5e933efbbc61d 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -314,6 +314,8 @@ export type TimelineWithoutExternalRefs = Omit { + before(() => { + cleanKibana(); + loginAndWaitForPage(USERS_URL); + }); + + it(`renders events tab`, () => { + cy.get(EVENTS_TAB).click(); + + cy.get(EVENTS_TAB_CONTENT).should('exist'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/users/users_external_alerts_tab.spec.ts b/x-pack/plugins/security_solution/cypress/integration/users/users_external_alerts_tab.spec.ts new file mode 100644 index 0000000000000..a2b62bc892032 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/users/users_external_alerts_tab.spec.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EXTERNAL_ALERTS_TAB, + EXTERNAL_ALERTS_TAB_CONTENT, +} from '../../screens/users/user_external_alerts'; +import { cleanKibana } from '../../tasks/common'; + +import { loginAndWaitForPage } from '../../tasks/login'; + +import { USERS_URL } from '../../urls/navigation'; + +describe('Users external alerts tab', () => { + before(() => { + cleanKibana(); + loginAndWaitForPage(USERS_URL); + }); + + it(`renders external alerts tab`, () => { + cy.get(EXTERNAL_ALERTS_TAB).click(); + + cy.get(EXTERNAL_ALERTS_TAB_CONTENT).should('exist'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/users/user_events.ts b/x-pack/plugins/security_solution/cypress/screens/users/user_events.ts new file mode 100644 index 0000000000000..c2bcd30f9d1c2 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/users/user_events.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const EVENTS_TAB = '[data-test-subj="navigation-events"]'; +export const EVENTS_TAB_CONTENT = '[data-test-subj="events-viewer-panel"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/users/user_external_alerts.ts b/x-pack/plugins/security_solution/cypress/screens/users/user_external_alerts.ts new file mode 100644 index 0000000000000..bc98b3bc59f37 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/users/user_external_alerts.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const EXTERNAL_ALERTS_TAB = '[data-test-subj="navigation-externalAlerts"]'; +export const EXTERNAL_ALERTS_TAB_CONTENT = '[data-test-subj="events-viewer-panel"]'; diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx new file mode 100644 index 0000000000000..7abca14a2e55f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { TimelineId } from '../../../../common/types'; +import { HostsType } from '../../../hosts/store/model'; +import { TestProviders } from '../../mock'; +import { EventsQueryTabBody, EventsQueryTabBodyComponentProps } from './events_query_tab_body'; +import { useGlobalFullScreen } from '../../containers/use_full_screen'; +import * as tGridActions from '../../../../../timelines/public/store/t_grid/actions'; + +jest.mock('../../lib/kibana', () => { + const original = jest.requireActual('../../lib/kibana'); + + return { + ...original, + useKibana: () => ({ + services: { + ...original.useKibana().services, + cases: { + ui: { + getCasesContext: jest.fn(), + }, + }, + }, + }), + }; +}); + +const FakeStatefulEventsViewer = () =>
{'MockedStatefulEventsViewer'}
; +jest.mock('../events_viewer', () => ({ StatefulEventsViewer: FakeStatefulEventsViewer })); + +jest.mock('../../containers/use_full_screen', () => ({ + useGlobalFullScreen: jest.fn().mockReturnValue({ + globalFullScreen: true, + }), +})); + +describe('EventsQueryTabBody', () => { + const commonProps: EventsQueryTabBodyComponentProps = { + indexNames: ['test-index'], + setQuery: jest.fn(), + timelineId: TimelineId.test, + type: HostsType.page, + endDate: new Date('2000').toISOString(), + startDate: new Date('2000').toISOString(), + }; + + it('renders EventsViewer', () => { + const { queryByText } = render( + + + + ); + + expect(queryByText('MockedStatefulEventsViewer')).toBeInTheDocument(); + }); + + it('renders the matrix histogram when globalFullScreen is false', () => { + (useGlobalFullScreen as jest.Mock).mockReturnValue({ + globalFullScreen: false, + }); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('eventsHistogramQueryPanel')).toBeInTheDocument(); + }); + + it("doesn't render the matrix histogram when globalFullScreen is true", () => { + (useGlobalFullScreen as jest.Mock).mockReturnValue({ + globalFullScreen: true, + }); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('eventsHistogramQueryPanel')).not.toBeInTheDocument(); + }); + + it('deletes query when unmouting', () => { + const mockDeleteQuery = jest.fn(); + const { unmount } = render( + + + + ); + unmount(); + + expect(mockDeleteQuery).toHaveBeenCalled(); + }); + + it('initializes t-grid', () => { + const spy = jest.spyOn(tGridActions, 'initializeTGridSettings'); + render( + + + + ); + + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx similarity index 72% rename from x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx rename to x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx index 59c3322fb02ed..cfd6546470d4a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx @@ -8,27 +8,28 @@ import React, { useEffect, useMemo } from 'react'; import { useDispatch } from 'react-redux'; +import { Filter } from '@kbn/es-query'; import { TimelineId } from '../../../../common/types/timeline'; -import { StatefulEventsViewer } from '../../../common/components/events_viewer'; +import { StatefulEventsViewer } from '../events_viewer'; import { timelineActions } from '../../../timelines/store/timeline'; -import { HostsComponentsQueryProps } from './types'; -import { eventsDefaultModel } from '../../../common/components/events_viewer/default_model'; -import { - MatrixHistogramOption, - MatrixHistogramConfigs, -} from '../../../common/components/matrix_histogram/types'; -import { MatrixHistogram } from '../../../common/components/matrix_histogram'; -import { useGlobalFullScreen } from '../../../common/containers/use_full_screen'; -import * as i18n from '../translations'; +import { eventsDefaultModel } from '../events_viewer/default_model'; + +import { MatrixHistogram } from '../matrix_histogram'; +import { useGlobalFullScreen } from '../../containers/use_full_screen'; +import * as i18n from '../../../hosts/pages/translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; -import { SourcererScopeName } from '../../../common/store/sourcerer/model'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; -import { defaultCellActions } from '../../../common/lib/cell_actions/default_cell_actions'; import { getEventsHistogramLensAttributes } from '../../../common/components/visualization_actions/lens_attributes/hosts/events'; +import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions'; +import { GlobalTimeArgs } from '../../containers/use_global_time'; +import { MatrixHistogramConfigs, MatrixHistogramOption } from '../matrix_histogram/types'; +import { QueryTabBodyProps as UserQueryTabBodyProps } from '../../../users/pages/navigation/types'; +import { QueryTabBodyProps as HostQueryTabBodyProps } from '../../../hosts/pages/navigation/types'; const EVENTS_HISTOGRAM_ID = 'eventsHistogramQuery'; @@ -61,7 +62,17 @@ export const histogramConfigs: MatrixHistogramConfigs = { getLensAttributes: getEventsHistogramLensAttributes, }; -const EventsQueryTabBodyComponent: React.FC = ({ +type QueryTabBodyProps = UserQueryTabBodyProps | HostQueryTabBodyProps; + +export type EventsQueryTabBodyComponentProps = QueryTabBodyProps & { + deleteQuery?: GlobalTimeArgs['deleteQuery']; + indexNames: string[]; + pageFilters?: Filter[]; + setQuery: GlobalTimeArgs['setQuery']; + timelineId: TimelineId; +}; + +const EventsQueryTabBodyComponent: React.FC = ({ deleteQuery, endDate, filterQuery, @@ -69,6 +80,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ pageFilters, setQuery, startDate, + timelineId, }) => { const dispatch = useDispatch(); const { globalFullScreen } = useGlobalFullScreen(); @@ -78,7 +90,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ useEffect(() => { dispatch( timelineActions.initializeTGridSettings({ - id: TimelineId.hostsPageEvents, + id: timelineId, defaultColumns: eventsDefaultModel.columns.map((c) => !tGridEnabled && c.initialWidth == null ? { @@ -89,7 +101,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ ), }) ); - }, [dispatch, tGridEnabled]); + }, [dispatch, tGridEnabled, timelineId]); useEffect(() => { return () => { @@ -119,7 +131,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ defaultModel={eventsDefaultModel} end={endDate} entityType="events" - id={TimelineId.hostsPageEvents} + id={timelineId} leadingControlColumns={leadingControlColumns} pageFilters={pageFilters} renderCellValue={DefaultCellRenderer} diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 7795e76c5fbbb..52f0b1a682097 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -203,7 +203,6 @@ export const mockGlobalState: State = { [usersModel.UsersTableType.allUsers]: { activePage: 0, limit: 10, - // TODO sort: { field: RiskScoreFields.riskScore, direction: Direction.desc }, }, [usersModel.UsersTableType.anomalies]: null, [usersModel.UsersTableType.risk]: { @@ -215,11 +214,15 @@ export const mockGlobalState: State = { }, severitySelection: [], }, + [usersModel.UsersTableType.events]: { activePage: 0, limit: 10 }, + [usersModel.UsersTableType.alerts]: { activePage: 0, limit: 10 }, }, }, details: { queries: { [usersModel.UsersTableType.anomalies]: null, + [usersModel.UsersTableType.events]: { activePage: 0, limit: 10 }, + [usersModel.UsersTableType.alerts]: { activePage: 0, limit: 10 }, }, }, }, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index 891db470161d4..142f3b922f842 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -15,6 +15,7 @@ import { HostsTableType } from '../../store/model'; import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { AnomaliesHostTable } from '../../../common/components/ml/tables/anomalies_host_table'; +import { EventsQueryTabBody } from '../../../common/components/events_tab/events_query_tab_body'; import { HostDetailsTabsProps } from './types'; import { type } from './utils'; @@ -23,10 +24,10 @@ import { HostsQueryTabBody, AuthenticationsQueryTabBody, UncommonProcessQueryTabBody, - EventsQueryTabBody, HostAlertsQueryTabBody, HostRiskTabBody, } from '../navigation'; +import { TimelineId } from '../../../../common/types'; export const HostDetailsTabs = React.memo( ({ @@ -98,7 +99,11 @@ export const HostDetailsTabs = React.memo( - + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 07979c289309a..d7c615c08ec28 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -15,15 +15,17 @@ import { HostsTableType } from '../store/model'; import { AnomaliesQueryTabBody } from '../../common/containers/anomalies/anomalies_query_tab_body'; import { AnomaliesHostTable } from '../../common/components/ml/tables/anomalies_host_table'; import { UpdateDateRange } from '../../common/components/charts/common'; +import { EventsQueryTabBody } from '../../common/components/events_tab/events_query_tab_body'; import { HOSTS_PATH } from '../../../common/constants'; + import { HostsQueryTabBody, HostRiskScoreQueryTabBody, AuthenticationsQueryTabBody, UncommonProcessQueryTabBody, - EventsQueryTabBody, } from './navigation'; import { HostAlertsQueryTabBody } from './navigation/alerts_query_tab_body'; +import { TimelineId } from '../../../common/types'; export const HostsTabs = memo( ({ @@ -96,7 +98,7 @@ export const HostsTabs = memo( - + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts b/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts index 6b74418549164..3ef211e1aef33 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/index.ts @@ -6,7 +6,6 @@ */ export * from './authentications_query_tab_body'; -export * from './events_query_tab_body'; export * from './hosts_query_tab_body'; export * from './uncommon_process_query_tab_body'; export * from './alerts_query_tab_body'; diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 55903a8b47665..08639f48864b3 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -22,10 +22,12 @@ import { MatrixHistogramConfigs, MatrixHistogramOption, } from '../../../common/components/matrix_histogram/types'; -import { eventsStackByOptions } from '../../../hosts/pages/navigation'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; -import { histogramConfigs } from '../../../hosts/pages/navigation/events_query_tab_body'; +import { + eventsStackByOptions, + histogramConfigs, +} from '../../../common/components/events_tab/events_query_tab_body'; import { getEsQueryConfig } from '../../../../../../../src/plugins/data/common'; import { HostsTableType } from '../../../hosts/store/model'; import { InputsModelId } from '../../../common/store/inputs/constants'; diff --git a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx index 810525d4f1ca7..0b87165cbe8ac 100644 --- a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx @@ -120,6 +120,7 @@ const UserRiskScoreTableComponent: React.FC = ({ dispatch( usersActions.updateTableSorting({ sort: newSort as RiskScoreSortField, + tableType, }) ); } diff --git a/x-pack/plugins/security_solution/public/users/pages/constants.ts b/x-pack/plugins/security_solution/public/users/pages/constants.ts index 95c0e361e82d8..793d7c6164b2d 100644 --- a/x-pack/plugins/security_solution/public/users/pages/constants.ts +++ b/x-pack/plugins/security_solution/public/users/pages/constants.ts @@ -10,6 +10,6 @@ import { UsersTableType } from '../store/model'; export const usersDetailsPagePath = `${USERS_PATH}/:detailName`; -export const usersTabPath = `${USERS_PATH}/:tabName(${UsersTableType.allUsers}|${UsersTableType.anomalies}|${UsersTableType.risk})`; +export const usersTabPath = `${USERS_PATH}/:tabName(${UsersTableType.allUsers}|${UsersTableType.anomalies}|${UsersTableType.risk}|${UsersTableType.events}|${UsersTableType.alerts})`; -export const usersDetailsTabPath = `${usersDetailsPagePath}/:tabName(${UsersTableType.anomalies})`; +export const usersDetailsTabPath = `${usersDetailsPagePath}/:tabName(${UsersTableType.anomalies}|${UsersTableType.events}|${UsersTableType.alerts})`; diff --git a/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx index 966fe067fde88..25ada310b74b7 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Route, Switch } from 'react-router-dom'; import { UsersTableType } from '../../store/model'; @@ -16,6 +16,10 @@ import { scoreIntervalToDateTime } from '../../../common/components/ml/score/sco import { UpdateDateRange } from '../../../common/components/charts/common'; import { Anomaly } from '../../../common/components/ml/types'; import { usersDetailsPagePath } from '../constants'; +import { TimelineId } from '../../../../common/types'; +import { EventsQueryTabBody } from '../../../common/components/events_tab/events_query_tab_body'; +import { AlertsView } from '../../../common/components/alerts_viewer'; +import { filterUserExternalAlertData } from './helpers'; export const UsersDetailsTabs = React.memo( ({ @@ -29,6 +33,7 @@ export const UsersDetailsTabs = React.memo( type, setAbsoluteRangeDatePicker, detailName, + pageFilters, }) => { const narrowDateRange = useCallback( (score: Anomaly, interval: string) => { @@ -57,6 +62,14 @@ export const UsersDetailsTabs = React.memo( [setAbsoluteRangeDatePicker] ); + const alertsPageFilters = useMemo( + () => + pageFilters != null + ? [...filterUserExternalAlertData, ...pageFilters] + : filterUserExternalAlertData, + [pageFilters] + ); + const tabProps = { deleteQuery, endDate: to, @@ -76,6 +89,22 @@ export const UsersDetailsTabs = React.memo( + + + + + + + ); } diff --git a/x-pack/plugins/security_solution/public/users/pages/details/helpers.ts b/x-pack/plugins/security_solution/public/users/pages/details/helpers.ts index c96d21d3110e4..daa02df2fb9ca 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/helpers.ts +++ b/x-pack/plugins/security_solution/public/users/pages/details/helpers.ts @@ -30,3 +30,35 @@ export const getUsersDetailsPageFilters = (userName: string): Filter[] => [ }, }, ]; + +export const filterUserExternalAlertData: Filter[] = [ + { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + exists: { + field: 'user.name', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: '', + disabled: false, + key: 'bool', + negate: false, + type: 'custom', + value: + '{"query": {"bool": {"filter": [{"bool": {"should": [{"exists": {"field": "user.name"}}],"minimum_should_match": 1}}]}}}', + }, + }, +]; diff --git a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx index e68c37d6b4042..36ace6a6b4543 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx @@ -50,6 +50,8 @@ import { useQueryInspector } from '../../../common/components/page/manage_query' import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime'; import { getCriteriaFromUsersType } from '../../../common/components/ml/criteria/get_criteria_from_users_type'; import { UsersType } from '../../store/model'; +import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml_user_permissions'; +import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; const QUERY_ID = 'UsersDetailsQueryId'; const UsersDetailsComponent: React.FC = ({ @@ -110,6 +112,8 @@ const UsersDetailsComponent: React.FC = ({ skip: selectedPatterns.length === 0, }); + const capabilities = useMlCapabilities(); + useQueryInspector({ setQuery, deleteQuery, refetch, inspect, loading, queryId: QUERY_ID }); return ( @@ -165,7 +169,9 @@ const UsersDetailsComponent: React.FC = ({ - + diff --git a/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx index 47bc406876c22..9671bd4ee38d0 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { omit } from 'lodash/fp'; import * as i18n from '../translations'; import { UsersDetailsNavTab } from './types'; import { UsersTableType } from '../../store/model'; @@ -13,13 +14,32 @@ import { USERS_PATH } from '../../../../common/constants'; const getTabsOnUsersDetailsUrl = (userName: string, tabName: UsersTableType) => `${USERS_PATH}/${userName}/${tabName}`; -export const navTabsUsersDetails = (userName: string): UsersDetailsNavTab => { - return { +export const navTabsUsersDetails = ( + userName: string, + hasMlUserPermissions: boolean +): UsersDetailsNavTab => { + const userDetailsNavTabs = { [UsersTableType.anomalies]: { id: UsersTableType.anomalies, name: i18n.NAVIGATION_ANOMALIES_TITLE, href: getTabsOnUsersDetailsUrl(userName, UsersTableType.anomalies), disabled: false, }, + [UsersTableType.events]: { + id: UsersTableType.events, + name: i18n.NAVIGATION_EVENTS_TITLE, + href: getTabsOnUsersDetailsUrl(userName, UsersTableType.events), + disabled: false, + }, + [UsersTableType.alerts]: { + id: UsersTableType.alerts, + name: i18n.NAVIGATION_ALERTS_TITLE, + href: getTabsOnUsersDetailsUrl(userName, UsersTableType.alerts), + disabled: false, + }, }; + + return hasMlUserPermissions + ? userDetailsNavTabs + : omit([UsersTableType.anomalies], userDetailsNavTabs); }; diff --git a/x-pack/plugins/security_solution/public/users/pages/details/types.ts b/x-pack/plugins/security_solution/public/users/pages/details/types.ts index 69974678bf4d9..1608d4b735b59 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/users/pages/details/types.ts @@ -44,7 +44,15 @@ export type UsersDetailsComponentProps = UsersDetailsComponentReduxProps & UsersDetailsComponentDispatchProps & UsersQueryProps; -type KeyUsersDetailsNavTab = UsersTableType.anomalies; +export type KeyUsersDetailsNavTabWithoutMlPermission = UsersTableType.events & + UsersTableType.alerts; + +type KeyUsersDetailsNavTabWithMlPermission = KeyUsersDetailsNavTabWithoutMlPermission & + UsersTableType.anomalies; + +type KeyUsersDetailsNavTab = + | KeyUsersDetailsNavTabWithoutMlPermission + | KeyUsersDetailsNavTabWithMlPermission; export type UsersDetailsNavTab = Record; diff --git a/x-pack/plugins/security_solution/public/users/pages/details/utils.ts b/x-pack/plugins/security_solution/public/users/pages/details/utils.ts index eb2820c6d4869..f4bdd7e6caa67 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/users/pages/details/utils.ts @@ -24,6 +24,8 @@ const TabNameMappedToI18nKey: Record = { [UsersTableType.allUsers]: i18n.NAVIGATION_ALL_USERS_TITLE, [UsersTableType.anomalies]: i18n.NAVIGATION_ANOMALIES_TITLE, [UsersTableType.risk]: i18n.NAVIGATION_RISK_TITLE, + [UsersTableType.events]: i18n.NAVIGATION_EVENTS_TITLE, + [UsersTableType.alerts]: i18n.NAVIGATION_ALERTS_TITLE, }; export const getBreadcrumbs = ( diff --git a/x-pack/plugins/security_solution/public/users/pages/index.tsx b/x-pack/plugins/security_solution/public/users/pages/index.tsx index 0b6b103b78176..f1f4e545ae9fd 100644 --- a/x-pack/plugins/security_solution/public/users/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/index.tsx @@ -12,44 +12,53 @@ import { UsersTableType } from '../store/model'; import { Users } from './users'; import { UsersDetails } from './details'; import { usersDetailsPagePath, usersDetailsTabPath, usersTabPath } from './constants'; +import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities'; +import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; -export const UsersContainer = React.memo(() => ( - - - - +export const UsersContainer = React.memo(() => { + const capabilities = useMlCapabilities(); + const hasMlPermissions = hasMlUserPermissions(capabilities); - } - /> - ( - - )} - /> - ( - - )} - /> - -)); + return ( + + + + + + } + /> + ( + + )} + /> + ( + + )} + /> + + ); +}); UsersContainer.displayName = 'UsersContainer'; diff --git a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx index 35124d1deddb1..254807eae27cc 100644 --- a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx @@ -38,6 +38,18 @@ export const navTabsUsers = ( href: getTabsOnUsersUrl(UsersTableType.risk), disabled: false, }, + [UsersTableType.events]: { + id: UsersTableType.events, + name: i18n.NAVIGATION_EVENTS_TITLE, + href: getTabsOnUsersUrl(UsersTableType.events), + disabled: false, + }, + [UsersTableType.alerts]: { + id: UsersTableType.alerts, + name: i18n.NAVIGATION_ALERTS_TITLE, + href: getTabsOnUsersUrl(UsersTableType.alerts), + disabled: false, + }, }; if (!hasMlUserPermissions) { diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/all_users_query_tab_body.tsx b/x-pack/plugins/security_solution/public/users/pages/navigation/all_users_query_tab_body.tsx index 8fa963ef179f2..b5c8b199fda54 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/all_users_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/all_users_query_tab_body.tsx @@ -42,12 +42,11 @@ export const AllUsersQueryTabBody = ({ indexNames, skip: querySkip, startDate, - // TODO Fix me + // TODO Move authentication table and hook store to 'public/common' folder when 'usersEnabled' FF is removed // @ts-ignore type, deleteQuery, }); - // TODO Use a different table return ( diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts index f3fd099d78548..d5c49590dad60 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts @@ -10,7 +10,11 @@ import { ESTermQuery } from '../../../../common/typed_json'; import { DocValueFields } from '../../../../../timelines/common'; import { NavTab } from '../../../common/components/navigation/types'; -type KeyUsersNavTabWithoutMlPermission = UsersTableType.allUsers & UsersTableType.risk; +type KeyUsersNavTabWithoutMlPermission = UsersTableType.allUsers & + UsersTableType.risk & + UsersTableType.events & + UsersTableType.alerts; + type KeyUsersNavTabWithMlPermission = KeyUsersNavTabWithoutMlPermission & UsersTableType.anomalies; type KeyUsersNavTab = KeyUsersNavTabWithoutMlPermission | KeyUsersNavTabWithMlPermission; diff --git a/x-pack/plugins/security_solution/public/users/pages/translations.ts b/x-pack/plugins/security_solution/public/users/pages/translations.ts index 7744ef125ffa2..96dcf8d2c8871 100644 --- a/x-pack/plugins/security_solution/public/users/pages/translations.ts +++ b/x-pack/plugins/security_solution/public/users/pages/translations.ts @@ -31,3 +31,17 @@ export const NAVIGATION_RISK_TITLE = i18n.translate( defaultMessage: 'Users by risk', } ); + +export const NAVIGATION_EVENTS_TITLE = i18n.translate( + 'xpack.securitySolution.users.navigation.eventsTitle', + { + defaultMessage: 'Events', + } +); + +export const NAVIGATION_ALERTS_TITLE = i18n.translate( + 'xpack.securitySolution.users.navigation.alertsTitle', + { + defaultMessage: 'External alerts', + } +); diff --git a/x-pack/plugins/security_solution/public/users/pages/users.tsx b/x-pack/plugins/security_solution/public/users/pages/users.tsx index bd6cc2d097c46..6acd2ddf32a3c 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users.tsx @@ -162,6 +162,10 @@ const UsersComponent = () => { const capabilities = useMlCapabilities(); const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); + const navTabs = useMemo( + () => navTabsUsers(hasMlUserPermissions(capabilities), riskyUsersFeatureEnabled), + [capabilities, riskyUsersFeatureEnabled] + ); return ( <> @@ -197,9 +201,7 @@ const UsersComponent = () => { - + diff --git a/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx index 50de49d1e4af1..522ff4c009504 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx @@ -19,6 +19,9 @@ import { scoreIntervalToDateTime } from '../../common/components/ml/score/score_ import { UpdateDateRange } from '../../common/components/charts/common'; import { UserRiskScoreQueryTabBody } from './navigation/user_risk_score_tab_body'; +import { EventsQueryTabBody } from '../../common/components/events_tab/events_query_tab_body'; +import { TimelineId } from '../../../common/types'; +import { AlertsView } from '../../common/components/alerts_viewer'; export const UsersTabs = memo( ({ @@ -83,6 +86,17 @@ export const UsersTabs = memo( + + + + + + ); } diff --git a/x-pack/plugins/security_solution/public/users/store/actions.ts b/x-pack/plugins/security_solution/public/users/store/actions.ts index 262604f68bdf5..b1d83f29da8c8 100644 --- a/x-pack/plugins/security_solution/public/users/store/actions.ts +++ b/x-pack/plugins/security_solution/public/users/store/actions.ts @@ -31,6 +31,7 @@ export const updateTableActivePage = actionCreator<{ export const updateTableSorting = actionCreator<{ sort: RiskScoreSortField; + tableType: usersModel.UsersTableType.risk; }>('UPDATE_USERS_SORTING'); export const updateUserRiskScoreSeverityFilter = actionCreator<{ diff --git a/x-pack/plugins/security_solution/public/users/store/model.ts b/x-pack/plugins/security_solution/public/users/store/model.ts index 22630d34d48a8..6e4a3730eca86 100644 --- a/x-pack/plugins/security_solution/public/users/store/model.ts +++ b/x-pack/plugins/security_solution/public/users/store/model.ts @@ -16,6 +16,8 @@ export enum UsersTableType { allUsers = 'allUsers', anomalies = 'anomalies', risk = 'userRisk', + events = 'events', + alerts = 'externalAlerts', } export type AllUsersTables = UsersTableType; @@ -36,10 +38,14 @@ export interface UsersQueries { [UsersTableType.allUsers]: AllUsersQuery; [UsersTableType.anomalies]: null | undefined; [UsersTableType.risk]: UsersRiskScoreQuery; + [UsersTableType.events]: BasicQueryPaginated; + [UsersTableType.alerts]: BasicQueryPaginated; } export interface UserDetailsQueries { [UsersTableType.anomalies]: null | undefined; + [UsersTableType.events]: BasicQueryPaginated; + [UsersTableType.alerts]: BasicQueryPaginated; } export interface UsersPageModel { diff --git a/x-pack/plugins/security_solution/public/users/store/reducer.ts b/x-pack/plugins/security_solution/public/users/store/reducer.ts index 26b2e8a225d5a..4b263eecb8c5a 100644 --- a/x-pack/plugins/security_solution/public/users/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/users/store/reducer.ts @@ -37,11 +37,27 @@ export const initialUsersState: UsersModel = { severitySelection: [], }, [UsersTableType.anomalies]: null, + [UsersTableType.events]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, + [UsersTableType.alerts]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, }, }, details: { queries: { [UsersTableType.anomalies]: null, + [UsersTableType.events]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, + [UsersTableType.alerts]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, }, }, }; @@ -80,14 +96,14 @@ export const usersReducer = reducerWithInitialState(initialUsersState) }, }, })) - .case(updateTableSorting, (state, { sort }) => ({ + .case(updateTableSorting, (state, { sort, tableType }) => ({ ...state, page: { ...state.page, queries: { ...state.page.queries, - [UsersTableType.risk]: { - ...state.page.queries[UsersTableType.risk], + [tableType]: { + ...state.page.queries[tableType], sort, }, }, diff --git a/x-pack/plugins/timelines/common/types/timeline/index.ts b/x-pack/plugins/timelines/common/types/timeline/index.ts index a6c8ed1b74bff..1e12baf13c2db 100644 --- a/x-pack/plugins/timelines/common/types/timeline/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/index.ts @@ -310,6 +310,8 @@ export type SavedTimelineNote = runtimeTypes.TypeOf