[SecuritySolution][Timeline] Remove timeline.isLoading (#198616)

## Summary

Trying to answer the question of: "Do we still need
`timeline.isLoading`?"

`timeline.isSaving` should be the only indicator for "loading" states of
timeline itself. All other pieces of state that are associated with
timeline that could have a loading state, have their own loading
indicators (e.g. data providers, alert list etc).

Therefore, this PR removes all references to `timeline.isLoading` and
parts of the UI that depended on it.

Places that `timeline.isLoading` was used
([context](https://github.com/elastic/kibana/pull/38185)):
- Blocking drag/drop on data providers
- This is not necessary anymore. Drag/drop works while the underlying
query is being executed.
- Showing a loading state for the alerts table & data provider changes
- Both components have their own loading state, so no extra loading
state is necessary

### Checklist

- [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

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jan Monschke 2024-11-18 13:17:57 +01:00 committed by GitHub
parent c14c1205ed
commit 47100291a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 15 additions and 480 deletions

View file

@ -15,7 +15,6 @@ interface Props {
children?: React.ReactNode; children?: React.ReactNode;
droppableId: string; droppableId: string;
height?: string; height?: string;
isDropDisabled?: boolean;
type?: string; type?: string;
render?: ({ isDraggingOver }: { isDraggingOver: boolean }) => React.ReactNode; render?: ({ isDraggingOver }: { isDraggingOver: boolean }) => React.ReactNode;
renderClone?: DraggableChildrenFn; renderClone?: DraggableChildrenFn;
@ -90,15 +89,7 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string
ReactDndDropTarget.displayName = 'ReactDndDropTarget'; ReactDndDropTarget.displayName = 'ReactDndDropTarget';
export const DroppableWrapper = React.memo<Props>( export const DroppableWrapper = React.memo<Props>(
({ ({ children = null, droppableId, height = '100%', type, render = null, renderClone }) => {
children = null,
droppableId,
height = '100%',
isDropDisabled = false,
type,
render = null,
renderClone,
}) => {
const DroppableContent = useCallback<DroppableProps['children']>( const DroppableContent = useCallback<DroppableProps['children']>(
(provided, snapshot) => ( (provided, snapshot) => (
<ReactDndDropTarget <ReactDndDropTarget
@ -116,7 +107,6 @@ export const DroppableWrapper = React.memo<Props>(
return ( return (
<Droppable <Droppable
isDropDisabled={isDropDisabled}
droppableId={droppableId} droppableId={droppableId}
direction={'horizontal'} direction={'horizontal'}
type={type} type={type}

View file

@ -358,7 +358,6 @@ export const mockGlobalState: State = {
historyIds: [], historyIds: [],
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isLoading: false,
kqlMode: 'filter', kqlMode: 'filter',
kqlQuery: { filterQuery: null }, kqlQuery: { filterQuery: null },
loadingEventIds: [], loadingEventIds: [],

View file

@ -1899,7 +1899,6 @@ export const mockTimelineModel: TimelineModel = {
indexNames: [], indexNames: [],
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isLoading: false,
isSaving: false, isSaving: false,
isSelectAllChecked: false, isSelectAllChecked: false,
kqlMode: 'filter', kqlMode: 'filter',
@ -2084,7 +2083,6 @@ export const defaultTimelineProps: CreateTimelineProps = {
indexNames: [], indexNames: [],
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isLoading: false,
isSaving: false, isSaving: false,
isSelectAllChecked: false, isSelectAllChecked: false,
itemsPerPage: 25, itemsPerPage: 25,

View file

@ -30,7 +30,7 @@ import {
mockGetOneTimelineResult, mockGetOneTimelineResult,
mockTimelineData, mockTimelineData,
} from '../../../common/mock'; } from '../../../common/mock';
import type { CreateTimeline, UpdateTimelineLoading } from './types'; import type { CreateTimeline } from './types';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import type { DataProvider } from '../../../../common/types/timeline'; import type { DataProvider } from '../../../../common/types/timeline';
import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline';
@ -127,7 +127,6 @@ describe('alert actions', () => {
const anchor = '2020-03-01T17:59:46.349Z'; const anchor = '2020-03-01T17:59:46.349Z';
const unix = moment(anchor).valueOf(); const unix = moment(anchor).valueOf();
let createTimeline: CreateTimeline; let createTimeline: CreateTimeline;
let updateTimelineIsLoading: UpdateTimelineLoading;
let searchStrategyClient: jest.Mocked<ISearchStart>; let searchStrategyClient: jest.Mocked<ISearchStart>;
let clock: sinon.SinonFakeTimers; let clock: sinon.SinonFakeTimers;
let mockKibanaServices: jest.Mock; let mockKibanaServices: jest.Mock;
@ -270,7 +269,6 @@ describe('alert actions', () => {
mockGetExceptionFilter = jest.fn().mockResolvedValue(undefined); mockGetExceptionFilter = jest.fn().mockResolvedValue(undefined);
createTimeline = jest.fn() as jest.Mocked<CreateTimeline>; createTimeline = jest.fn() as jest.Mocked<CreateTimeline>;
updateTimelineIsLoading = jest.fn() as jest.Mocked<UpdateTimelineLoading>;
mockKibanaServices = KibanaServices.get as jest.Mock; mockKibanaServices = KibanaServices.get as jest.Mock;
fetchMock = jest.fn(); fetchMock = jest.fn();
@ -296,28 +294,10 @@ describe('alert actions', () => {
describe('sendAlertToTimelineAction', () => { describe('sendAlertToTimelineAction', () => {
describe('timeline id is NOT empty string and apollo client exists', () => { describe('timeline id is NOT empty string and apollo client exists', () => {
test('it invokes updateTimelineIsLoading to set to true', async () => {
await sendAlertToTimelineAction({
createTimeline,
ecsData: mockEcsDataWithAlert,
updateTimelineIsLoading,
searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter,
});
expect(mockGetExceptionFilter).not.toHaveBeenCalled();
expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1);
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
});
test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => { test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: mockEcsDataWithAlert, ecsData: mockEcsDataWithAlert,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -407,7 +387,6 @@ describe('alert actions', () => {
indexNames: [], indexNames: [],
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isLoading: false,
isSaving: false, isSaving: false,
isSelectAllChecked: false, isSelectAllChecked: false,
itemsPerPage: 25, itemsPerPage: 25,
@ -477,7 +456,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: mockEcsDataWithAlert, ecsData: mockEcsDataWithAlert,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -496,7 +474,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: mockEcsDataWithAlert, ecsData: mockEcsDataWithAlert,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -505,14 +482,6 @@ describe('alert actions', () => {
delete defaultTimelinePropsWithoutNote.ruleNote; delete defaultTimelinePropsWithoutNote.ruleNote;
delete defaultTimelinePropsWithoutNote.ruleAuthor; delete defaultTimelinePropsWithoutNote.ruleAuthor;
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: false,
});
expect(mockGetExceptionFilter).not.toHaveBeenCalled(); expect(mockGetExceptionFilter).not.toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith({ expect(createTimeline).toHaveBeenCalledWith({
@ -544,7 +513,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMock, ecsData: ecsDataMock,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -552,7 +520,6 @@ describe('alert actions', () => {
const expectedTimelineProps = structuredClone(defaultTimelineProps); const expectedTimelineProps = structuredClone(defaultTimelineProps);
expectedTimelineProps.timeline.excludedRowRendererIds = []; expectedTimelineProps.timeline.excludedRowRendererIds = [];
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
expect(mockGetExceptionFilter).not.toHaveBeenCalled(); expect(mockGetExceptionFilter).not.toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith(expectedTimelineProps); expect(createTimeline).toHaveBeenCalledWith(expectedTimelineProps);
@ -574,7 +541,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMock, ecsData: ecsDataMock,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -582,7 +548,6 @@ describe('alert actions', () => {
const expectedTimelineProps = structuredClone(defaultTimelineProps); const expectedTimelineProps = structuredClone(defaultTimelineProps);
expectedTimelineProps.timeline.excludedRowRendererIds = []; expectedTimelineProps.timeline.excludedRowRendererIds = [];
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
expect(mockGetExceptionFilter).not.toHaveBeenCalled(); expect(mockGetExceptionFilter).not.toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith(expectedTimelineProps); expect(createTimeline).toHaveBeenCalledWith(expectedTimelineProps);
@ -608,12 +573,10 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMock, ecsData: ecsDataMock,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
expect(mockGetExceptionFilter).not.toHaveBeenCalled(); expect(mockGetExceptionFilter).not.toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith({ expect(createTimeline).toHaveBeenCalledWith({
@ -655,12 +618,10 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMock, ecsData: ecsDataMock,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
expect(mockGetExceptionFilter).not.toHaveBeenCalled(); expect(mockGetExceptionFilter).not.toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith(expectedTimelineProps); expect(createTimeline).toHaveBeenCalledWith(expectedTimelineProps);
@ -732,7 +693,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMockWithNoTemplateTimeline, ecsData: ecsDataMockWithNoTemplateTimeline,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -740,7 +700,6 @@ describe('alert actions', () => {
const expectedFrom = '2021-01-10T21:11:45.839Z'; const expectedFrom = '2021-01-10T21:11:45.839Z';
const expectedTo = '2021-01-10T21:12:45.839Z'; const expectedTo = '2021-01-10T21:12:45.839Z';
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
expect(mockGetExceptionFilter).toHaveBeenCalled(); expect(mockGetExceptionFilter).toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith({ expect(createTimeline).toHaveBeenCalledWith({
@ -861,7 +820,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMockWithNoTemplateTimelineAndNoFilters, ecsData: ecsDataMockWithNoTemplateTimelineAndNoFilters,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -886,7 +844,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMockWithTemplateTimeline, ecsData: ecsDataMockWithTemplateTimeline,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -894,7 +851,6 @@ describe('alert actions', () => {
const expectedFrom = '2021-01-10T21:11:45.839Z'; const expectedFrom = '2021-01-10T21:11:45.839Z';
const expectedTo = '2021-01-10T21:12:45.839Z'; const expectedTo = '2021-01-10T21:12:45.839Z';
expect(updateTimelineIsLoading).toHaveBeenCalled();
expect(mockGetExceptionFilter).toHaveBeenCalled(); expect(mockGetExceptionFilter).toHaveBeenCalled();
expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledTimes(1);
expect(createTimeline).toHaveBeenCalledWith({ expect(createTimeline).toHaveBeenCalledWith({
@ -1046,7 +1002,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMockWithNoTemplateTimeline, ecsData: ecsDataMockWithNoTemplateTimeline,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });
@ -1141,7 +1096,6 @@ describe('alert actions', () => {
await sendAlertToTimelineAction({ await sendAlertToTimelineAction({
createTimeline, createTimeline,
ecsData: ecsDataMockWithNoTemplateTimeline, ecsData: ecsDataMockWithNoTemplateTimeline,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter: mockGetExceptionFilter, getExceptionFilter: mockGetExceptionFilter,
}); });

View file

@ -936,7 +936,6 @@ export const sendBulkEventsToTimelineAction = async (
export const sendAlertToTimelineAction = async ({ export const sendAlertToTimelineAction = async ({
createTimeline, createTimeline,
ecsData: ecs, ecsData: ecs,
updateTimelineIsLoading,
searchStrategyClient, searchStrategyClient,
getExceptionFilter, getExceptionFilter,
}: SendAlertToTimelineActionProps) => { }: SendAlertToTimelineActionProps) => {
@ -962,7 +961,6 @@ export const sendAlertToTimelineAction = async ({
// For now we do not want to populate the template timeline if we have alertIds // For now we do not want to populate the template timeline if we have alertIds
if (!isEmpty(timelineId)) { if (!isEmpty(timelineId)) {
try { try {
updateTimelineIsLoading({ id: TimelineId.active, isLoading: true });
const [responseTimeline, eventDataResp] = await Promise.all([ const [responseTimeline, eventDataResp] = await Promise.all([
getTimelineTemplate(timelineId), getTimelineTemplate(timelineId),
lastValueFrom( lastValueFrom(
@ -1092,7 +1090,6 @@ export const sendAlertToTimelineAction = async ({
} catch (error) { } catch (error) {
/* eslint-disable-next-line no-console */ /* eslint-disable-next-line no-console */
console.error(error); console.error(error);
updateTimelineIsLoading({ id: TimelineId.active, isLoading: false });
return createTimeline({ return createTimeline({
from, from,
notes: null, notes: null,

View file

@ -23,7 +23,6 @@ import { useTimelineEventsHandler } from '../../../../timelines/containers';
import { eventsViewerSelector } from '../../../../common/components/events_viewer/selectors'; import { eventsViewerSelector } from '../../../../common/components/events_viewer/selectors';
import type { State } from '../../../../common/store/types'; import type { State } from '../../../../common/store/types';
import { useUpdateTimeline } from '../../../../timelines/components/open_timeline/use_update_timeline'; import { useUpdateTimeline } from '../../../../timelines/components/open_timeline/use_update_timeline';
import { timelineActions } from '../../../../timelines/store';
import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline'; import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline';
import { INVESTIGATE_BULK_IN_TIMELINE } from '../translations'; import { INVESTIGATE_BULK_IN_TIMELINE } from '../translations';
import { TimelineId } from '../../../../../common/types/timeline'; import { TimelineId } from '../../../../../common/types/timeline';
@ -141,18 +140,11 @@ export const useAddBulkToTimelineAction = ({
timelineType: TimelineTypeEnum.default, timelineType: TimelineTypeEnum.default,
}); });
const updateTimelineIsLoading = useCallback(
(payload: Parameters<typeof timelineActions.updateIsLoading>[0]) =>
dispatch(timelineActions.updateIsLoading(payload)),
[dispatch]
);
const updateTimeline = useUpdateTimeline(); const updateTimeline = useUpdateTimeline();
const createTimeline = useCallback( const createTimeline = useCallback(
async ({ timeline, ruleNote, timeline: { filters: eventIdFilters } }: CreateTimelineProps) => { async ({ timeline, ruleNote, timeline: { filters: eventIdFilters } }: CreateTimelineProps) => {
await clearActiveTimeline(); await clearActiveTimeline();
updateTimelineIsLoading({ id: TimelineId.active, isLoading: false });
updateTimeline({ updateTimeline({
duplicate: true, duplicate: true,
from, from,
@ -168,7 +160,7 @@ export const useAddBulkToTimelineAction = ({
ruleNote, ruleNote,
}); });
}, },
[updateTimeline, updateTimelineIsLoading, clearActiveTimeline, from, to] [updateTimeline, clearActiveTimeline, from, to]
); );
const sendBulkEventsToTimelineHandler = useCallback( const sendBulkEventsToTimelineHandler = useCallback(

View file

@ -5,7 +5,6 @@
* 2.0. * 2.0.
*/ */
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { ALERT_RULE_EXCEPTIONS_LIST, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils'; import { ALERT_RULE_EXCEPTIONS_LIST, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
@ -23,7 +22,6 @@ import { createHistoryEntry } from '../../../../common/utils/global_query_string
import { useKibana } from '../../../../common/lib/kibana'; import { useKibana } from '../../../../common/lib/kibana';
import { TimelineId } from '../../../../../common/types/timeline'; import { TimelineId } from '../../../../../common/types/timeline';
import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { TimelineTypeEnum } from '../../../../../common/api/timeline';
import { timelineActions } from '../../../../timelines/store';
import { sendAlertToTimelineAction } from '../actions'; import { sendAlertToTimelineAction } from '../actions';
import { useUpdateTimeline } from '../../../../timelines/components/open_timeline/use_update_timeline'; import { useUpdateTimeline } from '../../../../timelines/components/open_timeline/use_update_timeline';
import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline'; import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline';
@ -98,7 +96,6 @@ export const useInvestigateInTimeline = ({
const { const {
data: { search: searchStrategyClient }, data: { search: searchStrategyClient },
} = useKibana().services; } = useKibana().services;
const dispatch = useDispatch();
const { startTransaction } = useStartTransaction(); const { startTransaction } = useStartTransaction();
const { services } = useKibana(); const { services } = useKibana();
@ -133,12 +130,6 @@ export const useInvestigateInTimeline = ({
[addError, getExceptionFilterFromIds] [addError, getExceptionFilterFromIds]
); );
const updateTimelineIsLoading = useCallback(
(payload: Parameters<typeof timelineActions.updateIsLoading>[0]) =>
dispatch(timelineActions.updateIsLoading(payload)),
[dispatch]
);
const clearActiveTimeline = useCreateTimeline({ const clearActiveTimeline = useCreateTimeline({
timelineId: TimelineId.active, timelineId: TimelineId.active,
timelineType: TimelineTypeEnum.default, timelineType: TimelineTypeEnum.default,
@ -153,7 +144,6 @@ export const useInvestigateInTimeline = ({
!newColumns || isEmpty(newColumns) ? defaultUdtHeaders : newColumns; !newColumns || isEmpty(newColumns) ? defaultUdtHeaders : newColumns;
await clearActiveTimeline(); await clearActiveTimeline();
updateTimelineIsLoading({ id: TimelineId.active, isLoading: false });
updateTimeline({ updateTimeline({
duplicate: true, duplicate: true,
from: fromTimeline, from: fromTimeline,
@ -173,12 +163,11 @@ export const useInvestigateInTimeline = ({
ruleNote, ruleNote,
}); });
}, },
[updateTimeline, updateTimelineIsLoading, clearActiveTimeline] [updateTimeline, clearActiveTimeline]
); );
const investigateInTimelineAlertClick = useCallback(async () => { const investigateInTimelineAlertClick = useCallback(async () => {
createHistoryEntry(); createHistoryEntry();
startTransaction({ name: ALERTS_ACTIONS.INVESTIGATE_IN_TIMELINE }); startTransaction({ name: ALERTS_ACTIONS.INVESTIGATE_IN_TIMELINE });
if (onInvestigateInTimelineAlertClick) { if (onInvestigateInTimelineAlertClick) {
onInvestigateInTimelineAlertClick(); onInvestigateInTimelineAlertClick();
@ -188,7 +177,6 @@ export const useInvestigateInTimeline = ({
createTimeline, createTimeline,
ecsData: ecsRowData, ecsData: ecsRowData,
searchStrategyClient, searchStrategyClient,
updateTimelineIsLoading,
getExceptionFilter, getExceptionFilter,
}); });
} }
@ -198,7 +186,6 @@ export const useInvestigateInTimeline = ({
ecsRowData, ecsRowData,
onInvestigateInTimelineAlertClick, onInvestigateInTimelineAlertClick,
searchStrategyClient, searchStrategyClient,
updateTimelineIsLoading,
getExceptionFilter, getExceptionFilter,
]); ]);

View file

@ -55,13 +55,10 @@ export interface UpdateAlertStatusActionProps {
export interface SendAlertToTimelineActionProps { export interface SendAlertToTimelineActionProps {
createTimeline: CreateTimeline; createTimeline: CreateTimeline;
ecsData: Ecs | Ecs[]; ecsData: Ecs | Ecs[];
updateTimelineIsLoading: UpdateTimelineLoading;
searchStrategyClient: ISearchStart; searchStrategyClient: ISearchStart;
getExceptionFilter: GetExceptionFilter; getExceptionFilter: GetExceptionFilter;
} }
export type UpdateTimelineLoading = ({ id, isLoading }: { id: string; isLoading: boolean }) => void;
export interface CreateTimelineProps { export interface CreateTimelineProps {
from: string; from: string;
timeline: TimelineModel; timeline: TimelineModel;

View file

@ -121,7 +121,6 @@ describe('useRuleFromTimeline', () => {
expect(result.current.loading).toEqual(true); expect(result.current.loading).toEqual(true);
await waitForNextUpdate(); await waitForNextUpdate();
expect(setRuleQuery).toHaveBeenCalled(); expect(setRuleQuery).toHaveBeenCalled();
expect(mockDispatch).toHaveBeenCalledTimes(2);
}); });
}); });
@ -153,16 +152,8 @@ describe('useRuleFromTimeline', () => {
await waitForNextUpdate(); await waitForNextUpdate();
expect(setRuleQuery).toHaveBeenCalled(); expect(setRuleQuery).toHaveBeenCalled();
expect(mockDispatch).toHaveBeenCalledTimes(4); expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch).toHaveBeenNthCalledWith(1, { expect(mockDispatch).toHaveBeenNthCalledWith(1, {
type: 'x-pack/security_solution/local/timeline/UPDATE_LOADING',
payload: {
id: 'timeline-1',
isLoading: true,
},
});
expect(mockDispatch).toHaveBeenNthCalledWith(2, {
type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW',
payload: { payload: {
id: 'timeline', id: 'timeline',
@ -170,13 +161,6 @@ describe('useRuleFromTimeline', () => {
selectedPatterns: selectedTimeline.data.timeline.indexNames, selectedPatterns: selectedTimeline.data.timeline.indexNames,
}, },
}); });
expect(mockDispatch).toHaveBeenNthCalledWith(3, {
type: 'x-pack/security_solution/local/timeline/UPDATE_LOADING',
payload: {
id: 'timeline-1',
isLoading: false,
},
});
}); });
it('when from timeline data view id === selected data view id and browser fields is not empty, set rule data to match from timeline query', async () => { it('when from timeline data view id === selected data view id and browser fields is not empty, set rule data to match from timeline query', async () => {
@ -347,7 +331,7 @@ describe('useRuleFromTimeline', () => {
const { waitForNextUpdate } = renderHook(() => useRuleFromTimeline(setRuleQuery)); const { waitForNextUpdate } = renderHook(() => useRuleFromTimeline(setRuleQuery));
await waitForNextUpdate(); await waitForNextUpdate();
expect(setRuleQuery).toHaveBeenCalled(); expect(setRuleQuery).toHaveBeenCalled();
expect(mockDispatch).toHaveBeenNthCalledWith(4, { expect(mockDispatch).toHaveBeenNthCalledWith(2, {
type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW',
payload: { payload: {
id: 'timeline', id: 'timeline',

View file

@ -6,14 +6,12 @@
*/ */
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { timelineDefaults } from '../timelines/store/defaults'; import { timelineDefaults } from '../timelines/store/defaults';
import { APP_UI_ID } from '../../common/constants'; import { APP_UI_ID } from '../../common/constants';
import type { DataProvider } from '../../common/types'; import type { DataProvider } from '../../common/types';
import { TimelineId } from '../../common/types/timeline'; import { TimelineId } from '../../common/types/timeline';
import { TimelineTypeEnum } from '../../common/api/timeline'; import { TimelineTypeEnum } from '../../common/api/timeline';
import { useStartTransaction } from '../common/lib/apm/use_start_transaction'; import { useStartTransaction } from '../common/lib/apm/use_start_transaction';
import { timelineActions } from '../timelines/store';
import { useCreateTimeline } from '../timelines/hooks/use_create_timeline'; import { useCreateTimeline } from '../timelines/hooks/use_create_timeline';
import type { CreateTimelineProps } from '../detections/components/alerts_table/types'; import type { CreateTimelineProps } from '../detections/components/alerts_table/types';
import { useUpdateTimeline } from '../timelines/components/open_timeline/use_update_timeline'; import { useUpdateTimeline } from '../timelines/components/open_timeline/use_update_timeline';
@ -46,15 +44,8 @@ export const useInvestigateInTimeline = ({
from, from,
to, to,
}: UseInvestigateInTimelineActionProps) => { }: UseInvestigateInTimelineActionProps) => {
const dispatch = useDispatch();
const { startTransaction } = useStartTransaction(); const { startTransaction } = useStartTransaction();
const updateTimelineIsLoading = useCallback(
(payload: Parameters<typeof timelineActions.updateIsLoading>[0]) =>
dispatch(timelineActions.updateIsLoading(payload)),
[dispatch]
);
const clearActiveTimeline = useCreateTimeline({ const clearActiveTimeline = useCreateTimeline({
timelineId: TimelineId.active, timelineId: TimelineId.active,
timelineType: TimelineTypeEnum.default, timelineType: TimelineTypeEnum.default,
@ -65,7 +56,6 @@ export const useInvestigateInTimeline = ({
const createTimeline = useCallback( const createTimeline = useCallback(
async ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => { async ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => {
await clearActiveTimeline(); await clearActiveTimeline();
updateTimelineIsLoading({ id: TimelineId.active, isLoading: false });
updateTimeline({ updateTimeline({
duplicate: true, duplicate: true,
from: fromTimeline, from: fromTimeline,
@ -80,7 +70,7 @@ export const useInvestigateInTimeline = ({
ruleNote, ruleNote,
}); });
}, },
[updateTimeline, updateTimelineIsLoading, clearActiveTimeline] [updateTimeline, clearActiveTimeline]
); );
const investigateInTimelineClick = useCallback(async () => { const investigateInTimelineClick = useCallback(async () => {

View file

@ -11,7 +11,6 @@ import { waitFor } from '@testing-library/react';
import { mockTimelineResults, mockGetOneTimelineResult } from '../../../common/mock'; import { mockTimelineResults, mockGetOneTimelineResult } from '../../../common/mock';
import { timelineDefaults } from '../../store/defaults'; import { timelineDefaults } from '../../store/defaults';
import { updateIsLoading as dispatchUpdateIsLoading } from '../../store/actions';
import type { QueryTimelineById } from './helpers'; import type { QueryTimelineById } from './helpers';
import { import {
defaultTimelineToTimelineModel, defaultTimelineToTimelineModel,
@ -646,13 +645,6 @@ describe('helpers', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('dispatch updateIsLoading to true', () => {
expect(dispatchUpdateIsLoading).toBeCalledWith({
id: TimelineId.active,
isLoading: true,
});
});
test('get timeline by Id', () => { test('get timeline by Id', () => {
expect(resolveTimeline).toHaveBeenCalled(); expect(resolveTimeline).toHaveBeenCalled();
}); });
@ -671,13 +663,6 @@ describe('helpers', () => {
...timeline, ...timeline,
}); });
}); });
test('dispatch updateIsLoading to false', () => {
expect(dispatchUpdateIsLoading).toBeCalledWith({
id: TimelineId.active,
isLoading: false,
});
});
}); });
describe('update a timeline', () => { describe('update a timeline', () => {
@ -706,11 +691,6 @@ describe('helpers', () => {
await queryTimelineById(args); await queryTimelineById(args);
}); });
expect(dispatchUpdateIsLoading).toBeCalledWith({
id: TimelineId.active,
isLoading: true,
});
// expect(resolveTimeline).toHaveBeenCalled(); // expect(resolveTimeline).toHaveBeenCalled();
const { timeline } = formatTimelineResponseToModel( const { timeline } = formatTimelineResponseToModel(
omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)),
@ -741,11 +721,6 @@ describe('helpers', () => {
}, },
}); });
}); });
expect(dispatchUpdateIsLoading).toBeCalledWith({
id: TimelineId.active,
isLoading: false,
});
}); });
test('should update timeline correctly when timeline is untitled', async () => { test('should update timeline correctly when timeline is untitled', async () => {
@ -764,11 +739,6 @@ describe('helpers', () => {
queryTimelineById(newArgs); queryTimelineById(newArgs);
}); });
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
expect(mockUpdateTimeline).toHaveBeenNthCalledWith( expect(mockUpdateTimeline).toHaveBeenNthCalledWith(
1, 1,
expect.objectContaining({ expect.objectContaining({
@ -778,10 +748,6 @@ describe('helpers', () => {
}), }),
}) })
); );
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: false,
});
}); });
test('should update timeline correctly when timeline is already saved and onOpenTimeline is not provided', async () => { test('should update timeline correctly when timeline is already saved and onOpenTimeline is not provided', async () => {
@ -791,11 +757,6 @@ describe('helpers', () => {
queryTimelineById(args); queryTimelineById(args);
}); });
expect(dispatchUpdateIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
await waitFor(() => { await waitFor(() => {
expect(mockUpdateTimeline).toHaveBeenNthCalledWith( expect(mockUpdateTimeline).toHaveBeenNthCalledWith(
1, 1,
@ -860,13 +821,6 @@ describe('helpers', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('dispatch updateIsLoading to true', () => {
expect(dispatchUpdateIsLoading).toBeCalledWith({
id: TimelineId.active,
isLoading: true,
});
});
test('get timeline by Id', () => { test('get timeline by Id', () => {
expect(resolveTimeline).toHaveBeenCalled(); expect(resolveTimeline).toHaveBeenCalled();
}); });
@ -885,13 +839,6 @@ describe('helpers', () => {
}, },
}); });
}); });
test('dispatch updateIsLoading to false', () => {
expect(dispatchUpdateIsLoading).toBeCalledWith({
id: TimelineId.active,
isLoading: false,
});
});
}); });
}); });

View file

@ -9,8 +9,6 @@ import { set } from '@kbn/safer-lodash-set/fp';
import { getOr } from 'lodash/fp'; import { getOr } from 'lodash/fp';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import deepMerge from 'deepmerge'; import deepMerge from 'deepmerge';
import { useDispatch } from 'react-redux';
import { useCallback } from 'react';
import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context'; import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context';
import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; import type { ColumnHeaderOptions } from '../../../../common/types/timeline';
import type { import type {
@ -49,7 +47,6 @@ import {
DEFAULT_TO_MOMENT, DEFAULT_TO_MOMENT,
} from '../../../common/utils/default_date_settings'; } from '../../../common/utils/default_date_settings';
import { resolveTimeline } from '../../containers/api'; import { resolveTimeline } from '../../containers/api';
import { timelineActions } from '../../store';
export const OPEN_TIMELINE_CLASS_NAME = 'open-timeline'; export const OPEN_TIMELINE_CLASS_NAME = 'open-timeline';
@ -314,13 +311,6 @@ export interface QueryTimelineById {
export const useQueryTimelineById = () => { export const useQueryTimelineById = () => {
const { resetDiscoverAppState } = useDiscoverInTimelineContext(); const { resetDiscoverAppState } = useDiscoverInTimelineContext();
const updateTimeline = useUpdateTimeline(); const updateTimeline = useUpdateTimeline();
const dispatch = useDispatch();
const updateIsLoading = useCallback(
(status: { id: string; isLoading: boolean }) =>
dispatch(timelineActions.updateIsLoading(status)),
[dispatch]
);
return ({ return ({
activeTimelineTab = TimelineTabs.query, activeTimelineTab = TimelineTabs.query,
@ -333,7 +323,6 @@ export const useQueryTimelineById = () => {
openTimeline = true, openTimeline = true,
savedSearchId, savedSearchId,
}: QueryTimelineById) => { }: QueryTimelineById) => {
updateIsLoading({ id: TimelineId.active, isLoading: true });
if (timelineId == null) { if (timelineId == null) {
updateTimeline({ updateTimeline({
id: TimelineId.active, id: TimelineId.active,
@ -356,7 +345,6 @@ export const useQueryTimelineById = () => {
}, },
}); });
resetDiscoverAppState(); resetDiscoverAppState();
updateIsLoading({ id: TimelineId.active, isLoading: false });
} else { } else {
return Promise.resolve(resolveTimeline(timelineId)) return Promise.resolve(resolveTimeline(timelineId))
.then((result) => { .then((result) => {
@ -409,9 +397,6 @@ export const useQueryTimelineById = () => {
if (onError != null) { if (onError != null) {
onError(error, timelineId); onError(error, timelineId);
} }
})
.finally(() => {
updateIsLoading({ id: TimelineId.active, isLoading: false });
}); });
} }
}; };

View file

@ -109,9 +109,6 @@ export const DataProviders = React.memo<Props>(({ timelineId }) => {
const { browserFields } = useSourcererDataView(SourcererScopeName.timeline); const { browserFields } = useSourcererDataView(SourcererScopeName.timeline);
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const isLoading = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).isLoading
);
const dataProviders = useDeepEqualSelector( const dataProviders = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).dataProviders (state) => (getTimeline(state, timelineId) ?? timelineDefaults).dataProviders
); );
@ -167,7 +164,7 @@ export const DataProviders = React.memo<Props>(({ timelineId }) => {
dataProviders={dataProviders} dataProviders={dataProviders}
/> />
) : ( ) : (
<DroppableWrapper isDropDisabled={isLoading} droppableId={droppableId}> <DroppableWrapper droppableId={droppableId}>
<Empty browserFields={browserFields} timelineId={timelineId} /> <Empty browserFields={browserFields} timelineId={timelineId} />
</DroppableWrapper> </DroppableWrapper>
)} )}

View file

@ -44,7 +44,6 @@ interface OwnProps {
kqlQuery: string; // eslint-disable-line react/no-unused-prop-types kqlQuery: string; // eslint-disable-line react/no-unused-prop-types
isEnabled: boolean; isEnabled: boolean;
isExcluded: boolean; isExcluded: boolean;
isLoading: boolean;
isOpen: boolean; isOpen: boolean;
onDataProviderEdited?: OnDataProviderEdited; onDataProviderEdited?: OnDataProviderEdited;
operator: QueryOperator; operator: QueryOperator;
@ -77,7 +76,6 @@ interface GetProviderActionsProps {
field: string; field: string;
isEnabled: boolean; isEnabled: boolean;
isExcluded: boolean; isExcluded: boolean;
isLoading: boolean;
onDataProviderEdited?: OnDataProviderEdited; onDataProviderEdited?: OnDataProviderEdited;
onFilterForFieldPresent: () => void; onFilterForFieldPresent: () => void;
operator: QueryOperator; operator: QueryOperator;
@ -98,7 +96,6 @@ export const getProviderActions = ({
field, field,
isEnabled, isEnabled,
isExcluded, isExcluded,
isLoading,
operator, operator,
onDataProviderEdited, onDataProviderEdited,
onFilterForFieldPresent, onFilterForFieldPresent,
@ -116,28 +113,24 @@ export const getProviderActions = ({
items: [ items: [
{ {
className: EDIT_CLASS_NAME, className: EDIT_CLASS_NAME,
disabled: isLoading,
icon: 'pencil', icon: 'pencil',
name: i18n.EDIT_MENU_ITEM, name: i18n.EDIT_MENU_ITEM,
panel: 1, panel: 1,
}, },
{ {
className: EXCLUDE_CLASS_NAME, className: EXCLUDE_CLASS_NAME,
disabled: isLoading,
icon: `${isExcluded ? 'plusInCircle' : 'minusInCircle'}`, icon: `${isExcluded ? 'plusInCircle' : 'minusInCircle'}`,
name: isExcluded ? i18n.INCLUDE_DATA_PROVIDER : i18n.EXCLUDE_DATA_PROVIDER, name: isExcluded ? i18n.INCLUDE_DATA_PROVIDER : i18n.EXCLUDE_DATA_PROVIDER,
onClick: toggleExcluded, onClick: toggleExcluded,
}, },
{ {
className: ENABLE_CLASS_NAME, className: ENABLE_CLASS_NAME,
disabled: isLoading,
icon: `${isEnabled ? 'eyeClosed' : 'eye'}`, icon: `${isEnabled ? 'eyeClosed' : 'eye'}`,
name: isEnabled ? i18n.TEMPORARILY_DISABLE_DATA_PROVIDER : i18n.RE_ENABLE_DATA_PROVIDER, name: isEnabled ? i18n.TEMPORARILY_DISABLE_DATA_PROVIDER : i18n.RE_ENABLE_DATA_PROVIDER,
onClick: toggleEnabled, onClick: toggleEnabled,
}, },
{ {
className: FILTER_FOR_FIELD_PRESENT_CLASS_NAME, className: FILTER_FOR_FIELD_PRESENT_CLASS_NAME,
disabled: isLoading,
icon: 'logstashFilter', icon: 'logstashFilter',
name: i18n.FILTER_FOR_FIELD_PRESENT, name: i18n.FILTER_FOR_FIELD_PRESENT,
onClick: onFilterForFieldPresent, onClick: onFilterForFieldPresent,
@ -145,7 +138,7 @@ export const getProviderActions = ({
timelineType === TimelineTypeEnum.template timelineType === TimelineTypeEnum.template
? { ? {
className: CONVERT_TO_FIELD_CLASS_NAME, className: CONVERT_TO_FIELD_CLASS_NAME,
disabled: isLoading || operator === IS_ONE_OF_OPERATOR, disabled: operator === IS_ONE_OF_OPERATOR,
icon: 'visText', icon: 'visText',
name: name:
type === DataProviderTypeEnum.template type === DataProviderTypeEnum.template
@ -156,7 +149,6 @@ export const getProviderActions = ({
: { name: null }, : { name: null },
{ {
className: DELETE_CLASS_NAME, className: DELETE_CLASS_NAME,
disabled: isLoading,
icon: 'trash', icon: 'trash',
name: i18n.DELETE_DATA_PROVIDER, name: i18n.DELETE_DATA_PROVIDER,
onClick: deleteItem, onClick: deleteItem,
@ -196,7 +188,6 @@ export class ProviderItemActions extends React.PureComponent<OwnProps> {
field, field,
isEnabled, isEnabled,
isExcluded, isExcluded,
isLoading,
isOpen, isOpen,
operator, operator,
providerId, providerId,
@ -216,7 +207,6 @@ export class ProviderItemActions extends React.PureComponent<OwnProps> {
field, field,
isEnabled, isEnabled,
isExcluded, isExcluded,
isLoading,
onDataProviderEdited: this.onDataProviderEdited, onDataProviderEdited: this.onDataProviderEdited,
onFilterForFieldPresent: this.onFilterForFieldPresent, onFilterForFieldPresent: this.onFilterForFieldPresent,
operator, operator,

View file

@ -5,7 +5,6 @@
* 2.0. * 2.0.
*/ */
import { noop } from 'lodash/fp';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
@ -15,10 +14,7 @@ import {
TimelineTypeEnum, TimelineTypeEnum,
} from '../../../../../common/api/timeline'; } from '../../../../../common/api/timeline';
import type { BrowserFields } from '../../../../common/containers/source'; import type { BrowserFields } from '../../../../common/containers/source';
import { import { useShallowEqualSelector } from '../../../../common/hooks/use_selector';
useDeepEqualSelector,
useShallowEqualSelector,
} from '../../../../common/hooks/use_selector';
import { timelineSelectors } from '../../../store'; import { timelineSelectors } from '../../../store';
import type { PrimitiveOrArrayOfPrimitives } from '../../../../common/lib/kuery'; import type { PrimitiveOrArrayOfPrimitives } from '../../../../common/lib/kuery';
@ -27,7 +23,6 @@ import { ProviderBadge } from './provider_badge';
import { ProviderItemActions } from './provider_item_actions'; import { ProviderItemActions } from './provider_item_actions';
import type { DataProvidersAnd, QueryOperator } from './data_provider'; import type { DataProvidersAnd, QueryOperator } from './data_provider';
import { dragAndDropActions } from '../../../../common/store/drag_and_drop'; import { dragAndDropActions } from '../../../../common/store/drag_and_drop';
import { timelineDefaults } from '../../../store/defaults';
interface ProviderItemBadgeProps { interface ProviderItemBadgeProps {
andProviderId?: string; andProviderId?: string;
@ -86,10 +81,6 @@ export const ProviderItemBadge = React.memo<ProviderItemBadgeProps>(
return getTimeline(state, timelineId)?.timelineType ?? TimelineTypeEnum.default; return getTimeline(state, timelineId)?.timelineType ?? TimelineTypeEnum.default;
}); });
const { isLoading } = useDeepEqualSelector(
(state) => getTimeline(state, timelineId ?? '') ?? timelineDefaults
);
const togglePopover = useCallback(() => { const togglePopover = useCallback(() => {
setIsPopoverOpen(!isPopoverOpen); setIsPopoverOpen(!isPopoverOpen);
}, [isPopoverOpen, setIsPopoverOpen]); }, [isPopoverOpen, setIsPopoverOpen]);
@ -142,7 +133,7 @@ export const ProviderItemBadge = React.memo<ProviderItemBadgeProps>(
const button = useMemo( const button = useMemo(
() => ( () => (
<ProviderBadge <ProviderBadge
deleteProvider={!isLoading ? deleteProvider : noop} deleteProvider={deleteProvider}
field={field} field={field}
kqlQuery={kqlQuery} kqlQuery={kqlQuery}
isEnabled={isEnabled} isEnabled={isEnabled}
@ -163,7 +154,6 @@ export const ProviderItemBadge = React.memo<ProviderItemBadgeProps>(
field, field,
isEnabled, isEnabled,
isExcluded, isExcluded,
isLoading,
kqlQuery, kqlQuery,
onToggleTypeProvider, onToggleTypeProvider,
operator, operator,
@ -186,7 +176,6 @@ export const ProviderItemBadge = React.memo<ProviderItemBadgeProps>(
kqlQuery={kqlQuery} kqlQuery={kqlQuery}
isEnabled={isEnabled} isEnabled={isEnabled}
isExcluded={isExcluded} isExcluded={isExcluded}
isLoading={isLoading}
isOpen={isPopoverOpen} isOpen={isPopoverOpen}
onDataProviderEdited={onDataProviderEdited} onDataProviderEdited={onDataProviderEdited}
operator={operator} operator={operator}

View file

@ -32,7 +32,7 @@ describe('Providers', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: false }); (useDeepEqualSelector as jest.Mock).mockReturnValue({});
}); });
describe('rendering', () => { describe('rendering', () => {
@ -88,28 +88,6 @@ describe('Providers', () => {
expect(mockOnDataProviderRemoved.mock.calls[0][0].providerId).toEqual('id-Provider 1'); expect(mockOnDataProviderRemoved.mock.calls[0][0].providerId).toEqual('id-Provider 1');
}); });
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when the close button is clicked', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"] [data-euiicon-type]')
.first()
.simulate('click');
expect(mockOnDataProviderRemoved).not.toBeCalled();
});
test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => { test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
const wrapper = mount( const wrapper = mount(
<TestProviders> <TestProviders>
@ -132,31 +110,6 @@ describe('Providers', () => {
.simulate('click'); .simulate('click');
expect(mockOnDataProviderRemoved.mock.calls[0][0].providerId).toEqual('id-Provider 1'); expect(mockOnDataProviderRemoved.mock.calls[0][0].providerId).toEqual('id-Provider 1');
}); });
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper.find('button[data-test-subj="providerBadge"]').first().simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${DELETE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnDataProviderRemoved).not.toBeCalled();
});
}); });
describe('#onToggleDataProviderEnabled', () => { describe('#onToggleDataProviderEnabled', () => {
@ -191,35 +144,6 @@ describe('Providers', () => {
providerId: 'id-Provider 1', providerId: 'id-Provider 1',
}); });
}); });
test('while loading data, it does NOT invoke the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const mockOnToggleDataProviderEnabled = jest.spyOn(
timelineActions,
'updateDataProviderEnabled'
);
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper.find('button[data-test-subj="providerBadge"]').first().simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${ENABLE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderEnabled).not.toBeCalled();
});
}); });
describe('#onToggleDataProviderExcluded', () => { describe('#onToggleDataProviderExcluded', () => {
@ -257,37 +181,6 @@ describe('Providers', () => {
providerId: 'id-Provider 1', providerId: 'id-Provider 1',
}); });
}); });
test('while loading data, it does NOT invoke the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const mockOnToggleDataProviderExcluded = jest.spyOn(
timelineActions,
'updateDataProviderExcluded'
);
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper.find('button[data-test-subj="providerBadge"]').first().simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${EXCLUDE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderExcluded).not.toBeCalled();
});
}); });
describe('#ProviderWithAndProvider', () => { describe('#ProviderWithAndProvider', () => {
@ -349,35 +242,6 @@ describe('Providers', () => {
}); });
}); });
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when you click on the close button is clicked', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.at(4)
.find('[data-euiicon-type]')
.first()
.simulate('click');
wrapper.update();
expect(mockOnDataProviderRemoved).not.toBeCalled();
});
test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => { test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
const dataProviders = mockDataProviders.slice(0, 1); const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3); dataProviders[0].and = mockDataProviders.slice(1, 3);
@ -420,44 +284,6 @@ describe('Providers', () => {
}); });
}); });
test('while loading data, it does NOT invoke the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnToggleDataProviderEnabled = jest.spyOn(
timelineActions,
'updateDataProviderEnabled'
);
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={dataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.at(4)
.find('button')
.first()
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${ENABLE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderEnabled).not.toBeCalled();
});
test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => { test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
const dataProviders = mockDataProviders.slice(0, 1); const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3); dataProviders[0].and = mockDataProviders.slice(1, 3);
@ -499,43 +325,5 @@ describe('Providers', () => {
providerId: 'id-Provider 1', providerId: 'id-Provider 1',
}); });
}); });
test('while loading data, it does NOT invoke the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
(useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: true });
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnToggleDataProviderExcluded = jest.spyOn(
timelineActions,
'updateDataProviderExcluded'
);
const wrapper = mount(
<TestProviders>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={dataProviders}
timelineId={TimelineId.test}
/>
</DroppableWrapper>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.at(4)
.find('button')
.first()
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${EXCLUDE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderExcluded).not.toBeCalled();
});
}); });
}); });

View file

@ -100,7 +100,6 @@ const StatefulTimelineComponent: React.FC<Props> = ({
'sessionViewConfig', 'sessionViewConfig',
'initialized', 'initialized',
'show', 'show',
'isLoading',
'activeTab', 'activeTab',
], ],
getTimeline(state, timelineId) ?? timelineDefaults getTimeline(state, timelineId) ?? timelineDefaults

View file

@ -7,9 +7,9 @@
import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexGroup } from '@elastic/eui';
import { isEmpty } from 'lodash/fp'; import { isEmpty } from 'lodash/fp';
import React, { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { ConnectedProps } from 'react-redux'; import type { ConnectedProps } from 'react-redux';
import { connect, useDispatch } from 'react-redux'; import { connect } from 'react-redux';
import deepEqual from 'fast-deep-equal'; import deepEqual from 'fast-deep-equal';
import { InPortal } from 'react-reverse-portal'; import { InPortal } from 'react-reverse-portal';
import type { EuiDataGridControlColumn } from '@elastic/eui'; import type { EuiDataGridControlColumn } from '@elastic/eui';
@ -25,7 +25,7 @@ import {
} from '../../../../../flyout/document_details/shared/constants/panel_keys'; } from '../../../../../flyout/document_details/shared/constants/panel_keys';
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { timelineActions, timelineSelectors } from '../../../../store'; import { timelineSelectors } from '../../../../store';
import { useTimelineEvents } from '../../../../containers'; import { useTimelineEvents } from '../../../../containers';
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
import type { inputsModel, State } from '../../../../../common/store'; import type { inputsModel, State } from '../../../../../common/store';
@ -66,7 +66,6 @@ export const EqlTabContentComponent: React.FC<Props> = ({
eventIdToNoteIds, eventIdToNoteIds,
}) => { }) => {
const { telemetry } = useKibana().services; const { telemetry } = useKibana().services;
const dispatch = useDispatch();
const { query: eqlQuery = '', ...restEqlOption } = eqlOptions; const { query: eqlQuery = '', ...restEqlOption } = eqlOptions;
const { portalNode: eqlEventsCountPortalNode } = useEqlEventsCountPortal(); const { portalNode: eqlEventsCountPortalNode } = useEqlEventsCountPortal();
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen(); const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
@ -206,15 +205,6 @@ export const EqlTabContentComponent: React.FC<Props> = ({
[dataLoadingState] [dataLoadingState]
); );
useEffect(() => {
dispatch(
timelineActions.updateIsLoading({
id: timelineId,
isLoading: isQueryLoading || loadingSourcerer,
})
);
}, [loadingSourcerer, timelineId, isQueryLoading, dispatch]);
const unifiedHeader = useMemo( const unifiedHeader = useMemo(
() => ( () => (
<EuiFlexGroup gutterSize="s" direction="column"> <EuiFlexGroup gutterSize="s" direction="column">

View file

@ -280,15 +280,6 @@ export const QueryTabContentComponent: React.FC<Props> = ({
[dataLoadingState] [dataLoadingState]
); );
useEffect(() => {
dispatch(
timelineActions.updateIsLoading({
id: timelineId,
isLoading: isQueryLoading || loadingSourcerer,
})
);
}, [loadingSourcerer, timelineId, isQueryLoading, dispatch]);
// NOTE: The timeline is blank after browser FORWARD navigation (after using back button to navigate to // NOTE: The timeline is blank after browser FORWARD navigation (after using back button to navigate to
// the previous page from the timeline), yet we still see total count. This is because the timeline // the previous page from the timeline), yet we still see total count. This is because the timeline
// is not getting refreshed when using browser navigation. // is not getting refreshed when using browser navigation.

View file

@ -191,11 +191,6 @@ export const updateEqlOptions = actionCreator<{
value: string | undefined; value: string | undefined;
}>('UPDATE_EQL_OPTIONS_TIMELINE'); }>('UPDATE_EQL_OPTIONS_TIMELINE');
export const updateIsLoading = actionCreator<{
id: string;
isLoading: boolean;
}>('UPDATE_LOADING');
export const setEventsLoading = actionCreator<{ export const setEventsLoading = actionCreator<{
id: string; id: string;
eventIds: string[]; eventIds: string[];

View file

@ -64,7 +64,6 @@ export const timelineDefaults: SubsetTimelineModel &
indexNames: [], indexNames: [],
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isLoading: false,
isSaving: false, isSaving: false,
itemsPerPage: 25, itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100], itemsPerPageOptions: [10, 25, 50, 100],

View file

@ -104,7 +104,6 @@ const basicTimeline: TimelineModel = {
indexNames: [], indexNames: [],
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isLoading: false,
isSaving: false, isSaving: false,
isSelectAllChecked: false, isSelectAllChecked: false,
itemsPerPage: 25, itemsPerPage: 25,

View file

@ -131,7 +131,6 @@ export const addTimelineToStore = ({
...timelineById, ...timelineById,
[id]: { [id]: {
...timeline, ...timeline,
isLoading: timelineById[id].isLoading,
initialized: timeline.initialized ?? timelineById[id].initialized, initialized: timeline.initialized ?? timelineById[id].initialized,
resolveTimelineConfig, resolveTimelineConfig,
dateRange: dateRange:
@ -180,7 +179,6 @@ export const addNewTimeline = ({
savedObjectId: null, savedObjectId: null,
version: null, version: null,
isSaving: false, isSaving: false,
isLoading: false,
timelineType, timelineType,
...templateTimelineInfo, ...templateTimelineInfo,
}, },

View file

@ -295,7 +295,6 @@ describe('Timeline save middleware', () => {
isFavorite: false, isFavorite: false,
isLive: false, isLive: false,
isSelectAllChecked: false, isSelectAllChecked: false,
isLoading: false,
isSaving: false, isSaving: false,
itemsPerPage: 25, itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100], itemsPerPageOptions: [10, 25, 50, 100],

View file

@ -129,7 +129,6 @@ export interface TimelineModel {
selectedEventIds: Record<string, TimelineNonEcsData[]>; selectedEventIds: Record<string, TimelineNonEcsData[]>;
/** If selectAll checkbox in header is checked **/ /** If selectAll checkbox in header is checked **/
isSelectAllChecked: boolean; isSelectAllChecked: boolean;
isLoading: boolean;
selectAll: boolean; selectAll: boolean;
/* discover saved search Id */ /* discover saved search Id */
savedSearchId: string | null; savedSearchId: string | null;
@ -190,7 +189,6 @@ export type SubsetTimelineModel = Readonly<
| 'show' | 'show'
| 'sort' | 'sort'
| 'isSaving' | 'isSaving'
| 'isLoading'
| 'savedObjectId' | 'savedObjectId'
| 'version' | 'version'
| 'status' | 'status'

View file

@ -45,7 +45,6 @@ import {
removeColumn, removeColumn,
upsertColumn, upsertColumn,
updateColumns, updateColumns,
updateIsLoading,
updateSort, updateSort,
clearSelected, clearSelected,
setSelected, setSelected,
@ -409,16 +408,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
timelineById: state.timelineById, timelineById: state.timelineById,
}), }),
})) }))
.case(updateIsLoading, (state, { id, isLoading }) => ({
...state,
timelineById: {
...state.timelineById,
[id]: {
...state.timelineById[id],
isLoading,
},
},
}))
.case(updateSort, (state, { id, sort }) => ({ .case(updateSort, (state, { id, sort }) => ({
...state, ...state,
timelineById: updateTableSort({ id, sort, timelineById: state.timelineById }), timelineById: updateTableSort({ id, sort, timelineById: state.timelineById }),

View file

@ -117,8 +117,6 @@ export const ALERTS_TABLE_COUNT = `[data-test-subj="toolbar-alerts-count"]`;
export const STAR_ICON = '[data-test-subj="timeline-favorite-empty-star"]'; export const STAR_ICON = '[data-test-subj="timeline-favorite-empty-star"]';
export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]';
export const TIMELINE_COLLAPSED_ITEMS_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]'; export const TIMELINE_COLLAPSED_ITEMS_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]';
export const TIMELINE_CREATE_TEMPLATE_FROM_TIMELINE_BTN = export const TIMELINE_CREATE_TEMPLATE_FROM_TIMELINE_BTN =

View file

@ -57,7 +57,6 @@ import {
TOOLTIP, TOOLTIP,
} from '../screens/alerts'; } from '../screens/alerts';
import { LOADING_INDICATOR, REFRESH_BUTTON } from '../screens/security_header'; import { LOADING_INDICATOR, REFRESH_BUTTON } from '../screens/security_header';
import { TIMELINE_COLUMN_SPINNER } from '../screens/timeline';
import { import {
UPDATE_ENRICHMENT_RANGE_BUTTON, UPDATE_ENRICHMENT_RANGE_BUTTON,
ENRICHMENT_QUERY_END_INPUT, ENRICHMENT_QUERY_END_INPUT,
@ -216,7 +215,6 @@ export const goToClosedAlertsOnRuleDetailsPage = () => {
cy.get(CLOSED_ALERTS_FILTER_BTN).click(); cy.get(CLOSED_ALERTS_FILTER_BTN).click();
cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating'); cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating');
cy.get(REFRESH_BUTTON).should('have.attr', 'aria-label', 'Refresh query'); cy.get(REFRESH_BUTTON).should('have.attr', 'aria-label', 'Refresh query');
cy.get(TIMELINE_COLUMN_SPINNER).should('not.exist');
}; };
export const goToClosedAlerts = () => { export const goToClosedAlerts = () => {
@ -233,7 +231,6 @@ export const goToClosedAlerts = () => {
selectPageFilterValue(0, 'closed'); selectPageFilterValue(0, 'closed');
cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating'); cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating');
cy.get(REFRESH_BUTTON).should('have.attr', 'aria-label', 'Refresh query'); cy.get(REFRESH_BUTTON).should('have.attr', 'aria-label', 'Refresh query');
cy.get(TIMELINE_COLUMN_SPINNER).should('not.exist');
}; };
export const goToOpenedAlertsOnRuleDetailsPage = () => { export const goToOpenedAlertsOnRuleDetailsPage = () => {
@ -297,7 +294,6 @@ export const goToAcknowledgedAlerts = () => {
selectPageFilterValue(0, 'acknowledged'); selectPageFilterValue(0, 'acknowledged');
cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating'); cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating');
cy.get(REFRESH_BUTTON).should('have.attr', 'aria-label', 'Refresh query'); cy.get(REFRESH_BUTTON).should('have.attr', 'aria-label', 'Refresh query');
cy.get(TIMELINE_COLUMN_SPINNER).should('not.exist');
}; };
export const markAlertsAcknowledged = () => { export const markAlertsAcknowledged = () => {