mirror of
https://github.com/elastic/kibana.git
synced 2025-04-18 23:21:39 -04:00
[Security Solution][Risk Score] Use Risk Engine SavedObject intead of localStorage on the Risk Score web page (#215304)
## Summary The PR updates the implementation to fetch data from the Risk Engine Saved Object instead of storing and reusing it from LocalStorage. This change ensures that settings are applied globally rather than being limited to the browser’s LocalStorage. Since the Saved Object holds the most up-to-date information, it is now used to update the "Date" and the toggle for "including closed alerts for risk scoring" across all web browsers. ### Normal and Incognito Mode : https://github.com/user-attachments/assets/7638c88b-ff9e-4d42-9944-e55b53e33518 ### Default space vs custom space : https://github.com/user-attachments/assets/46bb35c7-3cd9-4b97-9f1c-90ec4ef1241a ## Testing Steps ### Verify Initial Values 1. Open the Entity Risk Score web page where the settings are applied. 2. Ensure that the date picker and toggle for "including closed alerts" reflect the values stored in the Risk Engine Saved Object rather than LocalStorage. 3. Modify and Save changes, - Change the date range in the date picker. - Toggle the "Include Closed Alerts" switch. ### Page Refresh Test - Refresh the page and confirm that the modified values persist, fetched correctly from the Risk Engine Saved Object. ### Cross-Browser Test - Open the same web page in a different browser or incognito mode. - Verify that the settings are consistent and correctly loaded from the Risk Engine Saved Object. ### Expected Outcome The settings should persist after a page refresh or across different browsers. The latest values should always be pulled from the Risk Engine Saved Object. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
933564d713
commit
dbe28b9f94
7 changed files with 50 additions and 101 deletions
|
@ -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(),
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -81,6 +81,8 @@ export interface RiskEngineConfiguration {
|
|||
_meta: {
|
||||
mappingsVersion: number;
|
||||
};
|
||||
excludeAlertStatuses?: string[];
|
||||
excludeAlertTags?: string[];
|
||||
}
|
||||
|
||||
export interface CalculateScoresParams {
|
||||
|
|
Loading…
Add table
Reference in a new issue