mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Security Solution] [Timeline] Bugfix for timeline row actions disappear sometimes (#70958)
This commit is contained in:
parent
465ed21194
commit
06bc389189
11 changed files with 37 additions and 49 deletions
|
@ -48,6 +48,7 @@ import {
|
||||||
displaySuccessToast,
|
displaySuccessToast,
|
||||||
displayErrorToast,
|
displayErrorToast,
|
||||||
} from '../../../common/components/toasters';
|
} from '../../../common/components/toasters';
|
||||||
|
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
timelineId: TimelineIdLiteral;
|
timelineId: TimelineIdLiteral;
|
||||||
|
@ -331,13 +332,14 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeTimeline({
|
initializeTimeline({
|
||||||
id: timelineId,
|
|
||||||
documentType: i18n.ALERTS_DOCUMENT_TYPE,
|
|
||||||
defaultModel: alertsDefaultModel,
|
defaultModel: alertsDefaultModel,
|
||||||
|
documentType: i18n.ALERTS_DOCUMENT_TYPE,
|
||||||
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
|
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
|
||||||
|
id: timelineId,
|
||||||
loadingText: i18n.LOADING_ALERTS,
|
loadingText: i18n.LOADING_ALERTS,
|
||||||
title: i18n.ALERTS_TABLE_TITLE,
|
|
||||||
selectAll: canUserCRUD ? selectAll : false,
|
selectAll: canUserCRUD ? selectAll : false,
|
||||||
|
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
|
||||||
|
title: i18n.ALERTS_TABLE_TITLE,
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -69,7 +69,7 @@ const AlertsTableComponent: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
|
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
|
||||||
const { initializeTimeline, setTimelineRowActions } = useManageTimeline();
|
const { initializeTimeline } = useManageTimeline();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeTimeline({
|
initializeTimeline({
|
||||||
|
@ -77,13 +77,10 @@ const AlertsTableComponent: React.FC<Props> = ({
|
||||||
documentType: i18n.ALERTS_DOCUMENT_TYPE,
|
documentType: i18n.ALERTS_DOCUMENT_TYPE,
|
||||||
defaultModel: alertsDefaultModel,
|
defaultModel: alertsDefaultModel,
|
||||||
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
|
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
|
||||||
|
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
|
||||||
title: i18n.ALERTS_TABLE_TITLE,
|
title: i18n.ALERTS_TABLE_TITLE,
|
||||||
unit: i18n.UNIT,
|
unit: i18n.UNIT,
|
||||||
});
|
});
|
||||||
setTimelineRowActions({
|
|
||||||
id: timelineId,
|
|
||||||
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { useAddToTimeline } from '../../hooks/use_add_to_timeline';
|
||||||
import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
|
import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
|
||||||
import {
|
import {
|
||||||
ManageGlobalTimeline,
|
ManageGlobalTimeline,
|
||||||
timelineDefaults,
|
getTimelineDefaults,
|
||||||
} from '../../../timelines/components/manage_timeline';
|
} from '../../../timelines/components/manage_timeline';
|
||||||
import { TimelineId } from '../../../../common/types/timeline';
|
import { TimelineId } from '../../../../common/types/timeline';
|
||||||
|
|
||||||
|
@ -152,10 +152,7 @@ describe('DraggableWrapperHoverContent', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
onFilterAdded = jest.fn();
|
onFilterAdded = jest.fn();
|
||||||
const manageTimelineForTesting = {
|
const manageTimelineForTesting = {
|
||||||
[timelineId]: {
|
[timelineId]: getTimelineDefaults(timelineId),
|
||||||
...timelineDefaults,
|
|
||||||
id: timelineId,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
wrapper = mount(
|
wrapper = mount(
|
||||||
|
@ -249,8 +246,7 @@ describe('DraggableWrapperHoverContent', () => {
|
||||||
|
|
||||||
const manageTimelineForTesting = {
|
const manageTimelineForTesting = {
|
||||||
[timelineId]: {
|
[timelineId]: {
|
||||||
...timelineDefaults,
|
...getTimelineDefaults(timelineId),
|
||||||
id: timelineId,
|
|
||||||
filterManager,
|
filterManager,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { EuiPanel } from '@elastic/eui';
|
import { EuiPanel } from '@elastic/eui';
|
||||||
import { getOr, isEmpty, union } from 'lodash/fp';
|
import { getOr, isEmpty, union } from 'lodash/fp';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import deepEqual from 'fast-deep-equal';
|
import deepEqual from 'fast-deep-equal';
|
||||||
|
|
||||||
|
@ -35,7 +34,6 @@ import {
|
||||||
} from '../../../../../../../src/plugins/data/public';
|
} from '../../../../../../../src/plugins/data/public';
|
||||||
import { inputsModel } from '../../store';
|
import { inputsModel } from '../../store';
|
||||||
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
|
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
|
||||||
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
|
|
||||||
|
|
||||||
const DEFAULT_EVENTS_VIEWER_HEIGHT = 500;
|
const DEFAULT_EVENTS_VIEWER_HEIGHT = 500;
|
||||||
|
|
||||||
|
@ -93,7 +91,6 @@ const EventsViewerComponent: React.FC<Props> = ({
|
||||||
toggleColumn,
|
toggleColumn,
|
||||||
utilityBar,
|
utilityBar,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
|
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
|
||||||
const kibana = useKibana();
|
const kibana = useKibana();
|
||||||
const { filterManager } = useKibana().services.data.query;
|
const { filterManager } = useKibana().services.data.query;
|
||||||
|
@ -103,16 +100,8 @@ const EventsViewerComponent: React.FC<Props> = ({
|
||||||
getManageTimelineById,
|
getManageTimelineById,
|
||||||
setIsTimelineLoading,
|
setIsTimelineLoading,
|
||||||
setTimelineFilterManager,
|
setTimelineFilterManager,
|
||||||
setTimelineRowActions,
|
|
||||||
} = useManageTimeline();
|
} = useManageTimeline();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimelineRowActions({
|
|
||||||
id,
|
|
||||||
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
|
|
||||||
});
|
|
||||||
}, [setTimelineRowActions, id, dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsTimelineLoading({ id, isLoading: isQueryLoading });
|
setIsTimelineLoading({ id, isLoading: isQueryLoading });
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { Props } from './top_n';
|
||||||
import { StatefulTopN } from '.';
|
import { StatefulTopN } from '.';
|
||||||
import {
|
import {
|
||||||
ManageGlobalTimeline,
|
ManageGlobalTimeline,
|
||||||
timelineDefaults,
|
getTimelineDefaults,
|
||||||
} from '../../../timelines/components/manage_timeline';
|
} from '../../../timelines/components/manage_timeline';
|
||||||
import { TimelineId } from '../../../../common/types/timeline';
|
import { TimelineId } from '../../../../common/types/timeline';
|
||||||
|
|
||||||
|
@ -272,8 +272,7 @@ describe('StatefulTopN', () => {
|
||||||
filterManager = new FilterManager(mockUiSettingsForFilterManager);
|
filterManager = new FilterManager(mockUiSettingsForFilterManager);
|
||||||
const manageTimelineForTesting = {
|
const manageTimelineForTesting = {
|
||||||
[TimelineId.active]: {
|
[TimelineId.active]: {
|
||||||
...timelineDefaults,
|
...getTimelineDefaults(TimelineId.active),
|
||||||
id: TimelineId.active,
|
|
||||||
filterManager,
|
filterManager,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -351,8 +350,7 @@ describe('StatefulTopN', () => {
|
||||||
|
|
||||||
const manageTimelineForTesting = {
|
const manageTimelineForTesting = {
|
||||||
[TimelineId.active]: {
|
[TimelineId.active]: {
|
||||||
...timelineDefaults,
|
...getTimelineDefaults(TimelineId.active),
|
||||||
id: TimelineId.active,
|
|
||||||
filterManager,
|
filterManager,
|
||||||
documentType: 'alerts',
|
documentType: 'alerts',
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
import { TimelineId } from '../../../../common/types/timeline';
|
import { TimelineId } from '../../../../common/types/timeline';
|
||||||
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
|
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
|
||||||
import { HostsComponentsQueryProps } from './types';
|
import { HostsComponentsQueryProps } from './types';
|
||||||
|
@ -18,6 +19,7 @@ import { MatrixHistogramContainer } from '../../../common/components/matrix_hist
|
||||||
import * as i18n from '../translations';
|
import * as i18n from '../translations';
|
||||||
import { HistogramType } from '../../../graphql/types';
|
import { HistogramType } from '../../../graphql/types';
|
||||||
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
|
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
|
||||||
|
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
|
||||||
|
|
||||||
const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
|
const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
|
||||||
|
|
||||||
|
@ -57,13 +59,17 @@ export const EventsQueryTabBody = ({
|
||||||
startDate,
|
startDate,
|
||||||
}: HostsComponentsQueryProps) => {
|
}: HostsComponentsQueryProps) => {
|
||||||
const { initializeTimeline } = useManageTimeline();
|
const { initializeTimeline } = useManageTimeline();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeTimeline({
|
initializeTimeline({
|
||||||
id: TimelineId.hostsPageEvents,
|
id: TimelineId.hostsPageEvents,
|
||||||
defaultModel: eventsDefaultModel,
|
defaultModel: eventsDefaultModel,
|
||||||
|
timelineRowActions: [
|
||||||
|
getInvestigateInResolverAction({ dispatch, timelineId: TimelineId.hostsPageEvents }),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}, [initializeTimeline]);
|
}, [dispatch, initializeTimeline]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import React, { createContext, useCallback, useContext, useReducer } from 'react';
|
import React, { createContext, useCallback, useContext, useReducer } from 'react';
|
||||||
import { noop } from 'lodash/fp';
|
import { noop } from 'lodash/fp';
|
||||||
|
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager';
|
import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager';
|
||||||
import { TimelineRowAction } from '../timeline/body/actions';
|
import { TimelineRowAction } from '../timeline/body/actions';
|
||||||
|
@ -22,6 +23,7 @@ interface ManageTimelineInit {
|
||||||
indexToAdd?: string[] | null;
|
indexToAdd?: string[] | null;
|
||||||
loadingText?: string;
|
loadingText?: string;
|
||||||
selectAll?: boolean;
|
selectAll?: boolean;
|
||||||
|
timelineRowActions: TimelineRowAction[];
|
||||||
title?: string;
|
title?: string;
|
||||||
unit?: (totalCount: number) => string;
|
unit?: (totalCount: number) => string;
|
||||||
}
|
}
|
||||||
|
@ -73,19 +75,20 @@ type ActionManageTimeline =
|
||||||
payload: { filterManager: FilterManager };
|
payload: { filterManager: FilterManager };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const timelineDefaults = {
|
export const getTimelineDefaults = (id: string) => ({
|
||||||
indexToAdd: null,
|
indexToAdd: null,
|
||||||
defaultModel: timelineDefaultModel,
|
defaultModel: timelineDefaultModel,
|
||||||
loadingText: i18n.LOADING_EVENTS,
|
loadingText: i18n.LOADING_EVENTS,
|
||||||
footerText: i18nF.TOTAL_COUNT_OF_EVENTS,
|
footerText: i18nF.TOTAL_COUNT_OF_EVENTS,
|
||||||
documentType: i18nF.TOTAL_COUNT_OF_EVENTS,
|
documentType: i18nF.TOTAL_COUNT_OF_EVENTS,
|
||||||
selectAll: false,
|
selectAll: false,
|
||||||
|
id,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
queryFields: [],
|
queryFields: [],
|
||||||
timelineRowActions: [],
|
timelineRowActions: [],
|
||||||
title: i18n.EVENTS,
|
title: i18n.EVENTS,
|
||||||
unit: (n: number) => i18n.UNIT(n),
|
unit: (n: number) => i18n.UNIT(n),
|
||||||
};
|
});
|
||||||
const reducerManageTimeline = (
|
const reducerManageTimeline = (
|
||||||
state: ManageTimelineById,
|
state: ManageTimelineById,
|
||||||
action: ActionManageTimeline
|
action: ActionManageTimeline
|
||||||
|
@ -95,7 +98,7 @@ const reducerManageTimeline = (
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[action.id]: {
|
[action.id]: {
|
||||||
...timelineDefaults,
|
...getTimelineDefaults(action.id),
|
||||||
...state[action.id],
|
...state[action.id],
|
||||||
...action.payload,
|
...action.payload,
|
||||||
},
|
},
|
||||||
|
@ -216,8 +219,8 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
|
||||||
if (state[id] != null) {
|
if (state[id] != null) {
|
||||||
return state[id];
|
return state[id];
|
||||||
}
|
}
|
||||||
initializeTimeline({ id });
|
initializeTimeline({ id, timelineRowActions: [] });
|
||||||
return { ...timelineDefaults, id };
|
return getTimelineDefaults(id);
|
||||||
},
|
},
|
||||||
[initializeTimeline, state]
|
[initializeTimeline, state]
|
||||||
);
|
);
|
||||||
|
@ -236,7 +239,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = {
|
const init = {
|
||||||
getManageTimelineById: (id: string) => ({ ...timelineDefaults, id }),
|
getManageTimelineById: (id: string) => getTimelineDefaults(id),
|
||||||
getTimelineFilterManager: () => undefined,
|
getTimelineFilterManager: () => undefined,
|
||||||
setIndexToAdd: () => undefined,
|
setIndexToAdd: () => undefined,
|
||||||
isManagedTimeline: () => false,
|
isManagedTimeline: () => false,
|
||||||
|
@ -245,6 +248,7 @@ const init = {
|
||||||
setTimelineRowActions: () => noop,
|
setTimelineRowActions: () => noop,
|
||||||
setTimelineFilterManager: () => noop,
|
setTimelineFilterManager: () => noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ManageTimelineContext = createContext<UseTimelineManager>(init);
|
const ManageTimelineContext = createContext<UseTimelineManager>(init);
|
||||||
|
|
||||||
export const useManageTimeline = () => useContext(ManageTimelineContext);
|
export const useManageTimeline = () => useContext(ManageTimelineContext);
|
||||||
|
|
|
@ -194,8 +194,7 @@ export const EventColumnView = React.memo<Props>(
|
||||||
</EventsTdContent>,
|
</EventsTdContent>,
|
||||||
]
|
]
|
||||||
: grouped.icon;
|
: grouped.icon;
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [button, closePopover, id, onClickCb, ecsData, timelineActions, isPopoverOpen]);
|
||||||
}, [button, ecsData, timelineActions, isPopoverOpen]); // , isPopoverOpen, closePopover, onButtonClick]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventsTrData data-test-subj="event-column-view">
|
<EventsTrData data-test-subj="event-column-view">
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended';
|
||||||
import { DataProviders } from '.';
|
import { DataProviders } from '.';
|
||||||
import { DataProvider } from './data_provider';
|
import { DataProvider } from './data_provider';
|
||||||
import { mockDataProviders } from './mock/mock_data_providers';
|
import { mockDataProviders } from './mock/mock_data_providers';
|
||||||
import { ManageGlobalTimeline, timelineDefaults } from '../../manage_timeline';
|
import { ManageGlobalTimeline, getTimelineDefaults } from '../../manage_timeline';
|
||||||
import { FilterManager } from '../../../../../../../../src/plugins/data/public/query/filter_manager';
|
import { FilterManager } from '../../../../../../../../src/plugins/data/public/query/filter_manager';
|
||||||
import { createKibanaCoreStartMock } from '../../../../common/mock/kibana_core';
|
import { createKibanaCoreStartMock } from '../../../../common/mock/kibana_core';
|
||||||
const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings;
|
const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings;
|
||||||
|
@ -28,8 +28,7 @@ describe('DataProviders', () => {
|
||||||
test('renders correctly against snapshot', () => {
|
test('renders correctly against snapshot', () => {
|
||||||
const manageTimelineForTesting = {
|
const manageTimelineForTesting = {
|
||||||
foo: {
|
foo: {
|
||||||
...timelineDefaults,
|
...getTimelineDefaults('foo'),
|
||||||
id: 'foo',
|
|
||||||
filterManager,
|
filterManager,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { mockDataProviders } from './mock/mock_data_providers';
|
||||||
import { Providers } from './providers';
|
import { Providers } from './providers';
|
||||||
import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME } from './provider_item_actions';
|
import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME } from './provider_item_actions';
|
||||||
import { useMountAppended } from '../../../../common/utils/use_mount_appended';
|
import { useMountAppended } from '../../../../common/utils/use_mount_appended';
|
||||||
import { ManageGlobalTimeline, timelineDefaults } from '../../manage_timeline';
|
import { ManageGlobalTimeline, getTimelineDefaults } from '../../manage_timeline';
|
||||||
|
|
||||||
const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings;
|
const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings;
|
||||||
|
|
||||||
|
@ -27,8 +27,7 @@ describe('Providers', () => {
|
||||||
|
|
||||||
const manageTimelineForTesting = {
|
const manageTimelineForTesting = {
|
||||||
foo: {
|
foo: {
|
||||||
...timelineDefaults,
|
...getTimelineDefaults('foo'),
|
||||||
id: 'foo',
|
|
||||||
filterManager,
|
filterManager,
|
||||||
isLoading,
|
isLoading,
|
||||||
},
|
},
|
||||||
|
|
|
@ -177,12 +177,11 @@ export const TimelineComponent: React.FC<Props> = ({
|
||||||
setIndexToAdd,
|
setIndexToAdd,
|
||||||
setIsTimelineLoading,
|
setIsTimelineLoading,
|
||||||
setTimelineFilterManager,
|
setTimelineFilterManager,
|
||||||
setTimelineRowActions,
|
|
||||||
} = useManageTimeline();
|
} = useManageTimeline();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeTimeline({ id, indexToAdd });
|
initializeTimeline({
|
||||||
setTimelineRowActions({
|
|
||||||
id,
|
id,
|
||||||
|
indexToAdd,
|
||||||
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
|
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue