mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[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:
parent
c14c1205ed
commit
47100291a8
28 changed files with 15 additions and 480 deletions
|
@ -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}
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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 }),
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue