mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Detections] Moves last updated info inline with status filter (#108096)
This commit is contained in:
parent
94d16f8882
commit
5f947c2531
10 changed files with 149 additions and 20 deletions
|
@ -22,6 +22,7 @@ import { useUserData } from '../../components/user_info';
|
|||
import { useSourcererScope } from '../../../common/containers/sourcerer';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
import { mockHistory, Router } from '../../../common/mock/router';
|
||||
import { mockTimelines } from '../../../common/mock/mock_timelines_plugin';
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
|
@ -56,6 +57,33 @@ jest.mock('../../components/alerts_info', () => ({
|
|||
useAlertInfo: jest.fn().mockReturnValue([]),
|
||||
}));
|
||||
|
||||
jest.mock('../../../common/lib/kibana', () => {
|
||||
const original = jest.requireActual('../../../common/lib/kibana');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useUiSetting$: jest.fn().mockReturnValue([]),
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
application: {
|
||||
navigateToUrl: jest.fn(),
|
||||
},
|
||||
timelines: { ...mockTimelines },
|
||||
data: {
|
||||
query: {
|
||||
filterManager: jest.fn().mockReturnValue({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
addError: jest.fn(),
|
||||
addSuccess: jest.fn(),
|
||||
addWarning: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const state: State = {
|
||||
...mockGlobalState,
|
||||
};
|
||||
|
|
|
@ -94,6 +94,9 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTimeline(state, TimelineId.detectionsPage) ?? timelineDefaults).graphEventId
|
||||
);
|
||||
const updatedAt = useShallowEqualSelector(
|
||||
(state) => (getTimeline(state, TimelineId.detectionsPage) ?? timelineDefaults).updated
|
||||
);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
|
@ -125,7 +128,10 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false);
|
||||
const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false);
|
||||
const loading = userInfoLoading || listsConfigLoading;
|
||||
const { navigateToUrl } = useKibana().services.application;
|
||||
const {
|
||||
application: { navigateToUrl },
|
||||
timelines: timelinesUi,
|
||||
} = useKibana().services;
|
||||
const [filterGroup, setFilterGroup] = useState<Status>(FILTER_OPEN);
|
||||
|
||||
const updateDateRangeCallback = useCallback<UpdateDateRange>(
|
||||
|
@ -289,7 +295,14 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
</LinkAnchor>
|
||||
</DetectionEngineHeaderPage>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{timelinesUi.getLastUpdated({ updatedAt: updatedAt || 0, showUpdating: loading })}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup wrap>
|
||||
<EuiFlexItem grow={2}>
|
||||
|
|
|
@ -23,6 +23,7 @@ import { useUserData } from '../../../../components/user_info';
|
|||
import { useSourcererScope } from '../../../../../common/containers/sourcerer';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { mockHistory, Router } from '../../../../../common/mock/router';
|
||||
import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin';
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
|
@ -55,6 +56,34 @@ jest.mock('react-router-dom', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana', () => {
|
||||
const original = jest.requireActual('../../../../../common/lib/kibana');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useUiSetting$: jest.fn().mockReturnValue([]),
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
application: {
|
||||
navigateToUrl: jest.fn(),
|
||||
capabilities: { actions: jest.fn().mockReturnValue({}) },
|
||||
},
|
||||
timelines: { ...mockTimelines },
|
||||
data: {
|
||||
query: {
|
||||
filterManager: jest.fn().mockReturnValue({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
addError: jest.fn(),
|
||||
addSuccess: jest.fn(),
|
||||
addWarning: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const state: State = {
|
||||
...mockGlobalState,
|
||||
};
|
||||
|
|
|
@ -59,7 +59,6 @@ import { AlertsHistogramPanel } from '../../../../components/alerts_kpis/alerts_
|
|||
import { AlertsTable } from '../../../../components/alerts_table';
|
||||
import { useUserData } from '../../../../components/user_info';
|
||||
import { OverviewEmpty } from '../../../../../overview/components/overview_empty';
|
||||
import { useAlertInfo } from '../../../../components/alerts_info';
|
||||
import { StepDefineRule } from '../../../../components/rules/step_define_rule';
|
||||
import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule';
|
||||
import {
|
||||
|
@ -178,6 +177,9 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
(state) =>
|
||||
(getTimeline(state, TimelineId.detectionsRulesDetailsPage) ?? timelineDefaults).graphEventId
|
||||
);
|
||||
const updatedAt = useShallowEqualSelector(
|
||||
(state) => (getTimeline(state, TimelineId.detectionsPage) ?? timelineDefaults).updated
|
||||
);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
|
@ -234,7 +236,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
defineRuleData: null,
|
||||
scheduleRuleData: null,
|
||||
};
|
||||
const [lastAlerts] = useAlertInfo({ ruleId });
|
||||
const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false);
|
||||
const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false);
|
||||
const mlCapabilities = useMlCapabilities();
|
||||
|
@ -255,6 +256,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
application: {
|
||||
capabilities: { actions },
|
||||
},
|
||||
timelines: timelinesUi,
|
||||
},
|
||||
} = useKibana();
|
||||
const hasActionsPrivileges = useMemo(() => {
|
||||
|
@ -649,16 +651,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
}}
|
||||
border
|
||||
subtitle={subTitle}
|
||||
subtitle2={[
|
||||
...(lastAlerts != null
|
||||
? [
|
||||
<>
|
||||
{detectionI18n.LAST_ALERT}
|
||||
{': '}
|
||||
{lastAlerts}
|
||||
</>,
|
||||
]
|
||||
: []),
|
||||
subtitle2={
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -667,8 +660,8 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
</EuiFlexItem>
|
||||
{ruleStatusInfo}
|
||||
</EuiFlexGroup>
|
||||
</>,
|
||||
]}
|
||||
</>
|
||||
}
|
||||
title={title}
|
||||
badgeOptions={badgeOptions}
|
||||
>
|
||||
|
@ -759,8 +752,18 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
</Display>
|
||||
{ruleDetailTab === RuleDetailTabs.alerts && (
|
||||
<>
|
||||
<AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{timelinesUi.getLastUpdated({
|
||||
updatedAt: updatedAt || 0,
|
||||
showUpdating: loading,
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<Display show={!globalFullScreen}>
|
||||
<AlertsHistogramPanel
|
||||
filters={alertMergedFilters}
|
||||
|
|
|
@ -180,6 +180,13 @@ export const useTimelineEvents = ({
|
|||
wrappedLoadPage(0);
|
||||
}, [wrappedLoadPage]);
|
||||
|
||||
const setUpdated = useCallback(
|
||||
(updatedAt: number) => {
|
||||
dispatch(timelineActions.setTimelineUpdatedAt({ id, updated: updatedAt }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const [timelineResponse, setTimelineResponse] = useState<TimelineArgs>({
|
||||
id,
|
||||
inspect: {
|
||||
|
@ -230,6 +237,7 @@ export const useTimelineEvents = ({
|
|||
totalCount: response.totalCount,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
setUpdated(newTimelineResponse.updatedAt);
|
||||
if (id === TimelineId.active) {
|
||||
activeTimeline.setExpandedDetail({});
|
||||
activeTimeline.setPageName(pageName);
|
||||
|
@ -303,7 +311,17 @@ export const useTimelineEvents = ({
|
|||
asyncSearch();
|
||||
refetch.current = asyncSearch;
|
||||
},
|
||||
[data.search, id, addWarning, addError, pageName, refetchGrid, skip, wrappedLoadPage]
|
||||
[
|
||||
pageName,
|
||||
skip,
|
||||
id,
|
||||
data.search,
|
||||
setUpdated,
|
||||
addWarning,
|
||||
addError,
|
||||
refetchGrid,
|
||||
wrappedLoadPage,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -67,6 +67,10 @@ export const createTimeline = actionCreator<TimelinePersistInput>('CREATE_TIMELI
|
|||
|
||||
export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT');
|
||||
|
||||
export const setTimelineUpdatedAt = actionCreator<{ id: string; updated: number }>(
|
||||
'SET_TIMELINE_UPDATED_AT'
|
||||
);
|
||||
|
||||
export const removeProvider = actionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
|
|
|
@ -46,6 +46,7 @@ import {
|
|||
updateTitleAndDescription,
|
||||
toggleModalSaveTimeline,
|
||||
updateEqlOptions,
|
||||
setTimelineUpdatedAt,
|
||||
} from './actions';
|
||||
import {
|
||||
addNewTimeline,
|
||||
|
@ -372,4 +373,14 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
...state,
|
||||
timelineById: updateTimelineShowTimeline({ id, show, timelineById: state.timelineById }),
|
||||
}))
|
||||
.case(setTimelineUpdatedAt, (state, { id, updated }) => ({
|
||||
...state,
|
||||
timelineById: {
|
||||
...state.timelineById,
|
||||
[id]: {
|
||||
...state.timelineById[id],
|
||||
updated,
|
||||
},
|
||||
},
|
||||
}))
|
||||
.build();
|
||||
|
|
|
@ -159,6 +159,13 @@ export const useTimelineEvents = ({
|
|||
wrappedLoadPage(0);
|
||||
}, [wrappedLoadPage]);
|
||||
|
||||
const setUpdated = useCallback(
|
||||
(updatedAt: number) => {
|
||||
dispatch(tGridActions.setTimelineUpdatedAt({ id, updated: updatedAt }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const [timelineResponse, setTimelineResponse] = useState<TimelineArgs>({
|
||||
id,
|
||||
inspect: {
|
||||
|
@ -212,6 +219,7 @@ export const useTimelineEvents = ({
|
|||
totalCount: response.totalCount,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
setUpdated(newTimelineResponse.updatedAt);
|
||||
return newTimelineResponse;
|
||||
});
|
||||
searchSubscription$.current.unsubscribe();
|
||||
|
@ -237,7 +245,7 @@ export const useTimelineEvents = ({
|
|||
asyncSearch();
|
||||
refetch.current = asyncSearch;
|
||||
},
|
||||
[data, addWarning, addError, skip]
|
||||
[skip, data, setUpdated, addWarning, addError]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -102,6 +102,10 @@ export const setTGridSelectAll = actionCreator<{ id: string; selectAll: boolean
|
|||
'SET_TGRID_SELECT_ALL'
|
||||
);
|
||||
|
||||
export const setTimelineUpdatedAt = actionCreator<{ id: string; updated: number }>(
|
||||
'SET_TIMELINE_UPDATED_AT'
|
||||
);
|
||||
|
||||
export const addProviderToTimeline = actionCreator<{ id: string; dataProvider: DataProvider }>(
|
||||
'ADD_PROVIDER_TO_TIMELINE'
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
setOpenAddToExistingCase,
|
||||
setOpenAddToNewCase,
|
||||
setSelected,
|
||||
setTimelineUpdatedAt,
|
||||
toggleDetailPanel,
|
||||
updateColumns,
|
||||
updateIsLoading,
|
||||
|
@ -237,4 +238,14 @@ export const tGridReducer = reducerWithInitialState(initialTGridState)
|
|||
},
|
||||
},
|
||||
}))
|
||||
.case(setTimelineUpdatedAt, (state, { id, updated }) => ({
|
||||
...state,
|
||||
timelineById: {
|
||||
...state.timelineById,
|
||||
[id]: {
|
||||
...state.timelineById[id],
|
||||
updated,
|
||||
},
|
||||
},
|
||||
}))
|
||||
.build();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue