diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d2e2d3819c165b..70affc4d4b4005 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1467,6 +1467,7 @@ x-pack/plugins/security_solution/public/overview/components/entity_analytics x-pack/plugins/security_solution/server/lib/entity_analytics @elastic/security-entity-analytics x-pack/plugins/security_solution/server/lib/risk_score @elastic/security-entity-analytics x-pack/test/security_solution_api_integration/test_suites/entity_analytics @elastic/security-entity-analytics +x-pack/plugins/security_solution/public/flyout/entity_details @elastic/security-entity-analytics # Security Defend Workflows - OSQuery Ownership /x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions @elastic/security-defend-workflows diff --git a/x-pack/packages/security-solution/data_table/common/types/data_table/index.ts b/x-pack/packages/security-solution/data_table/common/types/data_table/index.ts index f86b3ecc98eef8..e9625d39f2690a 100644 --- a/x-pack/packages/security-solution/data_table/common/types/data_table/index.ts +++ b/x-pack/packages/security-solution/data_table/common/types/data_table/index.ts @@ -31,6 +31,8 @@ export enum TableId { kubernetesPageSessions = 'kubernetes-page-sessions', alertsOnCasePage = 'alerts-case-page', alertsRiskInputs = 'alerts-risk-inputs', + // New version of `alertsRiskInputs` designed to support multiple kinds of risk inputs + riskInputs = 'risk-inputs', } export enum TableEntityType { @@ -52,6 +54,7 @@ export const tableEntity: Record = { [TableId.hostsPageSessions]: TableEntityType.session, [TableId.kubernetesPageSessions]: TableEntityType.session, [TableId.alertsRiskInputs]: TableEntityType.alert, + [TableId.riskInputs]: TableEntityType.alert, } as const; const TableIdLiteralRt = runtimeTypes.union([ diff --git a/x-pack/plugins/security_solution/common/risk_engine/indices.ts b/x-pack/plugins/security_solution/common/risk_engine/indices.ts index e148985b3139a6..281b4495e0c47b 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/indices.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/indices.ts @@ -13,3 +13,6 @@ export const latestRiskScoreIndexPattern = 'risk-score.risk-score-latest-*'; export const getRiskScoreLatestIndex = (spaceId = 'default') => `${riskScoreBaseIndexName}.risk-score-latest-${spaceId}`; + +export const getRiskScoreTimeSeriesIndex = (spaceId = 'default') => + `${riskScoreBaseIndexName}.risk-score-${spaceId}`; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index 1214a17c858677..efbf12b3e5e902 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -103,11 +103,3 @@ export const EMPTY_SEVERITY_COUNT = { [RiskSeverity.moderate]: 0, [RiskSeverity.unknown]: 0, }; - -export const SEVERITY_UI_SORT_ORDER = [ - RiskSeverity.unknown, - RiskSeverity.low, - RiskSeverity.moderate, - RiskSeverity.high, - RiskSeverity.critical, -]; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts index 1bf6ef39097d3b..b5e0c62526a61a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/common/index.ts @@ -7,7 +7,11 @@ import type { ESQuery } from '../../../../typed_json'; import { RISKY_HOSTS_INDEX_PREFIX, RISKY_USERS_INDEX_PREFIX } from '../../../../constants'; -import { RiskScoreEntity, getRiskScoreLatestIndex } from '../../../../risk_engine'; +import { + RiskScoreEntity, + getRiskScoreLatestIndex, + getRiskScoreTimeSeriesIndex, +} from '../../../../risk_engine'; export { RiskQueries } from '../../../../api/search_strategy'; /** @@ -30,7 +34,9 @@ export const getUserRiskIndex = ( isNewRiskScoreModuleInstalled: boolean ): string => { return isNewRiskScoreModuleInstalled - ? getRiskScoreLatestIndex(spaceId) + ? onlyLatest + ? getRiskScoreLatestIndex(spaceId) + : getRiskScoreTimeSeriesIndex(spaceId) : `${RISKY_USERS_INDEX_PREFIX}${onlyLatest ? 'latest_' : ''}${spaceId}`; }; diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 041fd9378382e9..dd639862e28126 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -11,7 +11,7 @@ import type { CaseViewRefreshPropInterface } from '@kbn/cases-plugin/common'; import { CaseMetricsFeature } from '@kbn/cases-plugin/common'; import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; -import { RightPanelKey } from '../../flyout/document_details/right'; +import { DocumentDetailsRightPanelKey } from '../../flyout/document_details/right'; import { useTourContext } from '../../common/components/guided_onboarding_tour'; import { AlertsCasesTourSteps, @@ -74,7 +74,7 @@ const CaseContainerComponent: React.FC = () => { if (isSecurityFlyoutEnabled) { openFlyout({ right: { - id: RightPanelKey, + id: DocumentDetailsRightPanelKey, params: { id: alertId, indexName: index, diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index ca82da005c703d..80e3e0f9f56418 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -13,7 +13,7 @@ import { dataTableActions, TableId } from '@kbn/securitysolution-data-table'; import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import { timelineActions } from '../../../../timelines/store/timeline'; import { ENABLE_EXPANDABLE_FLYOUT_SETTING } from '../../../../../common/constants'; -import { RightPanelKey } from '../../../../flyout/document_details/right'; +import { DocumentDetailsRightPanelKey } from '../../../../flyout/document_details/right'; import type { SetEventsDeleted, SetEventsLoading, @@ -103,7 +103,7 @@ const RowActionComponent = ({ if (isSecurityFlyoutEnabled && tableId !== TableId.rulePreview) { openFlyout({ right: { - id: RightPanelKey, + id: DocumentDetailsRightPanelKey, params: { id: eventId, indexName, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_summary.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_summary.test.ts.snap new file mode 100644 index 00000000000000..225ff3ef8c7a02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_summary.test.ts.snap @@ -0,0 +1,180 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getRiskScoreSummaryAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [], + "state": Object { + "adHocDataViews": Object { + "2cc5663b-f062-43f8-8688-fc8166c2ca8e": Object { + "allowNoIndex": false, + "fieldAttrs": Object {}, + "fieldFormats": Object {}, + "id": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "name": "risk-score.risk-score-default", + "runtimeFieldMap": Object {}, + "sourceFilters": Array [], + "timeFieldName": "@timestamp", + "title": "risk-score.risk-score-default", + }, + }, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "2cc5663b-f062-43f8-8688-fc8166c2ca8e": Object { + "columnOrder": Array [ + "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + ], + "columns": Object { + "2cc5663b-f062-43f8-8688-fc8166c2ca8e": Object { + "customLabel": true, + "dataType": "number", + "filter": Object { + "language": "kuery", + "query": "", + }, + "isBucketed": false, + "label": "Risk value", + "operationType": "last_value", + "params": Object { + "format": Object { + "id": "number", + "params": Object { + "compact": false, + "decimals": 0, + }, + }, + "sortField": "@timestamp", + }, + "reducedTimeRange": "", + "scale": "ratio", + "sourceField": "user.risk.calculated_score_norm", + "timeShift": "", + }, + }, + "ignoreGlobalFilters": false, + "incompleteColumns": Object {}, + "linkToLayers": Array [ + "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + ], + "sampling": 1, + }, + }, + }, + "indexpattern": Object { + "layers": Object {}, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + ], + "internalReferences": Array [ + Object { + "id": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "name": "indexpattern-datasource-layer-2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "type": "index-pattern", + }, + Object { + "id": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "name": "indexpattern-datasource-layer-2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "type": "index-pattern", + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layerId": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "layerType": "data", + "metricAccessor": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "palette": Object { + "name": "custom", + "params": Object { + "colorStops": Array [ + Object { + "color": "#98a2b3", + "stop": 0, + }, + Object { + "color": "#54B399", + "stop": 20, + }, + Object { + "color": "#D6BF57", + "stop": 40, + }, + Object { + "color": "#DA8B45", + "stop": 70, + }, + Object { + "color": "#E7664C", + "stop": 90, + }, + ], + "continuity": "above", + "maxSteps": 5, + "name": "custom", + "progression": "fixed", + "rangeMax": null, + "rangeMin": 0, + "rangeType": "number", + "reverse": false, + "steps": 3, + "stops": Array [ + Object { + "color": "#98a2b3", + "stop": 20, + }, + Object { + "color": "#54B399", + "stop": 40, + }, + Object { + "color": "#D6BF57", + "stop": 70, + }, + Object { + "color": "#DA8B45", + "stop": 90, + }, + Object { + "color": "#E7664C", + "stop": 100, + }, + ], + }, + "type": "palette", + }, + "subtitle": "Low", + "trendlineLayerId": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "trendlineLayerType": "metricTrendline", + "trendlineMetricAccessor": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + "trendlineTimeAccessor": "2cc5663b-f062-43f8-8688-fc8166c2ca8e", + }, + }, + "title": "Risk score summary", + "visualizationType": "lnsMetric", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary.test.ts new file mode 100644 index 00000000000000..4e3aaa69129fcf --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { RiskScoreEntity } from '../../../../../../../common/risk_engine'; +import { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; +import { useLensAttributes } from '../../../use_lens_attributes'; +import { getRiskScoreSummaryAttributes } from './risk_score_summary'; +import { RiskSeverity } from '../../../../../../../common/search_strategy'; +import type { MetricVisualizationState } from '@kbn/lens-plugin/public'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + indicesExist: true, + }), +})); + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('2cc5663b-f062-43f8-8688-fc8166c2ca8e'), +})); + +describe('getRiskScoreSummaryAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: getRiskScoreSummaryAttributes({ + severity: RiskSeverity.low, + query: `user.name: test.user`, + spaceId: 'default', + riskEntity: RiskScoreEntity.user, + }), + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('renders the subtitle', () => { + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: getRiskScoreSummaryAttributes({ + severity: RiskSeverity.low, + query: `user.name: test.user`, + spaceId: 'default', + riskEntity: RiskScoreEntity.user, + }), + }), + { wrapper } + ); + + expect((result?.current?.state.visualization as MetricVisualizationState).subtitle).toBe('Low'); + }); + + it('renders the query when applyGlobalQueriesAndFilters is false', () => { + const query = `test.field: test.user`; + + const { result } = renderHook( + () => + useLensAttributes({ + lensAttributes: getRiskScoreSummaryAttributes({ + severity: RiskSeverity.low, + query, + spaceId: 'default', + riskEntity: RiskScoreEntity.user, + }), + applyGlobalQueriesAndFilters: false, + }), + { wrapper } + ); + + expect(result?.current?.state.query.query).toBe(query); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary.ts new file mode 100644 index 00000000000000..728c6e17713734 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidv4 } from 'uuid'; +import { + SEVERITY_UI_SORT_ORDER, + RISK_SEVERITY_COLOUR, + RISK_SCORE_RANGES, +} from '../../../../../../entity_analytics/common/utils'; +import type { RiskSeverity } from '../../../../../../../common/search_strategy'; +import { RiskScoreEntity, RiskScoreFields } from '../../../../../../../common/search_strategy'; +import type { LensAttributes } from '../../../types'; + +interface GetRiskScoreSummaryAttributesProps { + query?: string; + spaceId?: string; + severity?: RiskSeverity; + riskEntity: RiskScoreEntity; +} + +export const getRiskScoreSummaryAttributes: ( + props: GetRiskScoreSummaryAttributesProps +) => LensAttributes = ({ spaceId, query, severity, riskEntity }) => { + const layerIds = [uuidv4(), uuidv4()]; + const internalReferenceId = uuidv4(); + const columnIds = [uuidv4(), uuidv4(), uuidv4()]; + const sourceField = + riskEntity === RiskScoreEntity.user + ? RiskScoreFields.userRiskScore + : RiskScoreFields.hostRiskScore; + + return { + title: 'Risk score summary', + description: '', + visualizationType: 'lnsMetric', + state: { + visualization: { + layerId: layerIds[0], + layerType: 'data', + metricAccessor: columnIds[0], + trendlineLayerId: layerIds[1], + trendlineLayerType: 'metricTrendline', + trendlineTimeAccessor: columnIds[1], + trendlineMetricAccessor: columnIds[2], + palette: { + type: 'palette', + name: 'custom', + params: { + steps: 3, + name: 'custom', + reverse: false, + rangeType: 'number', + rangeMin: 0, + rangeMax: null, + progression: 'fixed', + colorStops: SEVERITY_UI_SORT_ORDER.map((riskSeverity) => ({ + color: RISK_SEVERITY_COLOUR[riskSeverity], + stop: RISK_SCORE_RANGES[riskSeverity].start, + })), + stops: SEVERITY_UI_SORT_ORDER.map((riskSeverity) => ({ + color: RISK_SEVERITY_COLOUR[riskSeverity], + stop: RISK_SCORE_RANGES[riskSeverity].stop, + })), + continuity: 'above', + maxSteps: 5, + }, + }, + subtitle: severity, + }, + query: { + query: query ?? '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + [layerIds[0]]: { + columns: { + [columnIds[0]]: { + label: 'Risk', + dataType: 'number', + operationType: 'max', + isBucketed: false, + scale: 'ratio', + sourceField, + reducedTimeRange: '', + params: { + sortField: '@timestamp', + format: { + id: 'number', + params: { + decimals: 0, + compact: false, + }, + }, + emptyAsNull: true, + }, + customLabel: true, + }, + }, + columnOrder: [columnIds[0]], + incompleteColumns: {}, + }, + [layerIds[1]]: { + linkToLayers: [layerIds[0]], + columns: { + [columnIds[1]]: { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: RiskScoreFields.timestamp, + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + }, + [columnIds[2]]: { + label: 'Risk value', + dataType: 'number', + operationType: 'last_value', + isBucketed: false, + scale: 'ratio', + sourceField, + filter: { + query: '', + language: 'kuery', + }, + timeShift: '', + reducedTimeRange: '', + params: { + sortField: '@timestamp', + format: { + id: 'number', + params: { + decimals: 0, + compact: false, + }, + }, + }, + customLabel: true, + }, + }, + columnOrder: [columnIds[1], columnIds[2]], + sampling: 1, + ignoreGlobalFilters: false, + incompleteColumns: {}, + }, + }, + }, + indexpattern: { + layers: {}, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [ + { + type: 'index-pattern', + id: internalReferenceId, + name: `indexpattern-datasource-layer-${layerIds[0]}`, + }, + { + type: 'index-pattern', + id: internalReferenceId, + name: `indexpattern-datasource-layer-${layerIds[1]}`, + }, + ], + adHocDataViews: { + [internalReferenceId]: { + id: internalReferenceId, + title: `risk-score.risk-score-${spaceId ?? 'default'}`, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: `risk-score.risk-score-${spaceId ?? 'default'}`, + }, + }, + }, + references: [], + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 943bf4dab0d80f..fdaa006e15f565 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -14,7 +14,7 @@ import styled from 'styled-components'; import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import type { RangeFilterParams } from '@kbn/es-query'; import type { ClickTriggerEvent, MultiClickTriggerEvent } from '@kbn/charts-plugin/public'; -import type { XYState } from '@kbn/lens-plugin/public'; +import type { EmbeddableComponentProps, XYState } from '@kbn/lens-plugin/public'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { useKibana } from '../../lib/kibana'; import { useLensAttributes } from './use_lens_attributes'; @@ -81,6 +81,7 @@ const LensEmbeddableComponent: React.FC = ({ timerange, width: wrapperWidth, withActions = true, + disableOnClickFilter = false, }) => { const style = useMemo( () => ({ @@ -185,9 +186,10 @@ const LensEmbeddableComponent: React.FC = ({ [onLoad] ); - const onFilterCallback = useCallback( - async (e: ClickTriggerEvent['data'] | MultiClickTriggerEvent['data']) => { - if (!isClickTriggerEvent(e) || preferredSeriesType !== 'area') { + const onFilterCallback = useCallback(() => { + const callback: EmbeddableComponentProps['onFilter'] = async (e) => { + if (!isClickTriggerEvent(e) || preferredSeriesType !== 'area' || disableOnClickFilter) { + e.preventDefault(); return; } // Update timerange when clicking on a dot in an area chart @@ -201,9 +203,14 @@ const LensEmbeddableComponent: React.FC = ({ range: [rangeFilter.gte, rangeFilter.lt], }); } - }, - [createFiltersFromValueClickAction, updateDateRange, preferredSeriesType] - ); + }; + return callback; + }, [ + createFiltersFromValueClickAction, + updateDateRange, + preferredSeriesType, + disableOnClickFilter, + ]); const adHocDataViews = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 854aff1860a2ee..6f513e445660e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -79,6 +79,10 @@ export interface LensEmbeddableComponentProps { timerange: { from: string; to: string }; width?: string | number; withActions?: boolean; + /** + * Disable the on click filter for the visualization. + */ + disableOnClickFilter?: boolean; } export enum RequestStatus { diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alerts_by_ids.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alerts_by_ids.ts index cb853aa9488c1b..1ac9948818bccd 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alerts_by_ids.ts +++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alerts_by_ids.ts @@ -17,6 +17,8 @@ interface UseAlertByIdsOptions { interface Hit { fields: Record; + _index: string; + _id: string; } interface UserAlertByIdsResult { @@ -25,6 +27,9 @@ interface UserAlertByIdsResult { data?: Hit[]; } +// It prevents recreating the array on every hook call +const ALL_FIELD = ['*']; + /** * Fetches the alert documents associated to the ids that are passed. * By default it fetches all fields but they can be limited by passing @@ -32,7 +37,7 @@ interface UserAlertByIdsResult { */ export const useAlertsByIds = ({ alertIds, - fields = ['*'], + fields = ALL_FIELD, }: UseAlertByIdsOptions): UserAlertByIdsResult => { const [initialQuery] = useState(() => generateAlertByIdsQuery(alertIds, fields)); diff --git a/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx index 479174a92722fb..6fb3ca1ff0f5b8 100644 --- a/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/storybook_providers.tsx @@ -14,6 +14,8 @@ import type { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; import { CellActionsProvider } from '@kbn/cell-actions'; +import { NavigationProvider } from '@kbn/security-solution-navigation'; +import { CASES_FEATURE_ID } from '../../../common'; import { createStore } from '../store'; import { mockGlobalState } from './global_state'; import { SUB_PLUGINS_REDUCER } from './utils'; @@ -39,11 +41,36 @@ const uiSettings = { const coreMock = { application: { getUrlForApp: () => {}, + capabilities: { [CASES_FEATURE_ID]: {} }, + }, + lens: { + EmbeddableComponent: () => , + }, + cases: { + helpers: { + getUICapabilities: () => ({}), + }, + hooks: { + useCasesAddToExistingCaseModal: () => {}, + useCasesAddToNewCaseFlyout: () => {}, + }, }, data: { query: { filterManager: {}, }, + search: { + session: React.createRef(), + }, + actions: { + createFiltersFromValueClickAction: () => {}, + }, + }, + settings: { + client: { + get: () => {}, + set: () => {}, + }, }, uiSettings, notifications: { @@ -78,13 +105,15 @@ export const StorybookProviders: React.FC = ({ children }) => { return ( - Promise.resolve([])}> - - ({ eui: euiLightVars, darkMode: false })}> - {children} - - - + + Promise.resolve([])}> + + ({ eui: euiLightVars, darkMode: false })}> + {children} + + + + ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx index 50e1e11268e5e6..821a638e893c22 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx @@ -29,7 +29,7 @@ export interface UseAddToCaseActions { onSuccess?: () => Promise; isActiveTimelines: boolean; isInDetections: boolean; - refetch: (() => void) | undefined; + refetch?: (() => void) | undefined; } export const useAddToCaseActions = ({ @@ -173,5 +173,6 @@ export const useAddToCaseActions = ({ return { addToCaseActionItems, handleAddToNewCaseClick, + handleAddToExistingCaseClick, }; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts b/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts new file mode 100644 index 00000000000000..361d6d133a93d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { euiLightVars } from '@kbn/ui-theme'; +import { RiskSeverity } from '../../../common/search_strategy'; +import { SEVERITY_COLOR } from '../../overview/components/detection_response/utils'; + +export const SEVERITY_UI_SORT_ORDER = [ + RiskSeverity.unknown, + RiskSeverity.low, + RiskSeverity.moderate, + RiskSeverity.high, + RiskSeverity.critical, +]; + +export const RISK_SEVERITY_COLOUR: { [k in RiskSeverity]: string } = { + [RiskSeverity.unknown]: euiLightVars.euiColorMediumShade, + [RiskSeverity.low]: SEVERITY_COLOR.low, + [RiskSeverity.moderate]: SEVERITY_COLOR.medium, + [RiskSeverity.high]: SEVERITY_COLOR.high, + [RiskSeverity.critical]: SEVERITY_COLOR.critical, +}; + +export const RISK_SCORE_RANGES = { + [RiskSeverity.unknown]: { start: 0, stop: 20 }, + [RiskSeverity.low]: { start: 20, stop: 40 }, + [RiskSeverity.moderate]: { start: 40, stop: 70 }, + [RiskSeverity.high]: { start: 70, stop: 90 }, + [RiskSeverity.critical]: { start: 90, stop: 100 }, +}; diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/common/index.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/common/index.tsx index b28898b384e008..9430690394b49d 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/common/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/common/index.tsx @@ -12,17 +12,9 @@ import { EuiHealth, transparentize } from '@elastic/eui'; import styled, { css } from 'styled-components'; import { euiLightVars } from '@kbn/ui-theme'; +import { RISK_SEVERITY_COLOUR } from '../../../../../entity_analytics/common/utils'; import { WithHoverActions } from '../../../../../common/components/with_hover_actions'; -import { RiskSeverity } from '../../../../../../common/search_strategy'; -import { SEVERITY_COLOR } from '../../../../../overview/components/detection_response/utils'; - -export const RISK_SEVERITY_COLOUR: { [k in RiskSeverity]: string } = { - [RiskSeverity.unknown]: euiLightVars.euiColorMediumShade, - [RiskSeverity.low]: SEVERITY_COLOR.low, - [RiskSeverity.moderate]: SEVERITY_COLOR.medium, - [RiskSeverity.high]: SEVERITY_COLOR.high, - [RiskSeverity.critical]: SEVERITY_COLOR.critical, -}; +import type { RiskSeverity } from '../../../../../../common/search_strategy'; const RiskBadge = styled.div<{ $severity: RiskSeverity; $hideBackgroundColor: boolean }>` ${({ theme, $severity, $hideBackgroundColor }) => css` diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_badges.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_badges.tsx index f1ebae08975954..53c7e270f2e314 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_badges.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_badges.tsx @@ -7,8 +7,9 @@ import { EuiFlexGroup, EuiNotificationBadge, EuiFlexItem } from '@elastic/eui'; import React from 'react'; +import { RISK_SEVERITY_COLOUR } from '../../../../entity_analytics/common/utils'; import type { RiskSeverity } from '../../../../../common/search_strategy'; -import { RiskScoreLevel, RISK_SEVERITY_COLOUR } from './common'; +import { RiskScoreLevel } from './common'; import type { SeverityCount } from './types'; export const SeverityBadges: React.FC<{ diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_bar.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_bar.tsx index d53693b6d8e699..847488869bcd3b 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_bar.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_bar.tsx @@ -9,8 +9,8 @@ import styled from 'styled-components'; import { EuiColorPaletteDisplay } from '@elastic/eui'; import React, { useMemo } from 'react'; +import { RISK_SEVERITY_COLOUR } from '../../../../entity_analytics/common/utils'; import type { RiskSeverity } from '../../../../../common/search_strategy'; -import { RISK_SEVERITY_COLOUR } from './common'; import type { SeverityCount } from './types'; const StyledEuiColorPaletteDisplay = styled(EuiColorPaletteDisplay)` diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_filter_group.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_filter_group.tsx index 02ce5e01e88734..1ca7020bc818df 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_filter_group.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/severity/severity_filter_group.tsx @@ -16,8 +16,8 @@ import { useEuiTheme, } from '@elastic/eui'; +import { SEVERITY_UI_SORT_ORDER } from '../../../../entity_analytics/common/utils'; import type { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy'; -import { SEVERITY_UI_SORT_ORDER } from '../../../../../common/search_strategy'; import type { SeverityCount } from './types'; import { RiskScoreLevel } from './common'; import { ENTITY_RISK_LEVEL } from '../translations'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx index 0684bf05b79246..b4a97832e77ce4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx @@ -8,7 +8,7 @@ import type { FC } from 'react'; import React, { useCallback } from 'react'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; -import { RightPanelKey } from '../right'; +import { DocumentDetailsRightPanelKey } from '../right'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { EndpointIsolateSuccess } from '../../../common/components/endpoint/host_isolation'; import { useHostIsolationTools } from '../../../timelines/components/side_panel/event_details/use_host_isolation_tools'; @@ -32,7 +32,7 @@ export const PanelContent: FC = () => { const showAlertDetails = useCallback( () => openRightPanel({ - id: RightPanelKey, + id: DocumentDetailsRightPanelKey, params: { id: eventId, indexName, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/index.tsx index ff02d7b78a115d..b56282a5426463 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/index.tsx @@ -11,7 +11,8 @@ import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; import { PanelContent } from './content'; import { PanelHeader } from './header'; -export const IsolateHostPanelKey: IsolateHostPanelProps['key'] = 'document-details-isolate-host'; +export const DocumentDetailsIsolateHostPanelKey: IsolateHostPanelProps['key'] = + 'document-details-isolate-host'; export interface IsolateHostPanelProps extends FlyoutPanelProps { key: 'document-details-isolate-host'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx index a6e4e865adfa8f..049210326c0165 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx @@ -16,7 +16,7 @@ import { tabs } from './tabs'; import { useLeftPanelContext } from './context'; export type LeftPanelPaths = 'visualize' | 'insights' | 'investigation' | 'response'; -export const LeftPanelKey: LeftPanelProps['key'] = 'document-details-left'; +export const DocumentDetailsLeftPanelKey: LeftPanelProps['key'] = 'document-details-left'; export const LeftPanelVisualizeTab: LeftPanelPaths = 'visualize'; export const LeftPanelInsightsTab: LeftPanelPaths = 'insights'; export const LeftPanelInvestigationTab: LeftPanelPaths = 'investigation'; @@ -45,7 +45,7 @@ export const LeftPanel: FC> = memo(({ path }) => { const setSelectedTabId = (tabId: LeftPanelTabsType[number]['id']) => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: tabId, }, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx index 3e8c1f22de6221..f3297b57183f3f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx @@ -20,7 +20,7 @@ import { INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID, } from './test_ids'; import { useLeftPanelContext } from '../context'; -import { LeftPanelKey, LeftPanelInsightsTab } from '..'; +import { DocumentDetailsLeftPanelKey, LeftPanelInsightsTab } from '..'; import { ENTITIES_TAB_ID, EntitiesDetails } from '../components/entities_details'; import { THREAT_INTELLIGENCE_TAB_ID, @@ -86,7 +86,7 @@ export const InsightsTab: React.FC = memo(() => { (optionId: string) => { setActiveInsightsId(optionId); openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: optionId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/visualize_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/visualize_tab.tsx index b23d61f19e0539..2ff079feeeb98b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/visualize_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/visualize_tab.tsx @@ -13,7 +13,7 @@ import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useLeftPanelContext } from '../context'; -import { LeftPanelKey, LeftPanelVisualizeTab } from '..'; +import { DocumentDetailsLeftPanelKey, LeftPanelVisualizeTab } from '..'; import { VISUALIZE_TAB_BUTTON_GROUP_TEST_ID, VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID, @@ -64,7 +64,7 @@ export const VisualizeTab: FC = memo(() => { startTransaction({ name: ALERTS_ACTIONS.OPEN_ANALYZER }); } openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelVisualizeTab, subTab: optionId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx index db9f7bb5ba58a6..5bc4ebf31ab40b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx @@ -13,7 +13,7 @@ import { panels } from './panels'; export type PreviewPanelPaths = 'rule-preview' | 'alert-reason-preview'; export const RulePreviewPanel: PreviewPanelPaths = 'rule-preview'; export const AlertReasonPreviewPanel: PreviewPanelPaths = 'alert-reason-preview'; -export const PreviewPanelKey: PreviewPanelProps['key'] = 'document-details-preview'; +export const DocumentDetailsPreviewPanelKey: PreviewPanelProps['key'] = 'document-details-preview'; export interface PreviewPanelProps extends FlyoutPanelProps { key: 'document-details-preview'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx index 38a80490ea22f8..c97db50a0798b5 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx @@ -12,7 +12,7 @@ import { RightPanelContext } from '../context'; import { TestProviders } from '../../../../common/mock'; import { CorrelationsOverview } from './correlations_overview'; import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, @@ -196,7 +196,7 @@ describe('', () => { getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: CORRELATIONS_TAB_ID }, params: { id: panelContextValue.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx index a273257ba0fc77..52e66fed55b5df 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx @@ -22,7 +22,7 @@ import { RelatedCases } from './related_cases'; import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; import { CORRELATIONS_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; -import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; +import { DocumentDetailsLeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details'; /** @@ -43,7 +43,7 @@ export const CorrelationsOverview: React.FC = () => { const goToCorrelationsTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: CORRELATIONS_TAB_ID, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx index 2cf9276a1853ee..7b99e426c8abce 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx @@ -18,7 +18,7 @@ import { RightPanelContext } from '../context'; import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { PreviewPanelKey } from '../../preview'; +import { DocumentDetailsPreviewPanelKey } from '../../preview'; const ruleUuid = { category: 'kibana', @@ -119,7 +119,7 @@ describe('', () => { getByTestId(RULE_SUMMARY_BUTTON_TEST_ID).click(); expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({ - id: PreviewPanelKey, + id: DocumentDetailsPreviewPanelKey, path: { tab: 'rule-preview' }, params: { id: panelContext.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.tsx index 0003a5cd2c1230..5c65d9231eef08 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.tsx @@ -20,7 +20,11 @@ import { DESCRIPTION_TITLE_TEST_ID, RULE_SUMMARY_BUTTON_TEST_ID, } from './test_ids'; -import { PreviewPanelKey, type PreviewPanelProps, RulePreviewPanel } from '../../preview'; +import { + DocumentDetailsPreviewPanelKey, + type PreviewPanelProps, + RulePreviewPanel, +} from '../../preview'; /** * Displays the description of a document. @@ -35,7 +39,7 @@ export const Description: FC = () => { const openRulePreview = useCallback(() => { const PreviewPanelRulePreview: PreviewPanelProps['path'] = { tab: RulePreviewPanel }; openPreviewPanel({ - id: PreviewPanelKey, + id: DocumentDetailsPreviewPanelKey, path: PreviewPanelRulePreview, params: { id: eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx index 94345531560635..e52728b880d7b0 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx @@ -15,7 +15,7 @@ import { useRightPanelContext } from '../context'; import { getField } from '../../shared/utils'; import { HostEntityOverview } from './host_entity_overview'; import { UserEntityOverview } from './user_entity_overview'; -import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; +import { DocumentDetailsLeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; /** @@ -29,7 +29,7 @@ export const EntitiesOverview: React.FC = () => { const goToEntitiesTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx index 6f4711651ffc24..c56fef68a8bfa4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx @@ -15,7 +15,7 @@ import { import { HighlightedFieldsCell } from './highlighted_fields_cell'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { TestProviders } from '../../../../common/mock'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useGetEndpointDetails } from '../../../../management/hooks'; @@ -64,7 +64,7 @@ describe('', () => { getByTestId(HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID }, params: { id: panelContextValue.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx index 60c561116c38af..6d7c5652eadead 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx @@ -16,7 +16,7 @@ import { HOST_NAME_FIELD_NAME, USER_NAME_FIELD_NAME, } from '../../../../timelines/components/timeline/body/renderers/constants'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID, @@ -42,7 +42,7 @@ const LinkFieldCell: VFC = ({ value }) => { const goToInsightsEntities = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID }, params: { id: eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx index 25c25b87aa38d4..31a86495d05619 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx @@ -22,7 +22,7 @@ import { RightPanelContext } from '../context'; import { mockContextValue } from '../mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; const hostName = 'host'; @@ -160,7 +160,7 @@ describe('', () => { getByTestId(ENTITIES_HOST_OVERVIEW_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID }, params: { id: panelContextValue.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx index 58d9bf9d8a4188..a1d42871a42e4f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx @@ -49,7 +49,7 @@ import { ENTITIES_HOST_OVERVIEW_LINK_TEST_ID, ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID, } from './test_ids'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { RiskScoreDocTooltip } from '../../../../overview/components/common'; const HOST_ICON = 'storage'; @@ -70,7 +70,7 @@ export const HostEntityOverview: React.FC = ({ hostName const { openLeftPanel } = useExpandableFlyoutContext(); const goToEntitiesTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID }, params: { id: eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx index 1dd3af16ff4159..f774fe67e179ba 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx @@ -19,7 +19,7 @@ import { mockContextValue } from '../mocks/mock_context'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide'; -import { LeftPanelInvestigationTab, LeftPanelKey } from '../../left'; +import { LeftPanelInvestigationTab, DocumentDetailsLeftPanelKey } from '../../left'; jest.mock('../../shared/hooks/use_investigation_guide'); @@ -109,7 +109,7 @@ describe('', () => { getByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID).click(); expect(mockFlyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInvestigationTab, }, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx index d00310b360c261..04c73baad9d787 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide'; import { useRightPanelContext } from '../context'; -import { LeftPanelKey, LeftPanelInvestigationTab } from '../../left'; +import { DocumentDetailsLeftPanelKey, LeftPanelInvestigationTab } from '../../left'; import { INVESTIGATION_GUIDE_BUTTON_TEST_ID, INVESTIGATION_GUIDE_LOADING_TEST_ID, @@ -32,7 +32,7 @@ export const InvestigationGuide: React.FC = () => { const goToInvestigationsTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInvestigationTab, }, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx index 79fcf89977291b..fe711387dbf174 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { RightPanelContext } from '../context'; import { PREVALENCE_TEST_ID } from './test_ids'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import React from 'react'; import { PrevalenceOverview } from './prevalence_overview'; import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details'; @@ -167,7 +167,7 @@ describe('', () => { getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: PREVALENCE_TAB_ID }, params: { id: 'eventId', diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx index 674b9d662f4609..c7249f66065df9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx @@ -14,7 +14,7 @@ import { ExpandablePanel } from '../../../shared/components/expandable_panel'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; import { PREVALENCE_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; -import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; +import { DocumentDetailsLeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details'; import { InsightsSummaryRow } from './insights_summary_row'; @@ -33,7 +33,7 @@ export const PrevalenceOverview: FC = () => { const goPrevalenceTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: PREVALENCE_TAB_ID, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx index f407c33a1e2106..65a241a00e6bbf 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx @@ -14,7 +14,7 @@ import { RightPanelContext } from '../context'; import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; -import { PreviewPanelKey } from '../../preview'; +import { DocumentDetailsPreviewPanelKey } from '../../preview'; const flyoutContextValue = { openPreviewPanel: jest.fn(), @@ -82,7 +82,7 @@ describe('', () => { getByTestId(REASON_DETAILS_PREVIEW_BUTTON_TEST_ID).click(); expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({ - id: PreviewPanelKey, + id: DocumentDetailsPreviewPanelKey, path: { tab: 'alert-reason-preview' }, params: { id: panelContextValue.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx index 37629cd0678ca8..392e7532137146 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx @@ -13,7 +13,7 @@ import { ALERT_REASON } from '@kbn/rule-data-utils'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { getField } from '../../shared/utils'; -import { AlertReasonPreviewPanel, PreviewPanelKey } from '../../preview'; +import { AlertReasonPreviewPanel, DocumentDetailsPreviewPanelKey } from '../../preview'; import { REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, REASON_DETAILS_TEST_ID, @@ -34,7 +34,7 @@ export const Reason: FC = () => { const { openPreviewPanel } = useExpandableFlyoutContext(); const openRulePreview = useCallback(() => { openPreviewPanel({ - id: PreviewPanelKey, + id: DocumentDetailsPreviewPanelKey, path: { tab: AlertReasonPreviewPanel }, params: { id: eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.tsx index 0d1b6fac8df4be..c114530f4b6640 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.tsx @@ -9,7 +9,7 @@ import { EuiButton } from '@elastic/eui'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { FormattedMessage } from '@kbn/i18n-react'; import { useRightPanelContext } from '../context'; -import { LeftPanelKey, LeftPanelResponseTab } from '../../left'; +import { DocumentDetailsLeftPanelKey, LeftPanelResponseTab } from '../../left'; import { RESPONSE_BUTTON_TEST_ID } from './test_ids'; /** @@ -21,7 +21,7 @@ export const ResponseButton: React.FC = () => { const goToResponseTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelResponseTab }, params: { id: eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx index 1b8a646f1d356f..f5ae35db72f16d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx @@ -11,7 +11,7 @@ import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { TestProviders } from '../../../../common/mock'; import { ThreatIntelligenceOverview } from './threat_intelligence_overview'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { useFetchThreatIntelligence } from '../hooks/use_fetch_threat_intelligence'; import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelligence_details'; import { INSIGHTS_THREAT_INTELLIGENCE_TEST_ID } from './test_ids'; @@ -161,7 +161,7 @@ describe('', () => { getByTestId(TITLE_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: THREAT_INTELLIGENCE_TAB_ID }, params: { id: panelContextValue.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx index ebaea597219ac6..d57fb1d6c0aab9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx @@ -15,7 +15,7 @@ import { useFetchThreatIntelligence } from '../hooks/use_fetch_threat_intelligen import { InsightsSummaryRow } from './insights_summary_row'; import { useRightPanelContext } from '../context'; import { INSIGHTS_THREAT_INTELLIGENCE_TEST_ID } from './test_ids'; -import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; +import { DocumentDetailsLeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelligence_details'; /** @@ -29,7 +29,7 @@ export const ThreatIntelligenceOverview: FC = () => { const goToThreatIntelligenceTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: THREAT_INTELLIGENCE_TAB_ID, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx index 37b4666611efed..155f2c127fc3c4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx @@ -22,7 +22,7 @@ import { mockContextValue } from '../mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; const userName = 'user'; @@ -169,7 +169,7 @@ describe('', () => { getByTestId(ENTITIES_USER_OVERVIEW_LINK_TEST_ID).click(); expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID }, params: { id: panelContextValue.eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx index 81b1b75df57e9f..313719ec1c0bac 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx @@ -19,7 +19,7 @@ import { css } from '@emotion/css'; import { getOr } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; -import { LeftPanelInsightsTab, LeftPanelKey } from '../../left'; +import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useRightPanelContext } from '../context'; import type { DescriptionList } from '../../../../../common/utility_types'; @@ -70,7 +70,7 @@ export const UserEntityOverview: React.FC = ({ userName const { openLeftPanel } = useExpandableFlyoutContext(); const goToEntitiesTab = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, path: { tab: LeftPanelInsightsTab, subTab: ENTITIES_TAB_ID }, params: { id: eventId, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx index 35470f21f8085f..39abe1f818a967 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx @@ -20,7 +20,7 @@ import { tabs } from './tabs'; import { PanelFooter } from './footer'; export type RightPanelPaths = 'overview' | 'table' | 'json'; -export const RightPanelKey: RightPanelProps['key'] = 'document-details-right'; +export const DocumentDetailsRightPanelKey: RightPanelProps['key'] = 'document-details-right'; export interface RightPanelProps extends FlyoutPanelProps { key: 'document-details-right'; @@ -51,7 +51,7 @@ export const RightPanel: FC> = memo(({ path }) => { const setSelectedTabId = (tabId: RightPanelTabsType[number]['id']) => { openRightPanel({ - id: RightPanelKey, + id: DocumentDetailsRightPanelKey, path: { tab: tabId, }, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx index 12e36ea29e5a09..d78784f1649854 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx @@ -10,7 +10,7 @@ import React, { memo, useCallback } from 'react'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { HeaderActions } from './components/header_actions'; import { FlyoutNavigation } from '../../shared/components/flyout_navigation'; -import { LeftPanelKey } from '../left'; +import { DocumentDetailsLeftPanelKey } from '../left'; import { useRightPanelContext } from './context'; interface PanelNavigationProps { @@ -26,7 +26,7 @@ export const PanelNavigation: FC = memo(({ flyoutIsExpanda const expandDetails = useCallback(() => { openLeftPanel({ - id: LeftPanelKey, + id: DocumentDetailsLeftPanelKey, params: { id: eventId, indexName, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/url/expandable_flyout_state_from_event_meta.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/url/expandable_flyout_state_from_event_meta.ts index 95e5c509f96d6b..e25824ed8b68a1 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/url/expandable_flyout_state_from_event_meta.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/url/expandable_flyout_state_from_event_meta.ts @@ -6,7 +6,7 @@ */ import type { ExpandableFlyoutContext } from '@kbn/expandable-flyout'; -import { RightPanelKey } from '../../../right'; +import { DocumentDetailsRightPanelKey } from '../../../right'; interface RedirectParams { index: string; @@ -26,7 +26,7 @@ export const expandableFlyoutStateFromEventMeta = ({ }: RedirectParams): ExpandableFlyoutContext['panels'] => { return { right: { - id: RightPanelKey, + id: DocumentDetailsRightPanelKey, params: { id: eventId, indexName: index, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/action_column.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/action_column.test.tsx new file mode 100644 index 00000000000000..9df74c2d608460 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/action_column.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { ActionColumn } from './action_column'; +import { alertDataMock } from '../mocks'; + +describe('ActionColumn', () => { + it('renders', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-inputs-actions')).toBeInTheDocument(); + }); + + it('toggles the popover when button is clicked', () => { + const { getByRole } = render( + + + + ); + + fireEvent.click(getByRole('button')); + + expect(getByRole('dialog')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/action_column.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/action_column.tsx new file mode 100644 index 00000000000000..35b25d69fffce0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/action_column.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonIcon, EuiContextMenu, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useMemo, useState } from 'react'; +import type { AlertRawData } from '../content'; +import { useRiskInputActionsPanels } from '../hooks/use_risk_input_actions_panels'; + +interface ActionColumnProps { + alert: AlertRawData; +} + +export const ActionColumn: React.FC = ({ alert }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const togglePopover = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), []); + const alerts = useMemo(() => [alert], [alert]); + const panels = useRiskInputActionsPanels(alerts, closePopover); + + return ( + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/utility_bar.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/utility_bar.test.tsx new file mode 100644 index 00000000000000..aeb244e537bda2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/utility_bar.test.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { alertDataMock } from '../mocks'; +import { RiskInputsUtilityBar } from './utility_bar'; + +describe('RiskInputsPanel', () => { + it('renders', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-input-utility-bar')).toBeInTheDocument(); + }); + + it('renders current page message when totalItemCount is 1', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-input-utility-bar')).toHaveTextContent('Showing 1 Risk input'); + }); + + it('renders current page message when totalItemCount is 20', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-input-utility-bar')).toHaveTextContent( + 'Showing 1-10 of 20 Risk input' + ); + }); + + it('renders current page message when totalItemCount is 20 and on the second page', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-input-utility-bar')).toHaveTextContent( + 'Showing 11-20 of 20 Risk inputs' + ); + }); + + it('renders selected risk input message', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-input-utility-bar')).toHaveTextContent('3 selected risk input'); + }); + + it('toggles the popover when button is clicked', () => { + const { getByRole } = render( + + + + ); + + fireEvent.click(getByRole('button')); + + expect(getByRole('dialog')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/utility_bar.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/utility_bar.tsx new file mode 100644 index 00000000000000..40e3497a049454 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/components/utility_bar.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FunctionComponent } from 'react'; +import React, { useCallback, useState } from 'react'; +import type { Pagination } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiContextMenu, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import { useRiskInputActionsPanels } from '../hooks/use_risk_input_actions_panels'; +import type { AlertRawData } from '../content'; + +interface Props { + selectedAlerts: AlertRawData[]; + pagination: Pagination; +} + +export const RiskInputsUtilityBar: FunctionComponent = React.memo( + ({ selectedAlerts, pagination }) => { + const { euiTheme } = useEuiTheme(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const panels = useRiskInputActionsPanels(selectedAlerts, closePopover); + const displayedCurrentPage = pagination.pageIndex + 1; + const pageSize = pagination.pageSize ?? 10; + const fromItem: number = pagination.pageIndex * pageSize + 1; + const toItem: number = Math.min(pagination.totalItemCount, pageSize * displayedCurrentPage); + + return ( + <> + + + + {pagination.totalItemCount <= 1 ? ( + + + + ), + }} + /> + ) : ( + {`${fromItem}-${toItem}`}, + totalInputs: pagination.totalItemCount, + riskInputs: ( + + + + ), + }} + /> + )} + + + + {selectedAlerts.length > 0 && ( + + + + } + > + + + )} + + + + ); + } +); + +RiskInputsUtilityBar.displayName = 'RiskInputsUtilityBar'; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/content.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/content.test.tsx new file mode 100644 index 00000000000000..93396e7dea03a5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/content.test.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SimpleRiskInput } from '../../../../common/risk_engine'; +import { RiskCategories } from '../../../../common/risk_engine'; +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { RiskInputsPanel } from '.'; +import { TestProviders } from '../../../common/mock'; +import { times } from 'lodash/fp'; +import { alertDataMock } from './mocks'; + +const mockUseAlertsByIds = jest.fn().mockReturnValue({ loading: false, data: [] }); + +jest.mock('../../../common/containers/alerts/use_alerts_by_ids', () => ({ + useAlertsByIds: () => mockUseAlertsByIds(), +})); + +const TEST_RISK_INPUT: SimpleRiskInput = { + id: '123', + index: '_test_index', + category: RiskCategories.category_1, + description: 'test description', + risk_score: 70, + timestamp: '2023-05-15T16:12:14.967Z', +}; + +describe('RiskInputsPanel', () => { + it('renders', () => { + mockUseAlertsByIds.mockReturnValue({ + loading: false, + error: false, + data: [alertDataMock], + }); + + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-inputs-panel')).toBeInTheDocument(); + expect(getByTestId('risk-input-table-description-cell')).toHaveTextContent( + 'Risk inputRule Name' + ); + }); + + it('paginates', () => { + const riskInputs = times((index) => ({ ...TEST_RISK_INPUT, id: index.toString() }), 11); + const alerts = times((index) => ({ ...alertDataMock, _id: index.toString() }), 11); + + mockUseAlertsByIds.mockReturnValue({ + loading: false, + error: false, + data: alerts, + }); + + const { getAllByTestId, getByLabelText } = render( + + + + ); + + expect(getAllByTestId('risk-input-table-description-cell')).toHaveLength(10); + + fireEvent.click(getByLabelText('Next page')); + + expect(getAllByTestId('risk-input-table-description-cell')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/content.tsx new file mode 100644 index 00000000000000..544dca9df62a89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/content.tsx @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiBasicTableColumn, Pagination } from '@elastic/eui'; +import { useEuiBackgroundColor, EuiSpacer, EuiInMemoryTable, EuiTitle } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { get } from 'lodash/fp'; +import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; +import type { RiskInputs } from '../../../../common/risk_engine'; +import { ActionColumn } from './components/action_column'; +import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; +import { RiskInputsUtilityBar } from './components/utility_bar'; +import { useAlertsByIds } from '../../../common/containers/alerts/use_alerts_by_ids'; +import { FlyoutBody } from '../../shared/components/flyout_body'; + +export interface RiskInputsPanelContentProps extends Record { + riskInputs: RiskInputs; +} + +export interface AlertRawData { + fields: Record; + _index: string; + _id: string; +} + +export const RiskInputsPanelContent = ({ riskInputs }: RiskInputsPanelContentProps) => { + const [selectedItems, setSelectedItems] = useState([]); + const alertIds = useMemo(() => riskInputs.map(({ id }) => id), [riskInputs]); + const { loading, data: alertsData } = useAlertsByIds({ alertIds }); + + const euiTableSelectionProps = useMemo( + () => ({ + onSelectionChange: (selected: AlertRawData[]) => { + setSelectedItems(selected); + }, + initialSelected: [], + selectable: () => true, + }), + [] + ); + + const columns: Array> = useMemo( + () => [ + { + name: ( + + ), + width: '80px', + render: (alert: AlertRawData) => { + return ; + }, + }, + { + field: 'fields.@timestamp', + name: ( + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + width: '30%', + render: (timestamp: string) => , + }, + { + field: 'fields', + 'data-test-subj': 'risk-input-table-description-cell', + name: ( + + ), + truncateText: true, + mobileOptions: { show: true }, + sortable: true, + render: (fields: AlertRawData['fields']) => get(ALERT_RULE_NAME, fields), + }, + ], + [] + ); + + const [currentPage, setCurrentPage] = useState<{ + index: number; + size: number; + }>({ index: 0, size: 10 }); + + const onTableChange = useCallback(({ page }) => { + setCurrentPage(page); + }, []); + + const pagination: Pagination = useMemo( + () => ({ + totalItemCount: riskInputs.length, + pageIndex: currentPage.index, + pageSize: currentPage.size, + }), + [currentPage.index, currentPage.size, riskInputs.length] + ); + + return ( + <> + + +

+ +

+
+ + {/* Temporary label. It will be replaced by a filter */} + +

+ +

+
+ + + + +
+ + ); +}; + +RiskInputsPanelContent.displayName = 'RiskInputsPanelContent'; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions.ts b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions.ts new file mode 100644 index 00000000000000..6a8643c7fd9f33 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions.ts @@ -0,0 +1,84 @@ +/* + * 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 { TableId } from '@kbn/securitysolution-data-table'; +import { useMemo } from 'react'; +import { get, noop } from 'lodash/fp'; +import { AttachmentType } from '@kbn/cases-plugin/common'; +import type { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; +import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { useAddBulkToTimelineAction } from '../../../../detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline'; +import { useKibana } from '../../../../common/lib/kibana/kibana_react'; +import type { AlertRawData } from '../content'; + +/** + * The returned actions only support alerts risk inputs. + */ +export const useRiskInputActions = (alerts: AlertRawData[], closePopover: () => void) => { + const { from, to } = useGlobalTime(); + const timelineAction = useAddBulkToTimelineAction({ + localFilters: [], + from, + to, + scopeId: SourcererScopeName.detections, + tableId: TableId.riskInputs, + }); + + const { cases: casesService } = useKibana().services; + const createCaseFlyout = casesService?.hooks.useCasesAddToNewCaseFlyout({ onSuccess: noop }); + const selectCaseModal = casesService?.hooks.useCasesAddToExistingCaseModal(); + + const caseAttachments: CaseAttachmentsWithoutOwner = useMemo( + () => + alerts.map((alert: AlertRawData) => ({ + alertId: alert._id, + index: alert._index, + type: AttachmentType.alert, + rule: { + id: get(ALERT_RULE_UUID, alert.fields)[0], + name: get(ALERT_RULE_NAME, alert.fields)[0], + }, + })), + [alerts] + ); + + return useMemo( + () => ({ + addToExistingCase: () => { + closePopover(); + selectCaseModal.open({ getAttachments: () => caseAttachments }); + }, + addToNewCaseClick: () => { + closePopover(); + createCaseFlyout.open({ attachments: caseAttachments }); + }, + addToNewTimeline: () => { + closePopover(); + timelineAction.onClick( + alerts.map((alert: AlertRawData) => { + return { + _id: alert._id, + _index: alert._index, + data: [], + ecs: { + _id: alert._id, + _index: alert._index, + }, + }; + }), + false, + noop, + noop, + noop + ); + }, + }), + [alerts, caseAttachments, closePopover, createCaseFlyout, selectCaseModal, timelineAction] + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions_panels.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions_panels.test.tsx new file mode 100644 index 00000000000000..49bad231f2767f --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions_panels.test.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import { EuiContextMenu } from '@elastic/eui'; +import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; +import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { alertDataMock } from '../mocks'; +import { useRiskInputActionsPanels } from './use_risk_input_actions_panels'; + +const casesServiceMock = casesPluginMock.createStartContract(); +const mockCanUseCases = jest.fn().mockReturnValue({ + create: true, + read: true, +}); + +const mockedCasesServices = { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: mockCanUseCases, + }, +}; + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + useKibana: () => ({ + ...original.useKibana(), + services: { + ...original.useKibana().services, + cases: mockedCasesServices, + }, + }), + }; +}); + +const TestMenu = ({ panels }: { panels: EuiContextMenuPanelDescriptor[] }) => ( + +); + +const customRender = (alerts = [alertDataMock]) => { + const { result } = renderHook(() => useRiskInputActionsPanels(alerts, () => {}), { + wrapper: TestProviders, + }); + + return render( + + + + ); +}; + +describe('useRiskInputActionsPanels', () => { + it('displays the rule name when only one alert is selected', () => { + const { getByTestId } = customRender(); + + expect(getByTestId('contextMenuPanelTitle')).toHaveTextContent('Risk input: Rule Name'); + }); + + it('displays number of selected alerts when more than one alert is selected', () => { + const { getByTestId } = customRender([alertDataMock, alertDataMock]); + + expect(getByTestId('contextMenuPanelTitle')).toHaveTextContent('2 selected'); + }); + + it('displays cases actions when user has cases permissions', () => { + const { container } = customRender(); + + expect(container).toHaveTextContent('Add to existing case'); + expect(container).toHaveTextContent('Add to new case'); + }); + + it('does NOT display cases actions when user has NO cases permissions', () => { + mockCanUseCases.mockReturnValue({ + create: false, + read: false, + }); + + const { container } = customRender(); + + expect(container).not.toHaveTextContent('Add to existing case'); + expect(container).not.toHaveTextContent('Add to new case'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions_panels.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions_panels.tsx new file mode 100644 index 00000000000000..176b2a13db72d6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/hooks/use_risk_input_actions_panels.tsx @@ -0,0 +1,100 @@ +/* + * 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 { EuiTextTruncate } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import type { CasesService } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alerts_table/types'; +import { i18n } from '@kbn/i18n'; +import { get } from 'lodash/fp'; +import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; +import { useRiskInputActions } from './use_risk_input_actions'; +import type { AlertRawData } from '../content'; + +export const useRiskInputActionsPanels = (alerts: AlertRawData[], closePopover: () => void) => { + const { cases: casesService } = useKibana<{ cases?: CasesService }>().services; + const { addToExistingCase, addToNewCaseClick, addToNewTimeline } = useRiskInputActions( + alerts, + closePopover + ); + const userCasesPermissions = casesService?.helpers.canUseCases([SECURITY_SOLUTION_OWNER]); + const hasCasesPermissions = userCasesPermissions?.create && userCasesPermissions?.read; + + return useMemo(() => { + const timelinePanel = { + name: ( + + ), + + onClick: addToNewTimeline, + }; + const ruleName = get(['fields', ALERT_RULE_NAME], alerts[0]) ?? ['']; + const title = i18n.translate( + 'xpack.securitySolution.flyout.entityDetails.riskInputs.actions.title', + { + defaultMessage: 'Risk input: {description}', + values: { + description: + alerts.length === 1 + ? ruleName[0] + : i18n.translate( + 'xpack.securitySolution.flyout.entityDetails.riskInputs.actions.titleDescription', + { + defaultMessage: '{quantity} selected', + values: { + quantity: alerts.length, + }, + } + ), + }, + } + ); + + return [ + { + title: ( + + ), + id: 0, + items: hasCasesPermissions + ? [ + timelinePanel, + { + name: ( + + ), + + onClick: addToNewCaseClick, + }, + + { + name: ( + + ), + + onClick: addToExistingCase, + }, + ] + : [timelinePanel], + }, + ]; + }, [addToExistingCase, addToNewCaseClick, addToNewTimeline, alerts, hasCasesPermissions]); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/index.tsx new file mode 100644 index 00000000000000..386ec25bfeaf8e --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; +import type { RiskInputs } from '../../../../common/risk_engine'; +import { RiskInputsPanelContent } from './content'; + +export interface RiskInputsPanelProps extends Record { + riskInputs: RiskInputs; +} + +export interface RiskInputsExpandableFlyoutProps extends FlyoutPanelProps { + key: 'all-risk-inputs'; + params: RiskInputsPanelProps; +} + +export const RiskInputsPanelKey: RiskInputsExpandableFlyoutProps['key'] = 'all-risk-inputs'; + +export const RiskInputsPanel = ({ riskInputs }: RiskInputsPanelProps) => { + return ; +}; + +RiskInputsPanel.displayName = 'RiskInputsPanel'; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/mocks/index.ts b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/mocks/index.ts new file mode 100644 index 00000000000000..9e6f5db17034cf --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/risk_inputs_left/mocks/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import type { AlertRawData } from '../content'; + +export const alertDataMock: AlertRawData = { + _id: 'test-id', + _index: 'test-index', + fields: { + [ALERT_RULE_UUID]: ['2e051244-b3c6-4779-a241-e1b4f0beceb9'], + '@timestamp': ['2023-07-20T20:31:24.896Z'], + [ALERT_RULE_NAME]: ['Rule Name'], + }, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.stories.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.stories.tsx new file mode 100644 index 00000000000000..e45869115c50f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.stories.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Story } from '@storybook/react'; +import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { StorybookProviders } from '../../../../common/mock/storybook_providers'; +import { mockRiskScoreState } from '../../../../timelines/components/side_panel/new_user_detail/__mocks__'; +import { RiskSummary } from './risk_summary'; + +export default { + component: RiskSummary, + title: 'Components/RiskSummary', +}; + +const flyoutContextValue = { + openLeftPanel: () => window.alert('openLeftPanel called'), + panels: {}, +} as unknown as ExpandableFlyoutContext; + +export const Default: Story = () => { + return ( + + +
+ +
+
+
+ ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.test.tsx new file mode 100644 index 00000000000000..774a7c8a8458e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { mockRiskScoreState } from '../../user_right/mocks'; +import { RiskSummary } from './risk_summary'; + +jest.mock('../../../../common/components/visualization_actions/visualization_embeddable'); + +describe('RiskSummary', () => { + it('renders risk summary table', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-summary-table')).toBeInTheDocument(); + expect(getByTestId('risk-summary-table')).toHaveTextContent('Inputs1'); + expect(getByTestId('risk-summary-table')).toHaveTextContent('CategoryAlerts'); + }); + + it('renders risk summary table when riskScoreData is empty', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('risk-summary-table')).toBeInTheDocument(); + }); + + it('renders visualization embeddable', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('visualization-embeddable')).toBeInTheDocument(); + }); + + it('renders updated at', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-summary-updatedAt')).toHaveTextContent('Updated Nov 8, 1989'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.tsx new file mode 100644 index 00000000000000..763a6377a9d678 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/risk_summary.tsx @@ -0,0 +1,254 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import type { EuiBasicTableColumn } from '@elastic/eui'; +import { + useEuiTheme, + EuiAccordion, + EuiTitle, + EuiSpacer, + EuiBasicTable, + EuiFlexGroup, + EuiFlexItem, + useEuiFontSize, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { i18n } from '@kbn/i18n'; +import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; +import { ONE_WEEK_IN_HOURS } from '../../../../timelines/components/side_panel/new_user_detail/constants'; +import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; +import { RiskScoreEntity } from '../../../../../common/risk_engine'; +import type { RiskScoreState } from '../../../../explore/containers/risk_score'; +import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; +import { getRiskScoreSummaryAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_summary'; +import { ExpandablePanel } from '../../../shared/components/expandable_panel'; +import { RiskInputsPanelKey } from '../../risk_inputs_left'; + +export interface RiskSummaryProps { + riskScoreData: RiskScoreState; + queryId: string; +} + +interface TableItem { + category: string; + count: number; +} +const LENS_VISUALIZATION_HEIGHT = 126; // Static height in pixels specified by design +const LAST_30_DAYS = { from: 'now-30d', to: 'now' }; + +export const RiskSummary = React.memo(({ riskScoreData, queryId }: RiskSummaryProps) => { + const { data: userRisk } = riskScoreData; + const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; + const { euiTheme } = useEuiTheme(); + + const { openLeftPanel } = useExpandableFlyoutContext(); + const openPanel = useCallback(() => { + openLeftPanel({ + id: RiskInputsPanelKey, + params: { + riskInputs: userRiskData?.user.risk.inputs, + }, + }); + }, [openLeftPanel, userRiskData?.user.risk.inputs]); + + const lensAttributes = useMemo(() => { + return getRiskScoreSummaryAttributes({ + severity: userRiskData?.user?.risk?.calculated_level, + query: `user.name: ${userRiskData?.user?.name}`, + spaceId: 'default', + riskEntity: RiskScoreEntity.user, + }); + }, [userRiskData]); + + const columns: Array> = useMemo( + () => [ + { + field: 'category', + name: ( + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + }, + { + field: 'count', + name: ( + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + dataType: 'number', + }, + ], + [] + ); + + const xsFontSize = useEuiFontSize('xxs').fontSize; + + const items: TableItem[] = useMemo( + () => [ + { + category: i18n.translate('xpack.securitySolution.flyout.entityDetails.alertsGroupLabel', { + defaultMessage: 'Alerts', + }), + count: userRiskData?.user.risk.inputs?.length ?? 0, + }, + ], + [userRiskData?.user.risk.inputs?.length] + ); + + return ( + +

+ +

+ + } + extraAction={ + + {userRiskData && ( + + ), + }} + /> + )} + + } + > + + + + ), + link: { + callback: openPanel, + tooltip: ( + + ), + }, + iconType: 'arrowStart', + }} + expand={{ + expandable: false, + }} + > + + +
+ {userRiskData && ( + + } + /> + )} +
+
+ + +
+
+ + } + /> +
+ +
+
+
+
+
+ +
+ ); +}); +RiskSummary.displayName = 'RiskSummary'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx similarity index 79% rename from x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx rename to x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx index 1f82d21c653213..746aa8b25e0f1f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx @@ -7,22 +7,33 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; -import { UserDetailsContentComponent } from './user_details_content'; -import { StorybookProviders } from '../../../../common/mock/storybook_providers'; -import { mockManagedUser, mockObservedUser, mockRiskScoreState } from './__mocks__'; +import { EuiFlyout } from '@elastic/eui'; +import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { StorybookProviders } from '../../../common/mock/storybook_providers'; +import { + mockManagedUser, + mockObservedUser, + mockRiskScoreState, +} from '../../../timelines/components/side_panel/new_user_detail/__mocks__'; +import { UserPanelContent } from './content'; -storiesOf('UserDetailsContent', module) +const flyoutContextValue = { + openLeftPanel: () => window.alert('openLeftPanel called'), + panels: {}, +} as unknown as ExpandableFlyoutContext; + +storiesOf('Components/UserPanelContent', module) .addDecorator((storyFn) => ( - {}}> - {storyFn()} - + + {}}> + {storyFn()} + + )) .add('default', () => ( - )) .add('integration disabled', () => ( - )) .add('no managed data', () => ( - )) .add('no observed data', () => ( - )) .add('loading', () => ( - ; + contextID: string; + scopeId: string; + isDraggable: boolean; +} + +export const UserPanelContent = ({ + observedUser, + managedUser, + riskScoreState, + contextID, + scopeId, + isDraggable, +}: UserPanelContentProps) => { + return ( + + {riskScoreState.isModuleEnabled && riskScoreState.data?.length !== 0 && ( + <> + + + + )} + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/header.test.tsx similarity index 67% rename from x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx rename to x-pack/plugins/security_solution/public/flyout/entity_details/user_right/header.test.tsx index 2d984cc926e4ff..e4e4186919b6d8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/header.test.tsx @@ -7,36 +7,37 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { TestProviders } from '../../../../common/mock'; -import { mockManagedUser, mockObservedUser, mockRiskScoreState } from './__mocks__'; -import { UserDetailsContentComponent } from './user_details_content'; +import { TestProviders } from '../../../common/mock'; +import { + mockManagedUser, + mockObservedUser, +} from '../../../timelines/components/side_panel/new_user_detail/__mocks__'; +import { UserPanelHeader } from './header'; const mockProps = { userName: 'test', managedUser: mockManagedUser, observedUser: mockObservedUser, - riskScoreState: mockRiskScoreState, - contextID: 'test-user-details', - scopeId: 'test-scope-id', - isDraggable: false, }; -describe('UserDetailsContentComponent', () => { +jest.mock('../../../common/components/visualization_actions/visualization_embeddable'); + +describe('UserDetailsContent', () => { it('renders', () => { const { getByTestId } = render( - + ); - expect(getByTestId('user-details-content-header')).toBeInTheDocument(); + expect(getByTestId('user-panel-header')).toBeInTheDocument(); }); it('renders observed user date when it is bigger than managed user date', () => { const futureDay = '2989-03-07T20:00:00.000Z'; const { getByTestId } = render( - { ); - expect(getByTestId('user-details-content-lastSeen').textContent).toContain('Mar 7, 2989'); + expect(getByTestId('user-panel-header-lastSeen').textContent).toContain('Mar 7, 2989'); }); it('renders managed user date when it is bigger than observed user date', () => { const futureDay = '2989-03-07T20:00:00.000Z'; const { getByTestId } = render( - { ); - expect(getByTestId('user-details-content-lastSeen').textContent).toContain('Mar 7, 2989'); + expect(getByTestId('user-panel-header-lastSeen').textContent).toContain('Mar 7, 2989'); }); it('renders observed and managed badges when lastSeen is defined', () => { const { getByTestId } = render( - + ); - expect(getByTestId('user-details-content-observed-badge')).toBeInTheDocument(); - expect(getByTestId('user-details-content-managed-badge')).toBeInTheDocument(); + expect(getByTestId('user-panel-header-observed-badge')).toBeInTheDocument(); + expect(getByTestId('user-panel-header-managed-badge')).toBeInTheDocument(); }); it('does not render observed badge when lastSeen date is undefined', () => { const { queryByTestId } = render( - { ); - expect(queryByTestId('user-details-content-observed-badge')).not.toBeInTheDocument(); + expect(queryByTestId('user-panel-header-observed-badge')).not.toBeInTheDocument(); }); it('does not render managed badge when lastSeen date is undefined', () => { const { queryByTestId } = render( - { ); - expect(queryByTestId('user-details-content-managed-badge')).not.toBeInTheDocument(); + expect(queryByTestId('user-panel-header-managed-badge')).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/header.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/header.tsx new file mode 100644 index 00000000000000..12acfd0d2815d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/header.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiSpacer, EuiBadge, EuiText, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; +import { max } from 'lodash/fp'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; +import { getUsersDetailsUrl } from '../../../common/components/link_to/redirect_to_users'; +import type { + ManagedUserData, + ObservedUserData, +} from '../../../timelines/components/side_panel/new_user_detail/types'; + +import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; +import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; +import { FlyoutHeader } from '../../shared/components/flyout_header'; +import { FlyoutTitle } from '../../shared/components/flyout_title'; + +interface UserPanelHeaderProps { + userName: string; + observedUser: ObservedUserData; + managedUser: ManagedUserData; +} + +export const UserPanelHeader = ({ userName, observedUser, managedUser }: UserPanelHeaderProps) => { + const lastSeenDate = useMemo( + () => + max([observedUser.lastSeen, managedUser.lastSeen].map((el) => el.date && new Date(el.date))), + [managedUser.lastSeen, observedUser.lastSeen] + ); + + return ( + + + + + {lastSeenDate && } + + + + + + + + + + + + {observedUser.lastSeen.date && ( + + + + )} + + + {managedUser.lastSeen.date && ( + + + + )} + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx new file mode 100644 index 00000000000000..e06399305fe05d --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx @@ -0,0 +1,112 @@ +/* + * 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 { TestProviders } from '../../../common/mock'; +import type { UserPanelProps } from '.'; +import { UserPanel } from '.'; +import { mockRiskScoreState } from './mocks'; + +import { + mockManagedUser, + mockObservedUser, +} from '../../../timelines/components/side_panel/new_user_detail/__mocks__'; + +const mockProps: UserPanelProps = { + userName: 'test', + contextID: 'test-user-panel', + scopeId: 'test-scope-id', + isDraggable: false, +}; + +jest.mock('../../../common/components/visualization_actions/visualization_embeddable'); + +const mockedUseRiskScore = jest.fn().mockReturnValue(mockRiskScoreState); +jest.mock('../../../explore/containers/risk_score', () => ({ + useRiskScore: () => mockedUseRiskScore(), +})); + +const mockedUseManagedUser = jest.fn().mockReturnValue(mockManagedUser); +const mockedUseObservedUser = jest.fn().mockReturnValue(mockObservedUser); + +jest.mock('../../../timelines/components/side_panel/new_user_detail/hooks', () => { + const originalModule = jest.requireActual( + '../../../timelines/components/side_panel/new_user_detail/hooks' + ); + return { + ...originalModule, + useManagedUser: () => mockedUseManagedUser(), + useObservedUser: () => mockedUseObservedUser(), + }; +}); + +describe('UserPanel', () => { + beforeEach(() => { + mockedUseRiskScore.mockReturnValue(mockRiskScoreState); + mockedUseManagedUser.mockReturnValue(mockManagedUser); + mockedUseObservedUser.mockReturnValue(mockObservedUser); + }); + + it('renders', () => { + const { getByTestId, queryByTestId } = render( + + + + ); + + expect(getByTestId('user-panel-header')).toBeInTheDocument(); + expect(queryByTestId('securitySolutionFlyoutLoading')).not.toBeInTheDocument(); + expect(getByTestId('securitySolutionFlyoutNavigationExpandDetailButton')).toBeInTheDocument(); + }); + + it('renders loading state when risk score is loading', () => { + mockedUseRiskScore.mockReturnValue({ + ...mockRiskScoreState, + data: undefined, + loading: true, + }); + + const { getByTestId } = render( + + + + ); + + expect(getByTestId('securitySolutionFlyoutLoading')).toBeInTheDocument(); + }); + + it('renders loading state when observed user is loading', () => { + mockedUseObservedUser.mockReturnValue({ + ...mockObservedUser, + isLoading: true, + }); + + const { getByTestId } = render( + + + + ); + + expect(getByTestId('securitySolutionFlyoutLoading')).toBeInTheDocument(); + }); + + it('renders loading state when managed user is loading', () => { + mockedUseManagedUser.mockReturnValue({ + ...mockManagedUser, + isLoading: true, + }); + + const { getByTestId } = render( + + + + ); + + expect(getByTestId('securitySolutionFlyoutLoading')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx new file mode 100644 index 00000000000000..8f22560730bf0f --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { useQueryInspector } from '../../../common/components/page/manage_query'; +import { UsersType } from '../../../explore/users/store/model'; +import { getCriteriaFromUsersType } from '../../../common/components/ml/criteria/get_criteria_from_users_type'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; +import { + useManagedUser, + useObservedUser, +} from '../../../timelines/components/side_panel/new_user_detail/hooks'; +import { AnomalyTableProvider } from '../../../common/components/ml/anomaly/anomaly_table_provider'; +import { buildUserNamesFilter } from '../../../../common/search_strategy'; +import { useRiskScore } from '../../../explore/containers/risk_score'; +import { RiskScoreEntity } from '../../../../common/risk_engine'; +import { FlyoutLoading } from '../../shared/components/flyout_loading'; +import { RiskInputsPanelKey } from '../risk_inputs_left'; +import { FlyoutNavigation } from '../../shared/components/flyout_navigation'; +import { UserPanelContent } from './content'; +import { UserPanelHeader } from './header'; + +export interface UserPanelProps extends Record { + contextID: string; + scopeId: string; + userName: string; + isDraggable?: boolean; +} + +export interface UserPanelExpandableFlyoutProps extends FlyoutPanelProps { + key: 'user-panel'; + params: UserPanelProps; +} + +export const UserPanelKey: UserPanelExpandableFlyoutProps['key'] = 'user-panel'; +export const USER_PANEL_RISK_SCORE_QUERY_ID = 'userPanelRiskScoreQuery'; +const FIRST_RECORD_PAGINATION = { + cursorStart: 0, + querySize: 1, +}; + +export const UserPanel = ({ contextID, scopeId, userName, isDraggable }: UserPanelProps) => { + const userNameFilterQuery = useMemo( + () => (userName ? buildUserNamesFilter([userName]) : undefined), + [userName] + ); + + const riskScoreState = useRiskScore({ + riskEntity: RiskScoreEntity.user, + filterQuery: userNameFilterQuery, + onlyLatest: false, + pagination: FIRST_RECORD_PAGINATION, + }); + + const { inspect, refetch, loading } = riskScoreState; + const { to, from, isInitializing, setQuery, deleteQuery } = useGlobalTime(); + + const observedUser = useObservedUser(userName); + const managedUser = useManagedUser(userName); + + const { data: userRisk } = riskScoreState; + const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; + + useQueryInspector({ + deleteQuery, + inspect, + loading, + queryId: USER_PANEL_RISK_SCORE_QUERY_ID, + refetch, + setQuery, + }); + + const { openLeftPanel } = useExpandableFlyoutContext(); + const openPanel = useCallback(() => { + openLeftPanel({ + id: RiskInputsPanelKey, + params: { + riskInputs: userRiskData?.user.risk.inputs, + }, + }); + }, [openLeftPanel, userRiskData?.user.risk.inputs]); + + if (riskScoreState.loading || observedUser.isLoading || managedUser.isLoading) { + return ; + } + + return ( + + {({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => { + const observedUserWithAnomalies = { + ...observedUser, + anomalies: { + isLoading: isLoadingAnomaliesData, + anomalies: anomaliesData, + jobNameById, + }, + }; + return ( + <> + + + + + ); + }} + + ); +}; + +UserPanel.displayName = 'UserPanel'; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/mocks/index.ts b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/mocks/index.ts new file mode 100644 index 00000000000000..bfd1a9a8021990 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/mocks/index.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RiskScoreState } from '../../../../explore/containers/risk_score'; +import type { RiskScoreEntity, UserRiskScore } from '../../../../../common/search_strategy'; +import { RiskSeverity } from '../../../../../common/search_strategy'; +import { RiskCategories } from '../../../../../common/risk_engine'; + +const userRiskScore: UserRiskScore = { + '@timestamp': '626569200000', + user: { + name: 'test', + risk: { + rule_risks: [], + calculated_score_norm: 70, + multipliers: [], + calculated_level: RiskSeverity.high, + inputs: [ + { + id: '_id', + index: '_index', + category: RiskCategories.category_1, + description: 'Alert from Rule: My rule', + risk_score: 30, + timestamp: '2021-08-19T18:55:59.000Z', + }, + ], + }, + }, + alertsCount: 0, + oldestAlertTimestamp: '626569200000', +}; + +export const mockRiskScoreState: RiskScoreState = { + data: [userRiskScore], + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: () => {}, + totalCount: 0, + isModuleEnabled: true, + isAuthorized: true, + isDeprecated: false, + loading: false, +}; diff --git a/x-pack/plugins/security_solution/public/flyout/index.tsx b/x-pack/plugins/security_solution/public/flyout/index.tsx index a29654659c74c5..1c8ac6c4cc2c90 100644 --- a/x-pack/plugins/security_solution/public/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/index.tsx @@ -12,21 +12,28 @@ import { ExpandableFlyoutProvider, } from '@kbn/expandable-flyout'; import type { IsolateHostPanelProps } from './document_details/isolate_host'; -import { IsolateHostPanel, IsolateHostPanelKey } from './document_details/isolate_host'; +import { + IsolateHostPanel, + DocumentDetailsIsolateHostPanelKey, +} from './document_details/isolate_host'; import { IsolateHostPanelProvider } from './document_details/isolate_host/context'; import type { RightPanelProps } from './document_details/right'; -import { RightPanel, RightPanelKey } from './document_details/right'; +import { RightPanel, DocumentDetailsRightPanelKey } from './document_details/right'; import { RightPanelProvider } from './document_details/right/context'; import type { LeftPanelProps } from './document_details/left'; -import { LeftPanel, LeftPanelKey } from './document_details/left'; +import { LeftPanel, DocumentDetailsLeftPanelKey } from './document_details/left'; import { LeftPanelProvider } from './document_details/left/context'; import { SecuritySolutionFlyoutUrlSyncProvider, useSecurityFlyoutUrlSync, } from './document_details/shared/context/url_sync'; import type { PreviewPanelProps } from './document_details/preview'; -import { PreviewPanel, PreviewPanelKey } from './document_details/preview'; +import { PreviewPanel, DocumentDetailsPreviewPanelKey } from './document_details/preview'; import { PreviewPanelProvider } from './document_details/preview/context'; +import type { UserPanelExpandableFlyoutProps } from './entity_details/user_right'; +import { UserPanel, UserPanelKey } from './entity_details/user_right'; +import type { RiskInputsExpandableFlyoutProps } from './entity_details/risk_inputs_left'; +import { RiskInputsPanel, RiskInputsPanelKey } from './entity_details/risk_inputs_left'; /** * List of all panels that will be used within the document details expandable flyout. @@ -34,7 +41,7 @@ import { PreviewPanelProvider } from './document_details/preview/context'; */ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] = [ { - key: RightPanelKey, + key: DocumentDetailsRightPanelKey, component: (props) => ( @@ -42,7 +49,7 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] ), }, { - key: LeftPanelKey, + key: DocumentDetailsLeftPanelKey, component: (props) => ( @@ -50,7 +57,7 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] ), }, { - key: PreviewPanelKey, + key: DocumentDetailsPreviewPanelKey, component: (props) => ( @@ -58,13 +65,23 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] ), }, { - key: IsolateHostPanelKey, + key: DocumentDetailsIsolateHostPanelKey, component: (props) => ( ), }, + { + key: UserPanelKey, + component: (props) => , + }, + { + key: RiskInputsPanelKey, + component: (props) => ( + + ), + }, ]; const OuterProviders: FC = ({ children }) => { diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx index 2ee81c42949be6..2bb850e0c312c7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/risk_score_donut_chart.tsx @@ -8,11 +8,11 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; +import { RISK_SEVERITY_COLOUR } from '../../../../entity_analytics/common/utils'; import type { SeverityCount } from '../../../../explore/components/risk_score/severity/types'; import { useRiskDonutChartData } from './use_risk_donut_chart_data'; import type { FillColor } from '../../../../common/components/charts/donutchart'; import { emptyDonutColor } from '../../../../common/components/charts/donutchart_empty'; -import { RISK_SEVERITY_COLOUR } from '../../../../explore/components/risk_score/severity/common'; import { DonutChart } from '../../../../common/components/charts/donutchart'; import { Legend } from '../../../../common/components/charts/legend'; import { ChartLabel } from '../../detection_response/alerts_by_status/chart_label'; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/use_risk_donut_chart_data.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/use_risk_donut_chart_data.ts index 6ba4ad7c03d1b0..4b31142aef3c92 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/use_risk_donut_chart_data.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/common/use_risk_donut_chart_data.ts @@ -6,10 +6,10 @@ */ import { sum } from 'lodash/fp'; import { useMemo } from 'react'; +import { RISK_SEVERITY_COLOUR } from '../../../../entity_analytics/common/utils'; import type { LegendItem } from '../../../../common/components/charts/legend_item'; import type { SeverityCount } from '../../../../explore/components/risk_score/severity/types'; import type { DonutChartProps } from '../../../../common/components/charts/donutchart'; -import { RISK_SEVERITY_COLOUR } from '../../../../explore/components/risk_score/severity/common'; import type { RiskSeverity } from '../../../../../common/search_strategy'; const legendField = 'kibana.alert.severity'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx index bf590a5d55cdf4..ad33fdfe1b73ed 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx @@ -23,7 +23,6 @@ import { EventDetailsPanel } from './event_details'; import { HostDetailsPanel } from './host_details'; import { NetworkDetailsPanel } from './network_details'; import { UserDetailsPanel } from './user_details'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; interface DetailsPanelProps { browserFields: BrowserFields; @@ -53,7 +52,6 @@ export const DetailsPanel = React.memo( isReadOnly, }: DetailsPanelProps) => { const dispatch = useDispatch(); - const isNewUserDetailsFlyoutEnable = useIsExperimentalFeatureEnabled('newUserDetailsFlyout'); const getScope = useMemo(() => { if (isTimelineScope(scopeId)) { return timelineSelectors.getTimelineByIdSelector(); @@ -142,9 +140,6 @@ export const DetailsPanel = React.memo( if (currentTabDetail?.panelView === 'userDetail' && currentTabDetail?.params?.userName) { flyoutUniqueKey = currentTabDetail.params.userName; - if (isNewUserDetailsFlyoutEnable) { - panelSize = 'm'; - } visiblePanel = ( ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx index 9e0e60caf80de8..a8e1744c0c04ec 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { fireEvent, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; import { ManagedUser } from './managed_user'; @@ -29,19 +29,6 @@ describe('ManagedUser', () => { expect(getByTestId('managedUser-data')).toBeInTheDocument(); }); - it('updates the accordion button title when visibility toggles', () => { - const { getByTestId } = render( - - - - ); - const accordionButton = getByTestId('managedUser-accordion-button'); - - expect(accordionButton).toHaveTextContent('Show Azure AD data'); - fireEvent.click(accordionButton); - expect(accordionButton).toHaveTextContent('Hide Azure AD data'); - }); - it('renders the formatted date', () => { const { getByTestId } = render( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx index a802b49e85cae6..5cb8f2138e3635 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/managed_user.tsx @@ -14,9 +14,10 @@ import { useEuiTheme, EuiEmptyPrompt, EuiCallOut, + useEuiFontSize, } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import * as i18n from './translations'; @@ -44,10 +45,6 @@ export const ManagedUser = ({ }) => { const { euiTheme } = useEuiTheme(); const managedItems = useManagedUserItems(managedUser.details); - const [isManagedDataToggleOpen, setManagedDataToggleOpen] = useState(false); - const onToggleManagedData = useCallback(() => { - setManagedDataToggleOpen((isOpen) => !isOpen); - }, [setManagedDataToggleOpen]); const managedUserTableColumns = useMemo( () => getManagedUserTableColumns(contextID, scopeId, isDraggable), [isDraggable, contextID, scopeId] @@ -59,40 +56,16 @@ export const ManagedUser = ({ [getAppUrl] ); - if (!managedUser.isLoading && !managedUser.isIntegrationEnabled) { - return ( - <> - -
{i18n.MANAGED_DATA_TITLE}
-
- - - {i18n.NO_ACTIVE_INTEGRATION_TITLE}} - body={

{i18n.NO_ACTIVE_INTEGRATION_TEXT}

} - actions={ - - {i18n.ADD_EXTERNAL_INTEGRATION_BUTTON} - - } - /> -
- - ); - } + const xsFontSize = useEuiFontSize('xxs').fontSize; return ( <> - -
{i18n.MANAGED_DATA_TITLE}
-
- +
{i18n.MANAGED_DATA_TITLE}
+ } - onToggle={onToggleManagedData} extraAction={ <> {managedUser.lastSeen.date && ( - - ), - }} - /> + + + ), + }} + /> + )} } @@ -138,16 +118,34 @@ export const ManagedUser = ({ } `} > - - {managedItems || managedUser.isLoading ? ( - + {!managedUser.isLoading && !managedUser.isIntegrationEnabled ? ( + + {i18n.NO_ACTIVE_INTEGRATION_TITLE}} + titleSize="s" + body={

{i18n.NO_ACTIVE_INTEGRATION_TEXT}

} + actions={ + + {i18n.ADD_EXTERNAL_INTEGRATION_BUTTON} + + } /> - ) : ( - <> +
+ ) : ( + <> + {managedItems || managedUser.isLoading ? ( + + ) : (

{i18n.NO_AZURE_DATA_TEXT}

- - )} -
+ )} + + )}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx index f57eda9b1fb263..90934c533a2c46 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { fireEvent, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; import { mockObservedUser } from './__mocks__'; @@ -29,19 +29,6 @@ describe('ObservedUser', () => { expect(getByTestId('observedUser-data')).toBeInTheDocument(); }); - it('updates the accordion button title when visibility toggles', () => { - const { getByTestId } = render( - - - - ); - const accordionButton = getByTestId('observedUser-accordion-button'); - - expect(accordionButton).toHaveTextContent('Show observed data'); - fireEvent.click(accordionButton); - expect(accordionButton).toHaveTextContent('Hide observed data'); - }); - it('renders the formatted date', () => { const { getByTestId } = render( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx index 01335997813a02..9a0891c24a925f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/observed_user.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { EuiAccordion, EuiSpacer, EuiTitle, useEuiTheme, EuiPanel } from '@elastic/eui'; +import { EuiAccordion, EuiSpacer, EuiTitle, useEuiTheme, useEuiFontSize } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import * as i18n from './translations'; @@ -33,27 +33,21 @@ export const ObservedUser = ({ }) => { const { euiTheme } = useEuiTheme(); const observedItems = useObservedUserItems(observedUser); - const [isObservedDataToggleOpen, setObservedDataToggleOpen] = useState(false); - const onToggleObservedData = useCallback(() => { - setObservedDataToggleOpen((isOpen) => !isOpen); - }, [setObservedDataToggleOpen]); + const observedUserTableColumns = useMemo( () => getObservedUserTableColumns(contextID, scopeId, isDraggable), [contextID, scopeId, isDraggable] ); + const xsFontSize = useEuiFontSize('xxs').fontSize; return ( <> - -
{i18n.OBSERVED_DATA_TITLE}
-
- +

{i18n.OBSERVED_DATA_TITLE}

+ } - onToggle={onToggleObservedData} extraAction={ <> {observedUser.lastSeen.date && ( - - ), - }} - /> + + + ), + }} + /> + )} } @@ -101,19 +100,19 @@ export const ObservedUser = ({ } `} > - - - + + +
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts index 01563fcb947813..4671cf815162dd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/translations.ts @@ -46,20 +46,6 @@ export const OBSERVED_DATA_TITLE = i18n.translate( } ); -export const HIDE_OBSERVED_DATA_BUTTON = i18n.translate( - 'xpack.securitySolution.timeline.userDetails.hideObservedDataButton', - { - defaultMessage: 'Hide observed data', - } -); - -export const SHOW_OBSERVED_DATA_BUTTON = i18n.translate( - 'xpack.securitySolution.timeline.userDetails.showObservedDataButton', - { - defaultMessage: 'Show observed data', - } -); - export const HIDE_AZURE_DATA_BUTTON = i18n.translate( 'xpack.securitySolution.timeline.userDetails.hideManagedDataButton', { @@ -166,7 +152,7 @@ export const PHONE = i18n.translate('xpack.securitySolution.timeline.userDetails export const NO_ACTIVE_INTEGRATION_TITLE = i18n.translate( 'xpack.securitySolution.timeline.userDetails.noActiveIntegrationTitle', { - defaultMessage: 'You don’t have any active integrations', + defaultMessage: 'You don’t have any active asset repository integrations', } ); @@ -174,14 +160,14 @@ export const NO_ACTIVE_INTEGRATION_TEXT = i18n.translate( 'xpack.securitySolution.timeline.userDetails.noActiveIntegrationText', { defaultMessage: - 'External integrations can provide additional metadata and help you manage users.', + 'Additional metadata from integrations may help you to manage and identify risky entities.', } ); export const ADD_EXTERNAL_INTEGRATION_BUTTON = i18n.translate( 'xpack.securitySolution.timeline.userDetails.addExternalIntegrationButton', { - defaultMessage: 'Add external integrations', + defaultMessage: 'Add asset repository integrations', } ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx deleted file mode 100644 index 95ffb05a168897..00000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/new_user_detail/user_details_content.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiSpacer, - EuiHorizontalRule, - EuiIcon, - EuiBadge, - EuiText, - EuiFlexItem, - EuiFlexGroup, - useEuiFontSize, - useEuiTheme, - euiTextBreakWord, - EuiProgress, -} from '@elastic/eui'; - -import React, { useMemo } from 'react'; -import { css } from '@emotion/react'; -import { max } from 'lodash'; -import * as i18n from './translations'; - -import { RiskScoreEntity } from '../../../../../common/search_strategy'; -import { UserDetailsLink } from '../../../../common/components/links'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import type { RiskScoreState } from '../../../../explore/containers/risk_score'; -import { useRiskScore } from '../../../../explore/containers/risk_score'; - -import { useManagedUser, useObservedUser } from './hooks'; -import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/anomaly_table_provider'; -import { getCriteriaFromUsersType } from '../../../../common/components/ml/criteria/get_criteria_from_users_type'; -import { UsersType } from '../../../../explore/users/store/model'; -import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; -import type { ManagedUserData, ObservedUserData } from './types'; -import { RiskScoreField } from './risk_score_field'; -import { ObservedUser } from './observed_user'; -import { ManagedUser } from './managed_user'; - -export const QUERY_ID = 'usersDetailsQuery'; - -interface UserDetailsContentComponentProps { - userName: string; - observedUser: ObservedUserData; - managedUser: ManagedUserData; - riskScoreState: RiskScoreState; - contextID: string; - scopeId: string; - isDraggable: boolean; -} - -/** - * This is a visual component. It doesn't access any external Context or API. - * It designed for unit testing the UI and previewing changes on storybook. - */ -export const UserDetailsContentComponent = ({ - userName, - observedUser, - managedUser, - riskScoreState, - contextID, - scopeId, - isDraggable, -}: UserDetailsContentComponentProps) => { - const { euiTheme } = useEuiTheme(); - const { fontSize: xlFontSize } = useEuiFontSize('xl'); - - const lastSeenDate = useMemo( - () => - max([observedUser.lastSeen, managedUser.lastSeen].map((el) => el.date && new Date(el.date))), - [managedUser.lastSeen, observedUser.lastSeen] - ); - - return ( - <> - - - - - {i18n.USER} - - - {observedUser.lastSeen.date && ( - - {i18n.OBSERVED_BADGE} - - )} - - - {managedUser.lastSeen.date && ( - - {i18n.MANAGED_BADGE} - - )} - - - - - {observedUser.lastSeen.isLoading || managedUser.lastSeen.isLoading ? ( - - ) : ( - - )} - - - - - {userName} - - - - - {i18n.LAST_SEEN} - {': '} - {lastSeenDate && } - - - - - - - - - - ); -}; - -export const UserDetailsContent = ({ - userName, - contextID, - scopeId, - isDraggable = false, -}: { - userName: string; - contextID: string; - scopeId: string; - isDraggable?: boolean; -}) => { - const { to, from, isInitializing } = useGlobalTime(); - const riskScoreState = useRiskScore({ - riskEntity: RiskScoreEntity.user, - }); - const observedUser = useObservedUser(userName); - const managedUser = useManagedUser(userName); - - return ( - - {({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => ( - - )} - - ); -}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx index 17a1e21ceebd0b..4eefc6aa44f8a0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/index.tsx @@ -6,13 +6,9 @@ */ import React from 'react'; -import { EuiFlyoutBody, EuiSpacer, EuiButtonIcon } from '@elastic/eui'; -import { css } from '@emotion/react'; import { UserDetailsFlyout } from './user_details_flyout'; import { UserDetailsSidePanel } from './user_details_side_panel'; import type { UserDetailsProps } from './types'; -import { UserDetailsContent } from '../new_user_detail/user_details_content'; -import * as i18n from './translations'; const UserDetailsPanelComponent = ({ contextID, @@ -21,40 +17,7 @@ const UserDetailsPanelComponent = ({ handleOnClose, isFlyoutView, isDraggable, - isNewUserDetailsFlyoutEnable, }: UserDetailsProps) => { - if (isNewUserDetailsFlyoutEnable) { - return isFlyoutView ? ( - - - - ) : ( -
- - - - -
- ); - } - return isFlyoutView ? ( ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts index 96d17c57c08f4a..9e1fd0a6b1498f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/types.ts @@ -12,5 +12,4 @@ export interface UserDetailsProps { handleOnClose: () => void; isFlyoutView?: boolean; isDraggable?: boolean; - isNewUserDetailsFlyoutEnable?: boolean; } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx index 58b2a8fbbbb971..083d0651fbab43 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx @@ -9,9 +9,13 @@ import React, { useCallback, useContext, useMemo } from 'react'; import type { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { isString } from 'lodash/fp'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { TableId } from '@kbn/securitysolution-data-table'; +import { UserPanelKey } from '../../../../../flyout/entity_details/user_right'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { StatefulEventContext } from '../../../../../common/components/events_viewer/stateful_event_context'; import type { ExpandedDetailType } from '../../../../../../common/types'; -import { getScopedActions } from '../../../../../helpers'; +import { getScopedActions, isTimelineScope } from '../../../../../helpers'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { DefaultDraggable } from '../../../../../common/components/draggables'; import { getEmptyTagValue } from '../../../../../common/components/empty_value'; @@ -48,9 +52,11 @@ const UserNameComponent: React.FC = ({ }) => { const dispatch = useDispatch(); const eventContext = useContext(StatefulEventContext); + const isNewUserDetailsFlyoutEnable = useIsExperimentalFeatureEnabled('newUserDetailsFlyout'); const userName = `${value}`; - const isInTimelineContext = userName && eventContext?.timelineID; + const { openRightPanel } = useExpandableFlyoutContext(); + const openUserDetailsSidePanel = useCallback( (e) => { e.preventDefault(); @@ -58,31 +64,55 @@ const UserNameComponent: React.FC = ({ if (onClick) { onClick(); } + if (eventContext && isInTimelineContext) { const { timelineID, tabType } = eventContext; - const updatedExpandedDetail: ExpandedDetailType = { - panelView: 'userDetail', - params: { - userName, - }, - }; - const scopedActions = getScopedActions(timelineID); - if (scopedActions) { - dispatch( - scopedActions.toggleDetailPanel({ - ...updatedExpandedDetail, - id: timelineID, - tabType: tabType as TimelineTabs, - }) - ); - } - if (timelineID === TimelineId.active && tabType === TimelineTabs.query) { - activeTimeline.toggleExpandedDetail({ ...updatedExpandedDetail }); + if (isNewUserDetailsFlyoutEnable && !isTimelineScope(timelineID)) { + openRightPanel({ + id: UserPanelKey, + params: { + userName, + contextID: contextId, + scopeId: TableId.alertsOnAlertsPage, + isDraggable, + }, + }); + } else { + const updatedExpandedDetail: ExpandedDetailType = { + panelView: 'userDetail', + params: { + userName, + }, + }; + const scopedActions = getScopedActions(timelineID); + if (scopedActions) { + dispatch( + scopedActions.toggleDetailPanel({ + ...updatedExpandedDetail, + id: timelineID, + tabType: tabType as TimelineTabs, + }) + ); + } + + if (timelineID === TimelineId.active && tabType === TimelineTabs.query) { + activeTimeline.toggleExpandedDetail({ ...updatedExpandedDetail }); + } } } }, - [onClick, eventContext, isInTimelineContext, userName, dispatch] + [ + onClick, + eventContext, + isNewUserDetailsFlyoutEnable, + isInTimelineContext, + openRightPanel, + userName, + contextId, + isDraggable, + dispatch, + ] ); // The below is explicitly defined this way as the onClick takes precedence when it and the href are both defined diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 22771a5feae0b7..360be51913f25d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -36766,7 +36766,6 @@ "xpack.securitySolution.timeline.userDetails.firstSeenLabel": "Vu en premier", "xpack.securitySolution.timeline.userDetails.fullNameLabel": "Nom complet", "xpack.securitySolution.timeline.userDetails.hideManagedDataButton": "Masquer les données Azure AD", - "xpack.securitySolution.timeline.userDetails.hideObservedDataButton": "Masquer les données observées", "xpack.securitySolution.timeline.userDetails.hostOsNameLabel": "Système d'exploitation", "xpack.securitySolution.timeline.userDetails.ipAddressesLabel": "Adresses IP", "xpack.securitySolution.timeline.userDetails.lastNameLabel": "Nom", @@ -36785,7 +36784,6 @@ "xpack.securitySolution.timeline.userDetails.phoneLabel": "Téléphone", "xpack.securitySolution.timeline.userDetails.riskScoreLabel": "Score de risque", "xpack.securitySolution.timeline.userDetails.showManagedDataButton": "Afficher les données Azure AD", - "xpack.securitySolution.timeline.userDetails.showObservedDataButton": "Afficher les données observées", "xpack.securitySolution.timeline.userDetails.userIdLabel": "ID utilisateur", "xpack.securitySolution.timeline.userDetails.userLabel": "Utilisateur", "xpack.securitySolution.timeline.userDetails.valuesColumnTitle": "Valeurs", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8cea00c9c05790..a8e3d4b417f541 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -36764,7 +36764,6 @@ "xpack.securitySolution.timeline.userDetails.firstSeenLabel": "初回の認識", "xpack.securitySolution.timeline.userDetails.fullNameLabel": "フルネーム", "xpack.securitySolution.timeline.userDetails.hideManagedDataButton": "Azure ADデータを非表示", - "xpack.securitySolution.timeline.userDetails.hideObservedDataButton": "観測されたデータを非表示", "xpack.securitySolution.timeline.userDetails.hostOsNameLabel": "オペレーティングシステム", "xpack.securitySolution.timeline.userDetails.ipAddressesLabel": "IP アドレス", "xpack.securitySolution.timeline.userDetails.lastNameLabel": "姓", @@ -36783,7 +36782,6 @@ "xpack.securitySolution.timeline.userDetails.phoneLabel": "電話", "xpack.securitySolution.timeline.userDetails.riskScoreLabel": "リスクスコア", "xpack.securitySolution.timeline.userDetails.showManagedDataButton": "Azure ADデータを表示", - "xpack.securitySolution.timeline.userDetails.showObservedDataButton": "観測されたデータを表示", "xpack.securitySolution.timeline.userDetails.userIdLabel": "ユーザーID", "xpack.securitySolution.timeline.userDetails.userLabel": "ユーザー", "xpack.securitySolution.timeline.userDetails.valuesColumnTitle": "値", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b92382260e44e4..879ccde09a001c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -36760,7 +36760,6 @@ "xpack.securitySolution.timeline.userDetails.firstSeenLabel": "首次看到时间", "xpack.securitySolution.timeline.userDetails.fullNameLabel": "全名", "xpack.securitySolution.timeline.userDetails.hideManagedDataButton": "隐藏 Azure AD 数据", - "xpack.securitySolution.timeline.userDetails.hideObservedDataButton": "隐藏观察数据", "xpack.securitySolution.timeline.userDetails.hostOsNameLabel": "操作系统", "xpack.securitySolution.timeline.userDetails.ipAddressesLabel": "IP 地址", "xpack.securitySolution.timeline.userDetails.lastNameLabel": "姓氏", @@ -36779,7 +36778,6 @@ "xpack.securitySolution.timeline.userDetails.phoneLabel": "电话", "xpack.securitySolution.timeline.userDetails.riskScoreLabel": "风险分数", "xpack.securitySolution.timeline.userDetails.showManagedDataButton": "显示 Azure AD 数据", - "xpack.securitySolution.timeline.userDetails.showObservedDataButton": "显示观察数据", "xpack.securitySolution.timeline.userDetails.userIdLabel": "用户 ID", "xpack.securitySolution.timeline.userDetails.userLabel": "用户", "xpack.securitySolution.timeline.userDetails.valuesColumnTitle": "值",