[9.0] [Security Solution][Risk Score] Use Risk Engine SavedObject intead of localStorage on the Risk Score web page (#215304) (#215440)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Security Solution][Risk Score] Use Risk Engine SavedObject intead of
localStorage on the Risk Score web page
(#215304)](https://github.com/elastic/kibana/pull/215304)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Abhishek
Bhatia","email":"117628830+abhishekbhatia1710@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-21T08:49:24Z","message":"[Security
Solution][Risk Score] Use Risk Engine SavedObject intead of localStorage
on the Risk Score web page (#215304)\n\n## Summary\n\nThe PR updates the
implementation to fetch data from the Risk Engine\nSaved Object instead
of storing and reusing it from LocalStorage.\n\nThis change ensures that
settings are applied globally rather than being\nlimited to the
browser’s LocalStorage. Since the Saved Object holds the\nmost
up-to-date information, it is now used to update the \"Date\" and
the\ntoggle for \"including closed alerts for risk scoring\" across all
web\nbrowsers.\n\n\n### Normal and Incognito Mode :
\n\n\n\nhttps://github.com/user-attachments/assets/7638c88b-ff9e-4d42-9944-e55b53e33518\n\n\n###
Default space vs custom space :
\n\n\n\nhttps://github.com/user-attachments/assets/46bb35c7-3cd9-4b97-9f1c-90ec4ef1241a\n\n\n##
Testing Steps\n\n### Verify Initial Values\n1. Open the Entity Risk
Score web page where the settings are applied.\n2. Ensure that the date
picker and toggle for \"including closed alerts\"\nreflect the values
stored in the Risk Engine Saved Object rather than\nLocalStorage.\n3.
Modify and Save changes,\n - Change the date range in the date picker.\n
- Toggle the \"Include Closed Alerts\" switch.\n\n### Page Refresh
Test\n- Refresh the page and confirm that the modified values persist,
fetched\ncorrectly from the Risk Engine Saved Object.\n\n###
Cross-Browser Test\n- Open the same web page in a different browser or
incognito mode.\n- Verify that the settings are consistent and correctly
loaded from the\nRisk Engine Saved\n Object.\n\n### Expected
Outcome\nThe settings should persist after a page refresh or across
different\nbrowsers.\nThe latest values should always be pulled from the
Risk Engine Saved\nObject.\n\n\n### Checklist\n\nCheck the PR satisfies
following conditions. \n\nReviewers should verify this PR satisfies this
list as well.\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"dbe28b9f94a16fa09684f65ae813eb178672d14e","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","Team:Entity
Analytics","backport:version","v9.1.0"],"title":"[Security
Solution][Risk Score] Use Risk Engine SavedObject intead of localStorage
on the Risk Score web
page","number":215304,"url":"https://github.com/elastic/kibana/pull/215304","mergeCommit":{"message":"[Security
Solution][Risk Score] Use Risk Engine SavedObject intead of localStorage
on the Risk Score web page (#215304)\n\n## Summary\n\nThe PR updates the
implementation to fetch data from the Risk Engine\nSaved Object instead
of storing and reusing it from LocalStorage.\n\nThis change ensures that
settings are applied globally rather than being\nlimited to the
browser’s LocalStorage. Since the Saved Object holds the\nmost
up-to-date information, it is now used to update the \"Date\" and
the\ntoggle for \"including closed alerts for risk scoring\" across all
web\nbrowsers.\n\n\n### Normal and Incognito Mode :
\n\n\n\nhttps://github.com/user-attachments/assets/7638c88b-ff9e-4d42-9944-e55b53e33518\n\n\n###
Default space vs custom space :
\n\n\n\nhttps://github.com/user-attachments/assets/46bb35c7-3cd9-4b97-9f1c-90ec4ef1241a\n\n\n##
Testing Steps\n\n### Verify Initial Values\n1. Open the Entity Risk
Score web page where the settings are applied.\n2. Ensure that the date
picker and toggle for \"including closed alerts\"\nreflect the values
stored in the Risk Engine Saved Object rather than\nLocalStorage.\n3.
Modify and Save changes,\n - Change the date range in the date picker.\n
- Toggle the \"Include Closed Alerts\" switch.\n\n### Page Refresh
Test\n- Refresh the page and confirm that the modified values persist,
fetched\ncorrectly from the Risk Engine Saved Object.\n\n###
Cross-Browser Test\n- Open the same web page in a different browser or
incognito mode.\n- Verify that the settings are consistent and correctly
loaded from the\nRisk Engine Saved\n Object.\n\n### Expected
Outcome\nThe settings should persist after a page refresh or across
different\nbrowsers.\nThe latest values should always be pulled from the
Risk Engine Saved\nObject.\n\n\n### Checklist\n\nCheck the PR satisfies
following conditions. \n\nReviewers should verify this PR satisfies this
list as well.\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"dbe28b9f94a16fa09684f65ae813eb178672d14e"}},"sourceBranch":"main","suggestedTargetBranches":["9.0"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215304","number":215304,"mergeCommit":{"message":"[Security
Solution][Risk Score] Use Risk Engine SavedObject intead of localStorage
on the Risk Score web page (#215304)\n\n## Summary\n\nThe PR updates the
implementation to fetch data from the Risk Engine\nSaved Object instead
of storing and reusing it from LocalStorage.\n\nThis change ensures that
settings are applied globally rather than being\nlimited to the
browser’s LocalStorage. Since the Saved Object holds the\nmost
up-to-date information, it is now used to update the \"Date\" and
the\ntoggle for \"including closed alerts for risk scoring\" across all
web\nbrowsers.\n\n\n### Normal and Incognito Mode :
\n\n\n\nhttps://github.com/user-attachments/assets/7638c88b-ff9e-4d42-9944-e55b53e33518\n\n\n###
Default space vs custom space :
\n\n\n\nhttps://github.com/user-attachments/assets/46bb35c7-3cd9-4b97-9f1c-90ec4ef1241a\n\n\n##
Testing Steps\n\n### Verify Initial Values\n1. Open the Entity Risk
Score web page where the settings are applied.\n2. Ensure that the date
picker and toggle for \"including closed alerts\"\nreflect the values
stored in the Risk Engine Saved Object rather than\nLocalStorage.\n3.
Modify and Save changes,\n - Change the date range in the date picker.\n
- Toggle the \"Include Closed Alerts\" switch.\n\n### Page Refresh
Test\n- Refresh the page and confirm that the modified values persist,
fetched\ncorrectly from the Risk Engine Saved Object.\n\n###
Cross-Browser Test\n- Open the same web page in a different browser or
incognito mode.\n- Verify that the settings are consistent and correctly
loaded from the\nRisk Engine Saved\n Object.\n\n### Expected
Outcome\nThe settings should persist after a page refresh or across
different\nbrowsers.\nThe latest values should always be pulled from the
Risk Engine Saved\nObject.\n\n\n### Checklist\n\nCheck the PR satisfies
following conditions. \n\nReviewers should verify this PR satisfies this
list as well.\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"dbe28b9f94a16fa09684f65ae813eb178672d14e"}}]}]
BACKPORT-->

Co-authored-by: Abhishek Bhatia <117628830+abhishekbhatia1710@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2025-03-21 15:58:41 +01:00 committed by GitHub
parent 9b73c01bd6
commit c0a9009362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 50 additions and 101 deletions

View file

@ -21,4 +21,8 @@ import { DateRange } from '../common/common.gen';
export type ReadRiskEngineSettingsResponse = z.infer<typeof ReadRiskEngineSettingsResponse>;
export const ReadRiskEngineSettingsResponse = z.object({
range: DateRange.optional(),
/**
* Include closed alerts in the risk score calculation
*/
includeClosedAlerts: z.boolean().optional(),
});

View file

@ -21,3 +21,6 @@ paths:
properties:
range:
$ref: '../common/common.schema.yaml#/components/schemas/DateRange'
includeClosedAlerts:
type: boolean
description: Include closed alerts in the risk score calculation

View file

@ -22,10 +22,8 @@ describe('RiskScoreConfigurationSection', () => {
const mockConfigureSO = useConfigureSORiskEngineMutation as jest.Mock;
const defaultProps = {
includeClosedAlerts: false,
setIncludeClosedAlerts: jest.fn(),
from: 'now-30d',
to: 'now',
onDateChange: jest.fn(),
};
const mockAddSuccess = jest.fn();
@ -50,13 +48,17 @@ describe('RiskScoreConfigurationSection', () => {
<RiskScoreConfigurationSection {...defaultProps} includeClosedAlerts={true} />
);
wrapper.find(EuiSwitch).simulate('click');
expect(defaultProps.setIncludeClosedAlerts).toHaveBeenCalledWith(true);
expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true);
act(() => {
wrapper.find(EuiSwitch).simulate('click');
});
wrapper.update();
expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true);
});
it('calls onDateChange on date change', () => {
const wrapper = mount(<RiskScoreConfigurationSection {...defaultProps} />);
wrapper.find(EuiSuperDatePicker).props().onTimeChange({ start: 'now-30d', end: 'now' });
expect(defaultProps.onDateChange).toHaveBeenCalledWith({ start: 'now-30d', end: 'now' });
});
it('shows bottom bar when changes are made', async () => {
@ -71,20 +73,20 @@ describe('RiskScoreConfigurationSection', () => {
});
it('saves changes', () => {
const wrapper = mount(
<RiskScoreConfigurationSection {...defaultProps} includeClosedAlerts={true} />
);
const wrapper = mount(<RiskScoreConfigurationSection {...defaultProps} />);
// Simulate clicking the switch
// Simulate clicking the toggle switch
const closedAlertsToggle = wrapper.find('button[data-test-subj="includeClosedAlertsSwitch"]');
expect(closedAlertsToggle.exists()).toBe(true);
closedAlertsToggle.simulate('click');
wrapper.update();
// Simulate clicking the save button in the bottom bar
const saveChangesButton = wrapper.find('button[data-test-subj="riskScoreSaveButton"]');
expect(saveChangesButton.exists()).toBe(true);
saveChangesButton.simulate('click');
wrapper.update();
const callArgs = mockMutate.mock.calls[0][0];
expect(callArgs).toEqual({
includeClosedAlerts: true,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect } from 'react';
import {
EuiSuperDatePicker,
EuiButton,
@ -18,7 +18,6 @@ import {
EuiSpacer,
useEuiTheme,
} from '@elastic/eui';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { useAppToasts } from '../../common/hooks/use_app_toasts';
import * as i18n from '../translations';
import { useConfigureSORiskEngineMutation } from '../api/hooks/use_configure_risk_engine_saved_object';
@ -26,87 +25,45 @@ import { getEntityAnalyticsRiskScorePageStyles } from './risk_score_page_styles'
export const RiskScoreConfigurationSection = ({
includeClosedAlerts,
setIncludeClosedAlerts,
from,
to,
onDateChange,
}: {
includeClosedAlerts: boolean;
setIncludeClosedAlerts: (value: boolean) => void;
from: string;
to: string;
onDateChange: ({ start, end }: { start: string; end: string }) => void;
}) => {
const { euiTheme } = useEuiTheme();
const styles = getEntityAnalyticsRiskScorePageStyles(euiTheme);
const [start, setFrom] = useState(from);
const [end, setTo] = useState(to);
const [checked, setChecked] = useState(includeClosedAlerts);
const [isLoading, setIsLoading] = useState(false);
const [showBar, setShowBar] = useState(false);
const { addSuccess } = useAppToasts();
const initialIncludeClosedAlerts = useRef(includeClosedAlerts);
const initialStart = useRef(from);
const initialEnd = useRef(to);
const [savedIncludeClosedAlerts, setSavedIncludeClosedAlerts] = useLocalStorage(
'includeClosedAlerts',
includeClosedAlerts ?? false
);
const [savedStart, setSavedStart] = useLocalStorage(
'entityAnalytics:riskScoreConfiguration:fromDate',
from
);
const [savedEnd, setSavedEnd] = useLocalStorage(
'entityAnalytics:riskScoreConfiguration:toDate',
to
);
const [start, setStart] = useState(from);
const [end, setEnd] = useState(to);
useEffect(() => {
if (savedIncludeClosedAlerts !== null && savedIncludeClosedAlerts !== undefined) {
initialIncludeClosedAlerts.current = savedIncludeClosedAlerts;
setIncludeClosedAlerts(savedIncludeClosedAlerts);
}
if (savedStart && savedEnd) {
initialStart.current = savedStart;
initialEnd.current = savedEnd;
setFrom(savedStart);
setTo(savedEnd);
}
}, [savedIncludeClosedAlerts, savedStart, savedEnd, setIncludeClosedAlerts]);
setChecked(includeClosedAlerts);
setStart(from);
setEnd(to);
}, [includeClosedAlerts, from, to]);
const onRefresh = ({ start: newStart, end: newEnd }: { start: string; end: string }) => {
setFrom(newStart);
const adjustedEnd = newStart === newEnd ? 'now' : newEnd;
setTo(adjustedEnd);
onDateChange({ start: newStart, end: adjustedEnd });
checkForChanges(newStart, adjustedEnd, includeClosedAlerts);
const handleDateChange = ({ start: newStart, end: newEnd }: { start: string; end: string }) => {
setShowBar(true);
setStart(newStart);
setEnd(newEnd);
};
const handleToggle = () => {
const newValue = !includeClosedAlerts;
setIncludeClosedAlerts(newValue);
checkForChanges(start, end, newValue);
const onChange = () => {
setChecked((prev) => !prev);
setShowBar(true);
};
const checkForChanges = (newStart: string, newEnd: string, newIncludeClosedAlerts: boolean) => {
if (
newStart !== initialStart.current ||
newEnd !== initialEnd.current ||
newIncludeClosedAlerts !== initialIncludeClosedAlerts.current
) {
setShowBar(true);
} else {
setShowBar(false);
}
};
const { mutate } = useConfigureSORiskEngineMutation();
const handleSave = () => {
setIsLoading(true);
mutate(
{
includeClosedAlerts,
includeClosedAlerts: checked,
range: { start, end },
},
{
@ -116,14 +73,6 @@ export const RiskScoreConfigurationSection = ({
toastLifeTimeMs: 5000,
});
setIsLoading(false);
initialStart.current = start;
initialEnd.current = end;
initialIncludeClosedAlerts.current = includeClosedAlerts;
setSavedIncludeClosedAlerts(includeClosedAlerts);
setSavedStart(start);
setSavedEnd(end);
},
onError: () => {
setIsLoading(false);
@ -138,8 +87,8 @@ export const RiskScoreConfigurationSection = ({
<div>
<EuiSwitch
label={i18n.INCLUDE_CLOSED_ALERTS_LABEL}
checked={includeClosedAlerts}
onChange={handleToggle}
checked={checked}
onChange={onChange}
data-test-subj="includeClosedAlertsSwitch"
/>
</div>
@ -148,7 +97,7 @@ export const RiskScoreConfigurationSection = ({
<EuiSuperDatePicker
start={start}
end={end}
onTimeChange={onRefresh}
onTimeChange={handleDateChange}
width={'auto'}
compressed={false}
showUpdateButton={false}
@ -170,9 +119,9 @@ export const RiskScoreConfigurationSection = ({
iconType="cross"
onClick={() => {
setShowBar(false);
setFrom(initialStart.current);
setTo(initialEnd.current);
setIncludeClosedAlerts(initialIncludeClosedAlerts.current);
setStart(start);
setEnd(end);
setChecked(includeClosedAlerts);
}}
>
{i18n.DISCARD_CHANGES}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState, useCallback } from 'react';
import React, { useState } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
@ -28,6 +28,7 @@ import { useScheduleNowRiskEngineMutation } from '../api/hooks/use_schedule_now_
import { useAppToasts } from '../../common/hooks/use_app_toasts';
import * as i18n from '../translations';
import { getEntityAnalyticsRiskScorePageStyles } from '../components/risk_score_page_styles';
import { useRiskEngineSettings } from '../api/hooks/use_risk_engine_settings';
const TEN_SECONDS = 10000;
@ -35,9 +36,10 @@ export const EntityAnalyticsManagementPage = () => {
const { euiTheme } = useEuiTheme();
const styles = getEntityAnalyticsRiskScorePageStyles(euiTheme);
const privileges = useMissingRiskEnginePrivileges();
const [includeClosedAlerts, setIncludeClosedAlerts] = useState(false);
const [from, setFrom] = useState(localStorage.getItem('dateStart') || 'now-30d');
const [to, setTo] = useState(localStorage.getItem('dateEnd') || 'now');
const { data: riskEngineSettings } = useRiskEngineSettings();
const includeClosedAlerts = riskEngineSettings?.includeClosedAlerts ?? false;
const from = riskEngineSettings?.range?.start ?? 'now-30d';
const to = riskEngineSettings?.range?.end || 'now';
const { data: riskEngineStatus } = useRiskEngineStatus({
refetchInterval: TEN_SECONDS,
structuralSharing: false, // Force the component to rerender after every Risk Engine Status API call
@ -65,20 +67,6 @@ export const EntityAnalyticsManagementPage = () => {
}
};
const handleIncludeClosedAlertsToggle = useCallback(
(value: boolean) => {
setIncludeClosedAlerts(value);
},
[setIncludeClosedAlerts]
);
const handleDateChange = ({ start, end }: { start: string; end: string }) => {
setFrom(start);
setTo(end);
localStorage.setItem('dateStart', start);
localStorage.setItem('dateEnd', end);
};
const { status, runAt } = riskEngineStatus?.risk_engine_task_status || {};
const isRunning = status === 'running' || (!!runAt && new Date(runAt) < new Date());
@ -148,10 +136,8 @@ export const EntityAnalyticsManagementPage = () => {
<EuiFlexItem grow={2}>
<RiskScoreConfigurationSection
includeClosedAlerts={includeClosedAlerts}
setIncludeClosedAlerts={handleIncludeClosedAlertsToggle}
from={from}
to={to}
onDateChange={handleDateChange}
/>
<EuiHorizontalRule />
<RiskScoreUsefulLinksSection />

View file

@ -55,6 +55,9 @@ export const riskEngineSettingsRoute = (router: EntityAnalyticsRoutesDeps['route
return response.ok({
body: {
range: result.range,
includeClosedAlerts:
Array.isArray(result?.excludeAlertStatuses) &&
!result.excludeAlertStatuses.includes('closed'),
},
});
} catch (e) {

View file

@ -81,6 +81,8 @@ export interface RiskEngineConfiguration {
_meta: {
mappingsVersion: number;
};
excludeAlertStatuses?: string[];
excludeAlertTags?: string[];
}
export interface CalculateScoresParams {