mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[RAC] [SECURIT SOLUTIUONS] Remove drag drop security solutions (#106721)
* wip to remove drag & drop * fix timeline action visibility and filter present on scroll * remove unused class * clean up hover actions * add isDraggable on row render to allow the control of drag and drop * fix add to timeline to work the old way * fix types + unit test * fix cypress test, I went to fast with the renaming * review I Co-authored-by: Michael Olorunnisola <michael.olorunnisola@elastic.co>
This commit is contained in:
parent
5e2c22cef9
commit
3f14abb372
148 changed files with 2170 additions and 2292 deletions
|
@ -44,7 +44,7 @@ describe('timeline data providers', () => {
|
|||
closeTimeline();
|
||||
});
|
||||
|
||||
it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
|
||||
it.skip('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => {
|
||||
dragAndDropFirstHostToTimeline();
|
||||
openTimelineUsingToggle();
|
||||
cy.get(`${TIMELINE_FLYOUT} ${TIMELINE_DROPPED_DATA_PROVIDERS}`)
|
||||
|
@ -78,7 +78,7 @@ describe('timeline data providers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
|
||||
it.skip('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
|
||||
dragFirstHostToTimeline();
|
||||
|
||||
cy.get(IS_DRAGGING_DATA_PROVIDERS)
|
||||
|
@ -87,7 +87,7 @@ describe('timeline data providers', () => {
|
|||
.should('have.class', 'drop-target-data-providers');
|
||||
});
|
||||
|
||||
it('render an extra highlighted area in dataProvider when the user starts dragging a host AND is hovering over the data providers', () => {
|
||||
it.skip('render an extra highlighted area in dataProvider when the user starts dragging a host AND is hovering over the data providers', () => {
|
||||
dragFirstHostToEmptyTimelineDataProviders();
|
||||
|
||||
cy.get(IS_DRAGGING_DATA_PROVIDERS)
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('timeline flyout button', () => {
|
|||
closeTimelineUsingCloseButton();
|
||||
});
|
||||
|
||||
it('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
|
||||
it.skip('sets correct classes when the user starts dragging a host, but is not hovering over the data providers', () => {
|
||||
dragFirstHostToTimeline();
|
||||
|
||||
cy.get(IS_DRAGGING_DATA_PROVIDERS)
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
|
||||
export const ALL_HOSTS_TABLE = '[data-test-subj="table-allHosts-loading-false"]';
|
||||
|
||||
export const HOSTS_NAMES = '[data-test-subj="draggable-content-host.name"] a.euiLink';
|
||||
export const HOSTS_NAMES = '[data-test-subj="render-content-host.name"] a.euiLink';
|
||||
|
||||
export const HOSTS_NAMES_DRAGGABLE = '[data-test-subj="draggable-content-host.name"]';
|
||||
export const HOSTS_NAMES_DRAGGABLE = '[data-test-subj="render-content-host.name"]';
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const PROCESS_NAME_FIELD = '[data-test-subj="draggable-content-process.name"]';
|
||||
export const PROCESS_NAME_FIELD = '[data-test-subj="render-content-process.name"]';
|
||||
|
||||
export const UNCOMMON_PROCESSES_TABLE = '[data-test-subj="table-uncommonProcesses-loading-false"]';
|
||||
|
|
|
@ -340,13 +340,9 @@ describe.each(chartDataSets)('BarChart with stackByField', () => {
|
|||
const dataProviderId = `draggableId.content.draggable-legend-item-uuid_v4()-${escapeDataProviderId(
|
||||
stackByField
|
||||
)}-${escapeDataProviderId(datum.key)}`;
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[draggableId="${dataProviderId}"] [data-test-subj="providerContainer"]`)
|
||||
.first()
|
||||
.text()
|
||||
).toEqual(datum.key);
|
||||
expect(wrapper.find(`div[data-provider-id="${dataProviderId}"]`).first().text()).toEqual(
|
||||
datum.key
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -46,6 +46,7 @@ const DraggableLegendItemComponent: React.FC<{
|
|||
data-test-subj={`legend-item-${dataProviderId}`}
|
||||
field={field}
|
||||
id={dataProviderId}
|
||||
isDraggable={false}
|
||||
timelineId={timelineId}
|
||||
value={value}
|
||||
/>
|
||||
|
|
|
@ -17,6 +17,7 @@ exports[`DraggableWrapper rendering it renders against the snapshot 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
isDraggable={true}
|
||||
render={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -42,7 +42,11 @@ describe('DraggableWrapper', () => {
|
|||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DraggableWrapper dataProvider={dataProvider} render={() => message} />
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProvider}
|
||||
isDraggable={true}
|
||||
render={() => message}
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -54,7 +58,11 @@ describe('DraggableWrapper', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DraggableWrapper dataProvider={dataProvider} render={() => message} />
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProvider}
|
||||
isDraggable={true}
|
||||
render={() => message}
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -66,19 +74,27 @@ describe('DraggableWrapper', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DraggableWrapper dataProvider={dataProvider} render={() => message} />
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProvider}
|
||||
isDraggable={true}
|
||||
render={() => message}
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(false);
|
||||
expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('it renders hover actions when the mouse is over the text of draggable wrapper', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DraggableWrapper dataProvider={dataProvider} render={() => message} />
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProvider}
|
||||
isDraggable={true}
|
||||
render={() => message}
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -88,7 +104,7 @@ describe('DraggableWrapper', () => {
|
|||
wrapper.update();
|
||||
jest.runAllTimers();
|
||||
wrapper.update();
|
||||
expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -98,7 +114,12 @@ describe('DraggableWrapper', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DraggableWrapper dataProvider={dataProvider} render={() => message} truncate />
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProvider}
|
||||
isDraggable={true}
|
||||
render={() => message}
|
||||
truncate
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -112,7 +133,11 @@ describe('DraggableWrapper', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DraggableWrapper dataProvider={dataProvider} render={() => message} />
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProvider}
|
||||
isDraggable={true}
|
||||
render={() => message}
|
||||
/>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { EuiScreenReaderOnly } from '@elastic/eui';
|
||||
import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '@kbn/securitysolution-t-grid';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Draggable,
|
||||
DraggableProvided,
|
||||
|
@ -25,12 +25,13 @@ import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/com
|
|||
|
||||
import { TruncatableText } from '../truncatable_text';
|
||||
import { WithHoverActions } from '../with_hover_actions';
|
||||
import { DraggableWrapperHoverContent, useGetTimelineId } from './draggable_wrapper_hover_content';
|
||||
|
||||
import { getDraggableId, getDroppableId } from './helpers';
|
||||
import { ProviderContainer } from './provider_container';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { useHoverActions } from '../hover_actions/use_hover_actions';
|
||||
|
||||
// As right now, we do not know what we want there, we will keep it as a placeholder
|
||||
export const DragEffects = styled.div``;
|
||||
|
@ -80,7 +81,7 @@ const Wrapper = styled.div<WrapperProps>`
|
|||
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
const ProviderContentWrapper = styled.span`
|
||||
export const ProviderContentWrapper = styled.span`
|
||||
> span.euiToolTipAnchor {
|
||||
display: block; /* allow EuiTooltip content to be truncatable */
|
||||
}
|
||||
|
@ -95,6 +96,7 @@ type RenderFunctionProp = (
|
|||
interface Props {
|
||||
dataProvider: DataProvider;
|
||||
disabled?: boolean;
|
||||
isDraggable?: boolean;
|
||||
inline?: boolean;
|
||||
render: RenderFunctionProp;
|
||||
timelineId?: string;
|
||||
|
@ -121,55 +123,35 @@ export const getStyle = (
|
|||
};
|
||||
};
|
||||
|
||||
const draggableContainsLinks = (draggableElement: HTMLDivElement | null) => {
|
||||
const links = draggableElement?.querySelectorAll('.euiLink') ?? [];
|
||||
return links.length > 0;
|
||||
};
|
||||
|
||||
const DraggableWrapperComponent: React.FC<Props> = ({
|
||||
const DraggableOnWrapperComponent: React.FC<Props> = ({
|
||||
dataProvider,
|
||||
onFilterAdded,
|
||||
render,
|
||||
timelineId,
|
||||
truncate,
|
||||
}) => {
|
||||
const keyboardHandlerRef = useRef<HTMLDivElement | null>(null);
|
||||
const draggableRef = useRef<HTMLDivElement | null>(null);
|
||||
const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
|
||||
const [showTopN, setShowTopN] = useState<boolean>(false);
|
||||
const [goGetTimelineId, setGoGetTimelineId] = useState(false);
|
||||
const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId);
|
||||
const [providerRegistered, setProviderRegistered] = useState(false);
|
||||
const isDisabled = dataProvider.id.includes(`-${ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID}-`);
|
||||
const [hoverActionsOwnFocus, setHoverActionsOwnFocus] = useState<boolean>(false);
|
||||
const dispatch = useDispatch();
|
||||
const { timelines } = useKibana().services;
|
||||
|
||||
const handleClosePopOverTrigger = useCallback(() => {
|
||||
setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
|
||||
setHoverActionsOwnFocus((prevHoverActionsOwnFocus) => {
|
||||
if (prevHoverActionsOwnFocus) {
|
||||
setTimeout(() => {
|
||||
keyboardHandlerRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
return false; // always give up ownership
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setHoverActionsOwnFocus(false);
|
||||
}, 0); // invoked on the next tick, because we want to restore focus first
|
||||
}, [keyboardHandlerRef]);
|
||||
|
||||
const toggleTopN = useCallback(() => {
|
||||
setShowTopN((prevShowTopN) => {
|
||||
const newShowTopN = !prevShowTopN;
|
||||
if (newShowTopN === false) {
|
||||
handleClosePopOverTrigger();
|
||||
}
|
||||
return newShowTopN;
|
||||
});
|
||||
}, [handleClosePopOverTrigger]);
|
||||
const {
|
||||
closePopOverTrigger,
|
||||
handleClosePopOverTrigger,
|
||||
hoverActionsOwnFocus,
|
||||
hoverContent,
|
||||
keyboardHandlerRef,
|
||||
onCloseRequested,
|
||||
openPopover,
|
||||
onFocus,
|
||||
setContainerRef,
|
||||
showTopN,
|
||||
} = useHoverActions({
|
||||
dataProvider,
|
||||
onFilterAdded,
|
||||
render,
|
||||
timelineId,
|
||||
truncate,
|
||||
});
|
||||
|
||||
const registerProvider = useCallback(() => {
|
||||
if (!isDisabled) {
|
||||
|
@ -192,49 +174,6 @@ const DraggableWrapperComponent: React.FC<Props> = ({
|
|||
[unRegisterProvider]
|
||||
);
|
||||
|
||||
const hoverContent = useMemo(() => {
|
||||
// display links as additional content in the hover menu to enable keyboard
|
||||
// navigation of links (when the draggable contains them):
|
||||
const additionalContent =
|
||||
hoverActionsOwnFocus && !showTopN && draggableContainsLinks(draggableRef.current) ? (
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`draggable-link-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
|
||||
</ProviderContentWrapper>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<DraggableWrapperHoverContent
|
||||
additionalContent={additionalContent}
|
||||
closePopOver={handleClosePopOverTrigger}
|
||||
draggableId={getDraggableId(dataProvider.id)}
|
||||
field={dataProvider.queryMatch.field}
|
||||
goGetTimelineId={setGoGetTimelineId}
|
||||
onFilterAdded={onFilterAdded}
|
||||
ownFocus={hoverActionsOwnFocus}
|
||||
showTopN={showTopN}
|
||||
timelineId={timelineId ?? timelineIdFind}
|
||||
toggleTopN={toggleTopN}
|
||||
value={
|
||||
typeof dataProvider.queryMatch.value !== 'number'
|
||||
? dataProvider.queryMatch.value
|
||||
: `${dataProvider.queryMatch.value}`
|
||||
}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
dataProvider,
|
||||
handleClosePopOverTrigger,
|
||||
hoverActionsOwnFocus,
|
||||
onFilterAdded,
|
||||
render,
|
||||
showTopN,
|
||||
timelineId,
|
||||
timelineIdFind,
|
||||
toggleTopN,
|
||||
]);
|
||||
|
||||
const RenderClone = useCallback(
|
||||
(provided, snapshot) => (
|
||||
<ConditionalPortal registerProvider={registerProvider}>
|
||||
|
@ -264,7 +203,7 @@ const DraggableWrapperComponent: React.FC<Props> = ({
|
|||
{...provided.dragHandleProps}
|
||||
ref={(e: HTMLDivElement) => {
|
||||
provided.innerRef(e);
|
||||
draggableRef.current = e;
|
||||
setContainerRef(e);
|
||||
}}
|
||||
data-test-subj="providerContainer"
|
||||
isDragging={snapshot.isDragging}
|
||||
|
@ -292,13 +231,9 @@ const DraggableWrapperComponent: React.FC<Props> = ({
|
|||
)}
|
||||
</ProviderContainer>
|
||||
),
|
||||
[dataProvider, registerProvider, render, truncate]
|
||||
[dataProvider, registerProvider, render, setContainerRef, truncate]
|
||||
);
|
||||
|
||||
const openPopover = useCallback(() => {
|
||||
setHoverActionsOwnFocus(true);
|
||||
}, []);
|
||||
|
||||
const { onBlur, onKeyDown } = timelines.getUseDraggableKeyboardWrapper()({
|
||||
closePopover: handleClosePopOverTrigger,
|
||||
draggableId: getDraggableId(dataProvider.id),
|
||||
|
@ -307,24 +242,6 @@ const DraggableWrapperComponent: React.FC<Props> = ({
|
|||
openPopover,
|
||||
});
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
if (!hoverActionsOwnFocus) {
|
||||
keyboardHandlerRef.current?.focus();
|
||||
}
|
||||
}, [hoverActionsOwnFocus, keyboardHandlerRef]);
|
||||
|
||||
const onCloseRequested = useCallback(() => {
|
||||
setShowTopN(false);
|
||||
|
||||
if (hoverActionsOwnFocus) {
|
||||
setHoverActionsOwnFocus(false);
|
||||
|
||||
setTimeout(() => {
|
||||
onFocus(); // return focus to this draggable on the next tick, because we owned focus
|
||||
}, 0);
|
||||
}
|
||||
}, [onFocus, hoverActionsOwnFocus]);
|
||||
|
||||
const DroppableContent = useCallback(
|
||||
(droppableProvided) => (
|
||||
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
|
@ -350,7 +267,7 @@ const DraggableWrapperComponent: React.FC<Props> = ({
|
|||
{droppableProvided.placeholder}
|
||||
</div>
|
||||
),
|
||||
[DraggableContent, dataProvider.id, isDisabled, onBlur, onFocus, onKeyDown]
|
||||
[DraggableContent, dataProvider.id, isDisabled, keyboardHandlerRef, onBlur, onFocus, onKeyDown]
|
||||
);
|
||||
|
||||
const content = useMemo(
|
||||
|
@ -385,6 +302,75 @@ const DraggableWrapperComponent: React.FC<Props> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const DraggableWrapperComponent: React.FC<Props> = ({
|
||||
dataProvider,
|
||||
isDraggable = false,
|
||||
onFilterAdded,
|
||||
render,
|
||||
timelineId,
|
||||
truncate,
|
||||
}) => {
|
||||
const {
|
||||
closePopOverTrigger,
|
||||
hoverActionsOwnFocus,
|
||||
hoverContent,
|
||||
onCloseRequested,
|
||||
setContainerRef,
|
||||
showTopN,
|
||||
} = useHoverActions({
|
||||
dataProvider,
|
||||
isDraggable,
|
||||
onFilterAdded,
|
||||
render,
|
||||
timelineId,
|
||||
truncate,
|
||||
});
|
||||
const renderContent = useCallback(
|
||||
() => (
|
||||
<div
|
||||
ref={(e: HTMLDivElement) => {
|
||||
setContainerRef(e);
|
||||
}}
|
||||
tabIndex={-1}
|
||||
data-provider-id={getDraggableId(dataProvider.id)}
|
||||
>
|
||||
{truncate ? (
|
||||
<TruncatableText data-test-subj="render-truncatable-content">
|
||||
{render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
|
||||
</TruncatableText>
|
||||
) : (
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`render-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
|
||||
</ProviderContentWrapper>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
[dataProvider, render, setContainerRef, truncate]
|
||||
);
|
||||
if (!isDraggable) {
|
||||
return (
|
||||
<WithHoverActions
|
||||
alwaysShow={showTopN || hoverActionsOwnFocus}
|
||||
closePopOverTrigger={closePopOverTrigger}
|
||||
hoverContent={hoverContent}
|
||||
onCloseRequested={onCloseRequested}
|
||||
render={renderContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DraggableOnWrapperComponent
|
||||
dataProvider={dataProvider}
|
||||
onFilterAdded={onFilterAdded}
|
||||
render={render}
|
||||
timelineId={timelineId}
|
||||
truncate={truncate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DraggableWrapper = React.memo(DraggableWrapperComponent);
|
||||
|
||||
DraggableWrapper.displayName = 'DraggableWrapper';
|
||||
|
|
|
@ -1,564 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { mockBrowserFields } from '../../containers/source/mock';
|
||||
import '../../mock/match_media';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { FilterManager } from '../../../../../../../src/plugins/data/public';
|
||||
import { useSourcererScope } from '../../containers/sourcerer';
|
||||
import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
|
||||
jest.mock('../link_to');
|
||||
jest.mock('../../lib/kibana');
|
||||
jest.mock('../../containers/sourcerer', () => {
|
||||
const original = jest.requireActual('../../containers/sourcerer');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useSourcererScope: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
return {
|
||||
v1: jest.fn(() => 'uuid.v1()'),
|
||||
v4: jest.fn(() => 'uuid.v4()'),
|
||||
};
|
||||
});
|
||||
const mockStartDragToTimeline = jest.fn();
|
||||
jest.mock('../../../../../timelines/public/hooks/use_add_to_timeline', () => {
|
||||
const original = jest.requireActual('../../../../../timelines/public/hooks/use_add_to_timeline');
|
||||
return {
|
||||
...original,
|
||||
useAddToTimeline: () => ({ startDragToTimeline: mockStartDragToTimeline }),
|
||||
};
|
||||
});
|
||||
const mockAddFilters = jest.fn();
|
||||
jest.mock('../../../common/hooks/use_selector', () => ({
|
||||
useShallowEqualSelector: jest.fn(),
|
||||
useDeepEqualSelector: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../common/hooks/use_invalid_filter_query.tsx');
|
||||
|
||||
const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
|
||||
const timelineId = TimelineId.active;
|
||||
const field = 'process.name';
|
||||
const value = 'nice';
|
||||
const toggleTopN = jest.fn();
|
||||
const goGetTimelineId = jest.fn();
|
||||
const defaultProps = {
|
||||
field,
|
||||
goGetTimelineId,
|
||||
ownFocus: false,
|
||||
showTopN: false,
|
||||
timelineId,
|
||||
toggleTopN,
|
||||
value,
|
||||
};
|
||||
|
||||
describe('DraggableWrapperHoverContent', () => {
|
||||
beforeAll(() => {
|
||||
mockStartDragToTimeline.mockReset();
|
||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
||||
filterManager: { addFilters: mockAddFilters },
|
||||
});
|
||||
(useSourcererScope as jest.Mock).mockReturnValue({
|
||||
browserFields: mockBrowserFields,
|
||||
selectedPatterns: [],
|
||||
indexPattern: {},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The tests for "Filter for value" and "Filter out value" are similar enough
|
||||
* to combine them into "table tests" using this array
|
||||
*/
|
||||
const forOrOut = ['for', 'out'];
|
||||
|
||||
forOrOut.forEach((hoverAction) => {
|
||||
describe(`Filter ${hoverAction} value`, () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
test(`it renders the 'Filter ${hoverAction} value' button when showTopN is false`, () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test(`it does NOT render the 'Filter ${hoverAction} value' button when showTopN is true`, () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent {...{ ...defaultProps, showTopN: true }} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().exists()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test(`it should call goGetTimelineId when user is over the 'Filter ${hoverAction} value' button`, () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent {...{ ...defaultProps, timelineId: undefined }} />
|
||||
</TestProviders>
|
||||
);
|
||||
const button = wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first();
|
||||
button.simulate('mouseenter');
|
||||
expect(goGetTimelineId).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
describe('when run in the context of a timeline', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
let onFilterAdded: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
onFilterAdded = jest.fn();
|
||||
|
||||
wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent {...{ ...defaultProps, onFilterAdded }} />
|
||||
</TestProviders>
|
||||
);
|
||||
});
|
||||
|
||||
test('when clicked, it adds a filter to the timeline when running in the context of a timeline', () => {
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(mockAddFilters).toBeCalledWith({
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'process.name',
|
||||
negate: hoverAction === 'out' ? true : false,
|
||||
params: { query: 'nice' },
|
||||
type: 'phrase',
|
||||
value: 'nice',
|
||||
},
|
||||
query: { match: { 'process.name': { query: 'nice', type: 'phrase' } } },
|
||||
});
|
||||
});
|
||||
|
||||
test('when clicked, invokes onFilterAdded when running in the context of a timeline', () => {
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(onFilterAdded).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when NOT run in the context of a timeline', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
let onFilterAdded: () => void;
|
||||
const kibana = useKibana();
|
||||
|
||||
beforeEach(() => {
|
||||
kibana.services.data.query.filterManager.addFilters = jest.fn();
|
||||
onFilterAdded = jest.fn();
|
||||
|
||||
wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{ ...defaultProps, onFilterAdded, timelineId: TimelineId.test }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
});
|
||||
|
||||
test('when clicked, it adds a filter to the global filters when NOT running in the context of a timeline', () => {
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(kibana.services.data.query.filterManager.addFilters).toBeCalledWith({
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'process.name',
|
||||
negate: hoverAction === 'out' ? true : false,
|
||||
params: { query: 'nice' },
|
||||
type: 'phrase',
|
||||
value: 'nice',
|
||||
},
|
||||
query: { match: { 'process.name': { query: 'nice', type: 'phrase' } } },
|
||||
});
|
||||
});
|
||||
|
||||
test('when clicked, invokes onFilterAdded when NOT running in the context of a timeline', () => {
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(onFilterAdded).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('an empty string value when run in the context of a timeline', () => {
|
||||
let filterManager: FilterManager;
|
||||
let wrapper: ReactWrapper;
|
||||
let onFilterAdded: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
filterManager = new FilterManager(mockUiSettingsForFilterManager);
|
||||
filterManager.addFilters = jest.fn();
|
||||
onFilterAdded = jest.fn();
|
||||
|
||||
wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent {...{ ...defaultProps, onFilterAdded, value: '' }} />
|
||||
</TestProviders>
|
||||
);
|
||||
});
|
||||
|
||||
const expectedFilterTypeDescription =
|
||||
hoverAction === 'for' ? 'a "NOT exists"' : 'an "exists"';
|
||||
test(`when clicked, it adds ${expectedFilterTypeDescription} filter to the timeline when run in the context of a timeline`, () => {
|
||||
const expected =
|
||||
hoverAction === 'for'
|
||||
? {
|
||||
exists: { field: 'process.name' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'process.name',
|
||||
negate: true,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
}
|
||||
: {
|
||||
exists: { field: 'process.name' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'process.name',
|
||||
negate: false,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(mockAddFilters).toBeCalledWith(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('an empty string value when NOT run in the context of a timeline', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
let onFilterAdded: () => void;
|
||||
const kibana = useKibana();
|
||||
|
||||
beforeEach(() => {
|
||||
kibana.services.data.query.filterManager.addFilters = jest.fn();
|
||||
onFilterAdded = jest.fn();
|
||||
|
||||
wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
onFilterAdded,
|
||||
timelineId: TimelineId.test,
|
||||
value: '',
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
});
|
||||
|
||||
const expectedFilterTypeDescription =
|
||||
hoverAction === 'for' ? 'a "NOT exists"' : 'an "exists"';
|
||||
test(`when clicked, it adds ${expectedFilterTypeDescription} filter to the global filters when NOT running in the context of a timeline`, () => {
|
||||
const expected =
|
||||
hoverAction === 'for'
|
||||
? {
|
||||
exists: { field: 'process.name' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'process.name',
|
||||
negate: true,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
}
|
||||
: {
|
||||
exists: { field: 'process.name' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'process.name',
|
||||
negate: false,
|
||||
type: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.find(`[data-test-subj="filter-${hoverAction}-value"]`).first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(kibana.services.data.query.filterManager.addFilters).toBeCalledWith(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add to timeline', () => {
|
||||
const aggregatableStringField = 'cloud.account.id';
|
||||
const draggableId = 'draggable.id';
|
||||
|
||||
[false, true].forEach((showTopN) => {
|
||||
[value, null].forEach((maybeValue) => {
|
||||
[draggableId, undefined].forEach((maybeDraggableId) => {
|
||||
const shouldRender = !showTopN && maybeValue != null && maybeDraggableId != null;
|
||||
const assertion = shouldRender ? 'should render' : 'should NOT render';
|
||||
|
||||
test(`it ${assertion} the 'Add to timeline investigation' button when showTopN is ${showTopN}, value is ${maybeValue}, and a draggableId is ${maybeDraggableId}`, () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
draggableId: maybeDraggableId,
|
||||
field: aggregatableStringField,
|
||||
showTopN,
|
||||
value: maybeValue,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="add-to-timeline"]').first().exists()).toBe(
|
||||
shouldRender
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
draggableId,
|
||||
field: aggregatableStringField,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.find('[data-test-subj="add-to-timeline"]').first().simulate('click');
|
||||
|
||||
await waitFor(() => {
|
||||
wrapper.update();
|
||||
expect(mockStartDragToTimeline).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Top N', () => {
|
||||
test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, () => {
|
||||
const aggregatableStringField = 'cloud.account.id';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: aggregatableStringField,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test(`it renders the 'Show top field' button when showTopN is false and a allowlisted signal field is provided`, () => {
|
||||
const allowlistedField = 'signal.rule.name';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: allowlistedField,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, () => {
|
||||
const notKnownToBrowserFields = 'unknown.field';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: notKnownToBrowserFields,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false);
|
||||
});
|
||||
|
||||
test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, async () => {
|
||||
const allowlistedField = 'signal.rule.name';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: allowlistedField,
|
||||
timelineId: undefined,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
const button = wrapper.find(`[data-test-subj="show-top-field"]`).first();
|
||||
button.simulate('mouseenter');
|
||||
await waitFor(() => {
|
||||
expect(goGetTimelineId).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, () => {
|
||||
const allowlistedField = 'signal.rule.name';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: allowlistedField,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper.find('[data-test-subj="show-top-field"]').first().simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(toggleTopN).toBeCalled();
|
||||
});
|
||||
|
||||
test(`it does NOT render the Top N histogram when when showTopN is false`, () => {
|
||||
const allowlistedField = 'signal.rule.name';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: allowlistedField,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="eventsByDatasetOverviewPanel"]').first().exists()).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test(`it does NOT render the 'Show top field' button when showTopN is true`, () => {
|
||||
const allowlistedField = 'signal.rule.name';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: allowlistedField,
|
||||
showTopN: true,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false);
|
||||
});
|
||||
|
||||
test(`it renders the Top N histogram when when showTopN is true`, () => {
|
||||
const allowlistedField = 'signal.rule.name';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
field: allowlistedField,
|
||||
showTopN: true,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="eventsByDatasetOverview-uuid.v4()Panel"]').first().exists()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Copy to Clipboard', () => {
|
||||
test(`it renders the 'Copy to Clipboard' button when showTopN is false`, () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test(`it does NOT render the 'Copy to Clipboard' button when showTopN is true`, () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableWrapperHoverContent
|
||||
{...{
|
||||
...defaultProps,
|
||||
showTopN: true,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`[data-test-subj="copy-to-clipboard"]`).first().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,425 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFocusTrap,
|
||||
EuiPanel,
|
||||
EuiScreenReaderOnly,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useMemo, useState } from 'react';
|
||||
import { DraggableId } from 'react-beautiful-dnd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getAllFieldsByName } from '../../containers/source';
|
||||
import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../lib/clipboard/clipboard';
|
||||
import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { createFilter } from '../add_filter_to_global_search_bar';
|
||||
import { StatefulTopN } from '../top_n';
|
||||
|
||||
import { allowTopN } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
import { useDeepEqualSelector } from '../../hooks/use_selector';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
|
||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { useSourcererScope } from '../../containers/sourcerer';
|
||||
import { timelineSelectors } from '../../../timelines/store/timeline';
|
||||
import { stopPropagationAndPreventDefault } from '../../../../../timelines/public';
|
||||
import { TooltipWithKeyboardShortcut } from '../accessibility';
|
||||
|
||||
export const AdditionalContent = styled.div`
|
||||
padding: 2px;
|
||||
`;
|
||||
|
||||
AdditionalContent.displayName = 'AdditionalContent';
|
||||
|
||||
const getAdditionalScreenReaderOnlyContext = ({
|
||||
field,
|
||||
value,
|
||||
}: {
|
||||
field: string;
|
||||
value?: string[] | string | null;
|
||||
}): string => {
|
||||
if (value == null) {
|
||||
return field;
|
||||
}
|
||||
|
||||
return Array.isArray(value) ? `${field} ${value.join(' ')}` : `${field} ${value}`;
|
||||
};
|
||||
|
||||
const FILTER_FOR_VALUE_KEYBOARD_SHORTCUT = 'f';
|
||||
const FILTER_OUT_VALUE_KEYBOARD_SHORTCUT = 'o';
|
||||
const ADD_TO_TIMELINE_KEYBOARD_SHORTCUT = 'a';
|
||||
const SHOW_TOP_N_KEYBOARD_SHORTCUT = 't';
|
||||
const COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT = 'c';
|
||||
|
||||
interface Props {
|
||||
additionalContent?: React.ReactNode;
|
||||
closePopOver?: () => void;
|
||||
draggableId?: DraggableId;
|
||||
field: string;
|
||||
goGetTimelineId?: (args: boolean) => void;
|
||||
onFilterAdded?: () => void;
|
||||
ownFocus: boolean;
|
||||
showTopN: boolean;
|
||||
timelineId?: string | null;
|
||||
toggleTopN: () => void;
|
||||
value?: string[] | string | null;
|
||||
}
|
||||
|
||||
/** Returns a value for the `disabled` prop of `EuiFocusTrap` */
|
||||
const isFocusTrapDisabled = ({
|
||||
ownFocus,
|
||||
showTopN,
|
||||
}: {
|
||||
ownFocus: boolean;
|
||||
showTopN: boolean;
|
||||
}): boolean => {
|
||||
if (showTopN) {
|
||||
return false; // we *always* want to trap focus when showing Top N
|
||||
}
|
||||
|
||||
return !ownFocus;
|
||||
};
|
||||
|
||||
const DraggableWrapperHoverContentComponent: React.FC<Props> = ({
|
||||
additionalContent = null,
|
||||
closePopOver,
|
||||
draggableId,
|
||||
field,
|
||||
goGetTimelineId,
|
||||
onFilterAdded,
|
||||
ownFocus,
|
||||
showTopN,
|
||||
timelineId,
|
||||
toggleTopN,
|
||||
value,
|
||||
}) => {
|
||||
const kibana = useKibana();
|
||||
const { timelines } = kibana.services;
|
||||
const { startDragToTimeline } = timelines.getUseAddToTimeline()({
|
||||
draggableId,
|
||||
fieldName: field,
|
||||
});
|
||||
const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
|
||||
kibana.services.data.query.filterManager,
|
||||
]);
|
||||
const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
|
||||
const { filterManager: activeFilterMananager } = useDeepEqualSelector((state) =>
|
||||
getManageTimeline(state, timelineId ?? '')
|
||||
);
|
||||
const defaultFocusedButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const panelRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const filterManager = useMemo(
|
||||
() => (timelineId === TimelineId.active ? activeFilterMananager : filterManagerBackup),
|
||||
[timelineId, activeFilterMananager, filterManagerBackup]
|
||||
);
|
||||
|
||||
// Regarding data from useManageTimeline:
|
||||
// * `indexToAdd`, which enables the alerts index to be appended to
|
||||
// the `indexPattern` returned by `useWithSource`, may only be populated when
|
||||
// this component is rendered in the context of the active timeline. This
|
||||
// behavior enables the 'All events' view by appending the alerts index
|
||||
// to the index pattern.
|
||||
const activeScope: SourcererScopeName =
|
||||
timelineId === TimelineId.active
|
||||
? SourcererScopeName.timeline
|
||||
: timelineId != null &&
|
||||
[TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
|
||||
timelineId as TimelineId
|
||||
)
|
||||
? SourcererScopeName.detections
|
||||
: SourcererScopeName.default;
|
||||
const { browserFields, indexPattern } = useSourcererScope(activeScope);
|
||||
const handleStartDragToTimeline = useCallback(() => {
|
||||
startDragToTimeline();
|
||||
if (closePopOver != null) {
|
||||
closePopOver();
|
||||
}
|
||||
}, [closePopOver, startDragToTimeline]);
|
||||
|
||||
const filterForValue = useCallback(() => {
|
||||
const filter =
|
||||
value?.length === 0 ? createFilter(field, undefined) : createFilter(field, value);
|
||||
const activeFilterManager = filterManager;
|
||||
|
||||
if (activeFilterManager != null) {
|
||||
activeFilterManager.addFilters(filter);
|
||||
if (closePopOver != null) {
|
||||
closePopOver();
|
||||
}
|
||||
if (onFilterAdded != null) {
|
||||
onFilterAdded();
|
||||
}
|
||||
}
|
||||
}, [closePopOver, field, value, filterManager, onFilterAdded]);
|
||||
|
||||
const filterOutValue = useCallback(() => {
|
||||
const filter =
|
||||
value?.length === 0 ? createFilter(field, null, false) : createFilter(field, value, true);
|
||||
const activeFilterManager = filterManager;
|
||||
|
||||
if (activeFilterManager != null) {
|
||||
activeFilterManager.addFilters(filter);
|
||||
|
||||
if (closePopOver != null) {
|
||||
closePopOver();
|
||||
}
|
||||
if (onFilterAdded != null) {
|
||||
onFilterAdded();
|
||||
}
|
||||
}
|
||||
}, [closePopOver, field, value, filterManager, onFilterAdded]);
|
||||
|
||||
const isInit = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInit.current && goGetTimelineId != null && timelineId == null) {
|
||||
isInit.current = false;
|
||||
goGetTimelineId(true);
|
||||
}
|
||||
}, [goGetTimelineId, timelineId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ownFocus) {
|
||||
setTimeout(() => {
|
||||
defaultFocusedButtonRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
}, [ownFocus]);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(keyboardEvent: React.KeyboardEvent) => {
|
||||
if (!ownFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (keyboardEvent.key) {
|
||||
case FILTER_FOR_VALUE_KEYBOARD_SHORTCUT:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
filterForValue();
|
||||
break;
|
||||
case FILTER_OUT_VALUE_KEYBOARD_SHORTCUT:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
filterOutValue();
|
||||
break;
|
||||
case ADD_TO_TIMELINE_KEYBOARD_SHORTCUT:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
handleStartDragToTimeline();
|
||||
break;
|
||||
case SHOW_TOP_N_KEYBOARD_SHORTCUT:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
toggleTopN();
|
||||
break;
|
||||
case COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
const copyToClipboardButton = panelRef.current?.querySelector<HTMLButtonElement>(
|
||||
`.${COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME}`
|
||||
);
|
||||
if (copyToClipboardButton != null) {
|
||||
copyToClipboardButton.click();
|
||||
if (closePopOver != null) {
|
||||
closePopOver();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Enter':
|
||||
break;
|
||||
case 'Escape':
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
if (closePopOver != null) {
|
||||
closePopOver();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
[closePopOver, filterForValue, filterOutValue, handleStartDragToTimeline, ownFocus, toggleTopN]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel onKeyDown={onKeyDown} paddingSize={showTopN ? 'none' : 's'} panelRef={panelRef}>
|
||||
<EuiFocusTrap
|
||||
disabled={isFocusTrapDisabled({
|
||||
ownFocus,
|
||||
showTopN,
|
||||
})}
|
||||
>
|
||||
<EuiScreenReaderOnly>
|
||||
<p>{i18n.YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS(field)}</p>
|
||||
</EuiScreenReaderOnly>
|
||||
|
||||
{additionalContent != null && <AdditionalContent>{additionalContent}</AdditionalContent>}
|
||||
|
||||
{!showTopN && value != null && (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<TooltipWithKeyboardShortcut
|
||||
additionalScreenReaderOnlyContext={getAdditionalScreenReaderOnlyContext({
|
||||
field,
|
||||
value,
|
||||
})}
|
||||
content={i18n.FILTER_FOR_VALUE}
|
||||
shortcut={FILTER_FOR_VALUE_KEYBOARD_SHORTCUT}
|
||||
showShortcut={ownFocus}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.FILTER_FOR_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
color="text"
|
||||
data-test-subj="filter-for-value"
|
||||
iconType="magnifyWithPlus"
|
||||
onClick={filterForValue}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
|
||||
{!showTopN && value != null && (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<TooltipWithKeyboardShortcut
|
||||
additionalScreenReaderOnlyContext={getAdditionalScreenReaderOnlyContext({
|
||||
field,
|
||||
value,
|
||||
})}
|
||||
content={i18n.FILTER_OUT_VALUE}
|
||||
shortcut={FILTER_OUT_VALUE_KEYBOARD_SHORTCUT}
|
||||
showShortcut={ownFocus}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.FILTER_OUT_VALUE}
|
||||
color="text"
|
||||
data-test-subj="filter-out-value"
|
||||
iconType="magnifyWithMinus"
|
||||
onClick={filterOutValue}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
|
||||
{!showTopN && value != null && draggableId != null && (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<TooltipWithKeyboardShortcut
|
||||
additionalScreenReaderOnlyContext={getAdditionalScreenReaderOnlyContext({
|
||||
field,
|
||||
value,
|
||||
})}
|
||||
content={i18n.ADD_TO_TIMELINE}
|
||||
shortcut={ADD_TO_TIMELINE_KEYBOARD_SHORTCUT}
|
||||
showShortcut={ownFocus}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.ADD_TO_TIMELINE}
|
||||
color="text"
|
||||
data-test-subj="add-to-timeline"
|
||||
iconType="timeline"
|
||||
onClick={handleStartDragToTimeline}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
|
||||
<>
|
||||
{allowTopN({
|
||||
browserField: getAllFieldsByName(browserFields)[field],
|
||||
fieldName: field,
|
||||
}) && (
|
||||
<>
|
||||
{!showTopN && (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<TooltipWithKeyboardShortcut
|
||||
additionalScreenReaderOnlyContext={getAdditionalScreenReaderOnlyContext({
|
||||
field,
|
||||
value,
|
||||
})}
|
||||
content={i18n.SHOW_TOP(field)}
|
||||
shortcut={SHOW_TOP_N_KEYBOARD_SHORTCUT}
|
||||
showShortcut={ownFocus}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.SHOW_TOP(field)}
|
||||
color="text"
|
||||
data-test-subj="show-top-field"
|
||||
iconType="visBarVertical"
|
||||
onClick={toggleTopN}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
|
||||
{showTopN && (
|
||||
<StatefulTopN
|
||||
browserFields={browserFields}
|
||||
field={field}
|
||||
indexPattern={indexPattern}
|
||||
onFilterAdded={onFilterAdded}
|
||||
timelineId={timelineId ?? undefined}
|
||||
toggleTopN={toggleTopN}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
{!showTopN && (
|
||||
<WithCopyToClipboard
|
||||
data-test-subj="copy-to-clipboard"
|
||||
keyboardShortcut={ownFocus ? COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT : ''}
|
||||
text={`${field}${value != null ? `: "${value}"` : ''}`}
|
||||
titleSummary={i18n.FIELD}
|
||||
/>
|
||||
)}
|
||||
</EuiFocusTrap>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
DraggableWrapperHoverContentComponent.displayName = 'DraggableWrapperHoverContentComponent';
|
||||
|
||||
export const DraggableWrapperHoverContent = React.memo(DraggableWrapperHoverContentComponent);
|
||||
|
||||
export const useGetTimelineId = function (
|
||||
elem: React.MutableRefObject<Element | null>,
|
||||
getTimelineId: boolean = false
|
||||
) {
|
||||
const [timelineId, setTimelineId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let startElem: Element | (Node & ParentNode) | null = elem.current;
|
||||
if (startElem != null && getTimelineId) {
|
||||
for (; startElem && startElem !== document; startElem = startElem.parentNode) {
|
||||
const myElem: Element = startElem as Element;
|
||||
if (
|
||||
myElem != null &&
|
||||
myElem.classList != null &&
|
||||
myElem.classList.contains(SELECTOR_TIMELINE_GLOBAL_CONTAINER) &&
|
||||
myElem.hasAttribute('data-timeline-id')
|
||||
) {
|
||||
setTimelineId(myElem.getAttribute('data-timeline-id'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [elem, getTimelineId]);
|
||||
|
||||
return timelineId;
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
|
||||
|
||||
export const useGetTimelineId = function (
|
||||
elem: React.MutableRefObject<Element | null>,
|
||||
getTimelineId: boolean = false
|
||||
) {
|
||||
const [timelineId, setTimelineId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let startElem: Element | (Node & ParentNode) | null = elem.current;
|
||||
if (startElem != null && getTimelineId) {
|
||||
for (; startElem && startElem !== document; startElem = startElem.parentNode) {
|
||||
const myElem: Element = startElem as Element;
|
||||
if (
|
||||
myElem != null &&
|
||||
myElem.classList != null &&
|
||||
myElem.classList.contains(SELECTOR_TIMELINE_GLOBAL_CONTAINER) &&
|
||||
myElem.hasAttribute('data-timeline-id')
|
||||
) {
|
||||
setTimelineId(myElem.getAttribute('data-timeline-id'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [elem, getTimelineId]);
|
||||
|
||||
return timelineId;
|
||||
};
|
|
@ -36,6 +36,7 @@ exports[`draggables rendering it renders the default DefaultDraggable 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
isDraggable={true}
|
||||
render={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -20,6 +20,7 @@ import { Provider } from '../../../timelines/components/timeline/data_providers/
|
|||
|
||||
export interface DefaultDraggableType {
|
||||
id: string;
|
||||
isDraggable?: boolean;
|
||||
field: string;
|
||||
value?: string | null;
|
||||
name?: string | null;
|
||||
|
@ -79,6 +80,7 @@ Content.displayName = 'Content';
|
|||
* that's only displayed when the specified value is non-`null`.
|
||||
*
|
||||
* @param id - a unique draggable id, which typically follows the format `${contextId}-${eventId}-${field}-${value}`
|
||||
* @param isDraggable - optional prop to disable drag & drop and it will defaulted to true
|
||||
* @param field - the name of the field, e.g. `network.transport`
|
||||
* @param value - value of the field e.g. `tcp`
|
||||
* @param name - defaulting to `field`, this optional human readable name is used by the `DataProvider` that represents the data
|
||||
|
@ -88,7 +90,17 @@ Content.displayName = 'Content';
|
|||
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
|
||||
*/
|
||||
export const DefaultDraggable = React.memo<DefaultDraggableType>(
|
||||
({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) => {
|
||||
({
|
||||
id,
|
||||
isDraggable = true,
|
||||
field,
|
||||
value,
|
||||
name,
|
||||
children,
|
||||
timelineId,
|
||||
tooltipContent,
|
||||
queryValue,
|
||||
}) => {
|
||||
const dataProviderProp: DataProvider = useMemo(
|
||||
() => ({
|
||||
and: [],
|
||||
|
@ -125,6 +137,7 @@ export const DefaultDraggable = React.memo<DefaultDraggableType>(
|
|||
return (
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProviderProp}
|
||||
isDraggable={isDraggable}
|
||||
render={renderCallback}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
|
@ -155,6 +168,7 @@ export type BadgeDraggableType = Omit<DefaultDraggableType, 'id'> & {
|
|||
* @param field - the name of the field, e.g. `network.transport`
|
||||
* @param value - value of the field e.g. `tcp`
|
||||
* @param iconType -the (optional) type of icon e.g. `snowflake` to display on the badge
|
||||
* @param isDraggable
|
||||
* @param name - defaulting to `field`, this optional human readable name is used by the `DataProvider` that represents the data
|
||||
* @param color - defaults to `hollow`, optionally overwrite the color of the badge icon
|
||||
* @param children - defaults to displaying `value`, this allows an arbitrary visualization to be displayed in lieu of the default behavior
|
||||
|
@ -168,6 +182,7 @@ const DraggableBadgeComponent: React.FC<BadgeDraggableType> = ({
|
|||
field,
|
||||
value,
|
||||
iconType,
|
||||
isDraggable,
|
||||
name,
|
||||
color = 'hollow',
|
||||
children,
|
||||
|
@ -177,6 +192,7 @@ const DraggableBadgeComponent: React.FC<BadgeDraggableType> = ({
|
|||
value != null ? (
|
||||
<DefaultDraggable
|
||||
id={`draggable-badge-default-draggable-${contextId}-${eventId}-${field}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
field={field}
|
||||
name={name}
|
||||
value={value}
|
||||
|
|
|
@ -63,6 +63,7 @@ const EnrichmentDescription: React.FC<ThreatSummaryItem['description']> = ({
|
|||
key={key}
|
||||
contextId={key}
|
||||
eventId={eventId}
|
||||
isDraggable={false}
|
||||
fieldName={fieldName || 'unknown'}
|
||||
value={value}
|
||||
/>
|
||||
|
|
|
@ -245,7 +245,7 @@ describe('EventFieldsBrowser', () => {
|
|||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="draggable-content-@timestamp"]').at(0).text()).toEqual(
|
||||
expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').at(0).text()).toEqual(
|
||||
'Feb 28, 2019 @ 16:50:54.621'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback, useState, useRef } from 'react';
|
||||
import { getDraggableId } from '@kbn/securitysolution-t-grid';
|
||||
import { HoverActions } from '../../hover_actions';
|
||||
import { useActionCellDataProvider } from './use_action_cell_data_provider';
|
||||
import { EventFieldsData } from '../types';
|
||||
import { useGetTimelineId } from '../../drag_and_drop/draggable_wrapper_hover_content';
|
||||
import { useGetTimelineId } from '../../drag_and_drop/use_get_timeline_id_from_dom';
|
||||
import { ColumnHeaderOptions } from '../../../../../common/types/timeline';
|
||||
import { BrowserField } from '../../../containers/source';
|
||||
|
||||
|
@ -66,11 +65,10 @@ export const ActionCell: React.FC<Props> = React.memo(
|
|||
});
|
||||
}, []);
|
||||
|
||||
const draggableIds = actionCellConfig?.idList.map((id) => getDraggableId(id));
|
||||
return (
|
||||
<HoverActions
|
||||
dataType={data.type}
|
||||
draggableIds={draggableIds?.length ? draggableIds : undefined}
|
||||
dataProvider={actionCellConfig?.dataProvider}
|
||||
field={data.field}
|
||||
goGetTimelineId={setGoGetTimelineId}
|
||||
isObjectArray={data.isObjectArray}
|
||||
|
|
|
@ -55,6 +55,7 @@ export const FieldValueCell = React.memo(
|
|||
fieldFormat={data.format}
|
||||
fieldName={data.field}
|
||||
fieldType={data.type}
|
||||
isDraggable={false}
|
||||
isObjectArray={data.isObjectArray}
|
||||
value={value}
|
||||
linkValue={(getLinkValue && getLinkValue(data.field)) ?? linkValue}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import { escapeDataProviderId } from '@kbn/securitysolution-t-grid';
|
||||
import { isArray, isEmpty, isString } from 'lodash/fp';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
AGENT_STATUS_FIELD_NAME,
|
||||
EVENT_MODULE_FIELD_NAME,
|
||||
|
@ -27,6 +28,7 @@ import { EVENT_DURATION_FIELD_NAME } from '../../../../timelines/components/dura
|
|||
import { PORT_NAMES } from '../../../../network/components/port';
|
||||
import { INDICATOR_REFERENCE } from '../../../../../common/cti/constants';
|
||||
import { BrowserField } from '../../../containers/source';
|
||||
import { DataProvider, IS_OPERATOR } from '../../../../../common/types';
|
||||
|
||||
export interface UseActionCellDataProvider {
|
||||
contextId?: string;
|
||||
|
@ -40,6 +42,20 @@ export interface UseActionCellDataProvider {
|
|||
values: string[] | null | undefined;
|
||||
}
|
||||
|
||||
const getDataProvider = (field: string, id: string, value: string): DataProvider => ({
|
||||
and: [],
|
||||
enabled: true,
|
||||
id: escapeDataProviderId(id),
|
||||
name: field,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field,
|
||||
value,
|
||||
operator: IS_OPERATOR,
|
||||
},
|
||||
});
|
||||
|
||||
export const useActionCellDataProvider = ({
|
||||
contextId,
|
||||
eventId,
|
||||
|
@ -50,72 +66,90 @@ export const useActionCellDataProvider = ({
|
|||
isObjectArray,
|
||||
linkValue,
|
||||
values,
|
||||
}: UseActionCellDataProvider): { idList: string[]; stringValues: string[] } | null => {
|
||||
if (values === null || values === undefined) return null;
|
||||
|
||||
const stringifiedValues: string[] = [];
|
||||
const arrayValues = Array.isArray(values) ? values : [values];
|
||||
|
||||
const idList: string[] = arrayValues.reduce((memo, value, index) => {
|
||||
let id = null;
|
||||
let valueAsString: string = isString(value) ? value : `${values}`;
|
||||
if (fieldFromBrowserField == null) {
|
||||
stringifiedValues.push(valueAsString);
|
||||
return memo;
|
||||
}
|
||||
const appendedUniqueId = `${contextId}-${eventId}-${field}-${index}-${value}-${eventId}-${field}-${value}`;
|
||||
if (isObjectArray || fieldType === GEO_FIELD_TYPE || [MESSAGE_FIELD_NAME].includes(field)) {
|
||||
stringifiedValues.push(valueAsString);
|
||||
return memo;
|
||||
} else if (fieldType === IP_FIELD_TYPE) {
|
||||
id = `formatted-ip-data-provider-${contextId}-${field}-${value}-${eventId}`;
|
||||
if (isString(value) && !isEmpty(value)) {
|
||||
try {
|
||||
const addresses = JSON.parse(value);
|
||||
if (isArray(addresses)) {
|
||||
valueAsString = addresses.join(',');
|
||||
}
|
||||
} catch (_) {
|
||||
// Default to keeping the existing string value
|
||||
}: UseActionCellDataProvider): {
|
||||
stringValues: string[];
|
||||
dataProvider: DataProvider[];
|
||||
} | null => {
|
||||
const cellData = useMemo(() => {
|
||||
if (values === null || values === undefined) return null;
|
||||
const arrayValues = Array.isArray(values) ? values : [values];
|
||||
return arrayValues.reduce<{
|
||||
stringValues: string[];
|
||||
dataProvider: DataProvider[];
|
||||
}>(
|
||||
(memo, value, index) => {
|
||||
let id: string = '';
|
||||
let valueAsString: string = isString(value) ? value : `${values}`;
|
||||
const appendedUniqueId = `${contextId}-${eventId}-${field}-${index}-${value}`;
|
||||
if (fieldFromBrowserField == null) {
|
||||
memo.stringValues.push(valueAsString);
|
||||
return memo;
|
||||
}
|
||||
}
|
||||
} else if (PORT_NAMES.some((portName) => field === portName)) {
|
||||
id = `port-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === EVENT_DURATION_FIELD_NAME) {
|
||||
id = `duration-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === HOST_NAME_FIELD_NAME) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}`;
|
||||
} else if (fieldFormat === BYTES_FORMAT) {
|
||||
id = `bytes-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === SIGNAL_RULE_NAME_FIELD_NAME) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}-${linkValue}`;
|
||||
} else if (field === EVENT_MODULE_FIELD_NAME) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
|
||||
} else if (field === SIGNAL_STATUS_FIELD_NAME) {
|
||||
id = `alert-details-value-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === AGENT_STATUS_FIELD_NAME) {
|
||||
const valueToUse = typeof value === 'string' ? value : '';
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}`;
|
||||
valueAsString = valueToUse;
|
||||
} else if (
|
||||
[
|
||||
RULE_REFERENCE_FIELD_NAME,
|
||||
REFERENCE_URL_FIELD_NAME,
|
||||
EVENT_URL_FIELD_NAME,
|
||||
INDICATOR_REFERENCE,
|
||||
].includes(field)
|
||||
) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
|
||||
} else {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}`;
|
||||
}
|
||||
stringifiedValues.push(valueAsString);
|
||||
memo.push(escapeDataProviderId(id));
|
||||
return memo;
|
||||
}, [] as string[]);
|
||||
|
||||
return {
|
||||
idList,
|
||||
stringValues: stringifiedValues,
|
||||
};
|
||||
if (isObjectArray || fieldType === GEO_FIELD_TYPE || [MESSAGE_FIELD_NAME].includes(field)) {
|
||||
memo.stringValues.push(valueAsString);
|
||||
return memo;
|
||||
} else if (fieldType === IP_FIELD_TYPE) {
|
||||
id = `formatted-ip-data-provider-${contextId}-${field}-${value}-${eventId}`;
|
||||
if (isString(value) && !isEmpty(value)) {
|
||||
try {
|
||||
const addresses = JSON.parse(value);
|
||||
if (isArray(addresses)) {
|
||||
valueAsString = addresses.join(',');
|
||||
addresses.forEach((ip) => memo.dataProvider.push(getDataProvider(field, id, ip)));
|
||||
}
|
||||
} catch (_) {
|
||||
// Default to keeping the existing string value
|
||||
}
|
||||
memo.stringValues.push(valueAsString);
|
||||
return memo;
|
||||
}
|
||||
} else if (PORT_NAMES.some((portName) => field === portName)) {
|
||||
id = `port-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === EVENT_DURATION_FIELD_NAME) {
|
||||
id = `duration-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === HOST_NAME_FIELD_NAME) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}`;
|
||||
} else if (fieldFormat === BYTES_FORMAT) {
|
||||
id = `bytes-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === SIGNAL_RULE_NAME_FIELD_NAME) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}-${linkValue}`;
|
||||
} else if (field === EVENT_MODULE_FIELD_NAME) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
|
||||
} else if (field === SIGNAL_STATUS_FIELD_NAME) {
|
||||
id = `alert-details-value-default-draggable-${appendedUniqueId}`;
|
||||
} else if (field === AGENT_STATUS_FIELD_NAME) {
|
||||
const valueToUse = typeof value === 'string' ? value : '';
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}`;
|
||||
valueAsString = valueToUse;
|
||||
} else if (
|
||||
[
|
||||
RULE_REFERENCE_FIELD_NAME,
|
||||
REFERENCE_URL_FIELD_NAME,
|
||||
EVENT_URL_FIELD_NAME,
|
||||
INDICATOR_REFERENCE,
|
||||
].includes(field)
|
||||
) {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}-${value}`;
|
||||
} else {
|
||||
id = `event-details-value-default-draggable-${appendedUniqueId}`;
|
||||
}
|
||||
memo.stringValues.push(valueAsString);
|
||||
memo.dataProvider.push(getDataProvider(field, id, value));
|
||||
return memo;
|
||||
},
|
||||
{ stringValues: [], dataProvider: [] }
|
||||
);
|
||||
}, [
|
||||
contextId,
|
||||
eventId,
|
||||
field,
|
||||
fieldFormat,
|
||||
fieldFromBrowserField,
|
||||
fieldType,
|
||||
isObjectArray,
|
||||
linkValue,
|
||||
values,
|
||||
]);
|
||||
return cellData;
|
||||
};
|
||||
|
|
|
@ -6,16 +6,17 @@
|
|||
*/
|
||||
|
||||
import { EuiFocusTrap, EuiScreenReaderOnly } from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useMemo, useState } from 'react';
|
||||
import { DraggableId } from 'react-beautiful-dnd';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getAllFieldsByName } from '../../containers/source';
|
||||
import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../lib/clipboard/clipboard';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { getAllFieldsByName } from '../../containers/source';
|
||||
import { allowTopN } from './utils';
|
||||
import { useDeepEqualSelector } from '../../hooks/use_selector';
|
||||
import { ColumnHeaderOptions, TimelineId } from '../../../../common/types/timeline';
|
||||
import { ColumnHeaderOptions, DataProvider, TimelineId } from '../../../../common/types/timeline';
|
||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { useSourcererScope } from '../../containers/sourcerer';
|
||||
import { timelineSelectors } from '../../../timelines/store/timeline';
|
||||
|
@ -38,43 +39,51 @@ export const AdditionalContent = styled.div`
|
|||
|
||||
AdditionalContent.displayName = 'AdditionalContent';
|
||||
|
||||
const StyledHoverActionsContainer = styled.div<{ $showTopN: boolean }>`
|
||||
const StyledHoverActionsContainer = styled.div<{ $showTopN: boolean; $showOwnFocus: boolean }>`
|
||||
padding: ${(props) => `0 ${props.theme.eui.paddingSizes.s}`};
|
||||
display: flex;
|
||||
|
||||
&:focus-within {
|
||||
.timelines__hoverActionButton,
|
||||
.securitySolution__hoverActionButton {
|
||||
opacity: 1;
|
||||
${(props) =>
|
||||
props.$showOwnFocus
|
||||
? `
|
||||
&:focus-within {
|
||||
.timelines__hoverActionButton,
|
||||
.securitySolution__hoverActionButton {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.timelines__hoverActionButton,
|
||||
.securitySolution__hoverActionButton {
|
||||
opacity: 1;
|
||||
&:hover {
|
||||
.timelines__hoverActionButton,
|
||||
.securitySolution__hoverActionButton {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timelines__hoverActionButton,
|
||||
.securitySolution__hoverActionButton {
|
||||
opacity: ${(props) => (props.$showTopN ? 1 : 0)};
|
||||
opacity: ${props.$showTopN ? 1 : 0};
|
||||
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
: ''}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
additionalContent?: React.ReactNode;
|
||||
closePopOver?: () => void;
|
||||
dataProvider?: DataProvider | DataProvider[];
|
||||
dataType?: string;
|
||||
draggableIds?: DraggableId[];
|
||||
draggableId?: DraggableId;
|
||||
field: string;
|
||||
goGetTimelineId?: (args: boolean) => void;
|
||||
isObjectArray: boolean;
|
||||
onFilterAdded?: () => void;
|
||||
ownFocus: boolean;
|
||||
showOwnFocus?: boolean;
|
||||
showTopN: boolean;
|
||||
timelineId?: string | null;
|
||||
toggleColumn?: (column: ColumnHeaderOptions) => void;
|
||||
|
@ -100,13 +109,15 @@ const isFocusTrapDisabled = ({
|
|||
export const HoverActions: React.FC<Props> = React.memo(
|
||||
({
|
||||
additionalContent = null,
|
||||
dataProvider,
|
||||
dataType,
|
||||
draggableIds,
|
||||
draggableId,
|
||||
field,
|
||||
goGetTimelineId,
|
||||
isObjectArray,
|
||||
onFilterAdded,
|
||||
ownFocus,
|
||||
showOwnFocus = true,
|
||||
showTopN,
|
||||
timelineId,
|
||||
toggleColumn,
|
||||
|
@ -117,29 +128,13 @@ export const HoverActions: React.FC<Props> = React.memo(
|
|||
const { timelines } = kibana.services;
|
||||
// Common actions used by the alert table and alert flyout
|
||||
const {
|
||||
addToTimeline: {
|
||||
AddToTimelineButton,
|
||||
keyboardShortcut: addToTimelineKeyboardShortcut,
|
||||
useGetHandleStartDragToTimeline,
|
||||
},
|
||||
columnToggle: {
|
||||
ColumnToggleButton,
|
||||
columnToggleFn,
|
||||
keyboardShortcut: columnToggleKeyboardShortcut,
|
||||
},
|
||||
copy: { CopyButton, keyboardShortcut: copyKeyboardShortcut },
|
||||
filterForValue: {
|
||||
FilterForValueButton,
|
||||
filterForValueFn,
|
||||
keyboardShortcut: filterForValueKeyboardShortcut,
|
||||
},
|
||||
filterOutValue: {
|
||||
FilterOutValueButton,
|
||||
filterOutValueFn,
|
||||
keyboardShortcut: filterOutValueKeyboardShortcut,
|
||||
},
|
||||
getAddToTimelineButton,
|
||||
getColumnToggleButton,
|
||||
getCopyButton,
|
||||
getFilterForValueButton,
|
||||
getFilterOutValueButton,
|
||||
} = timelines.getHoverActions();
|
||||
|
||||
const [stKeyboardEvent, setStKeyboardEvent] = useState<React.KeyboardEvent>();
|
||||
const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
|
||||
kibana.services.data.query.filterManager,
|
||||
]);
|
||||
|
@ -169,30 +164,8 @@ export const HoverActions: React.FC<Props> = React.memo(
|
|||
: SourcererScopeName.default;
|
||||
const { browserFields } = useSourcererScope(activeScope);
|
||||
|
||||
const handleStartDragToTimeline = (() => {
|
||||
const handleStartDragToTimelineFns = draggableIds?.map((draggableId) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
return useGetHandleStartDragToTimeline({ draggableId, field });
|
||||
});
|
||||
return () => handleStartDragToTimelineFns?.forEach((dragFn) => dragFn());
|
||||
})();
|
||||
|
||||
const handleFilterForValue = useCallback(() => {
|
||||
filterForValueFn({ field, value: values, filterManager, onFilterAdded });
|
||||
}, [filterForValueFn, field, values, filterManager, onFilterAdded]);
|
||||
|
||||
const handleFilterOutValue = useCallback(() => {
|
||||
filterOutValueFn({ field, value: values, filterManager, onFilterAdded });
|
||||
}, [filterOutValueFn, field, values, filterManager, onFilterAdded]);
|
||||
|
||||
const handleToggleColumn = useCallback(
|
||||
() => (toggleColumn ? columnToggleFn({ toggleColumn, field }) : null),
|
||||
[columnToggleFn, field, toggleColumn]
|
||||
);
|
||||
|
||||
const isInit = useRef(true);
|
||||
const defaultFocusedButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const panelRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInit.current && goGetTimelineId != null && timelineId == null) {
|
||||
|
@ -215,31 +188,6 @@ export const HoverActions: React.FC<Props> = React.memo(
|
|||
return;
|
||||
}
|
||||
switch (keyboardEvent.key) {
|
||||
case addToTimelineKeyboardShortcut:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
handleStartDragToTimeline();
|
||||
break;
|
||||
case columnToggleKeyboardShortcut:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
handleToggleColumn();
|
||||
break;
|
||||
case copyKeyboardShortcut:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
const copyToClipboardButton = panelRef.current?.querySelector<HTMLButtonElement>(
|
||||
`.${COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME}`
|
||||
);
|
||||
if (copyToClipboardButton != null) {
|
||||
copyToClipboardButton.click();
|
||||
}
|
||||
break;
|
||||
case filterForValueKeyboardShortcut:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
handleFilterForValue();
|
||||
break;
|
||||
case filterOutValueKeyboardShortcut:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
handleFilterOutValue();
|
||||
break;
|
||||
case SHOW_TOP_N_KEYBOARD_SHORTCUT:
|
||||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
toggleTopN();
|
||||
|
@ -250,33 +198,26 @@ export const HoverActions: React.FC<Props> = React.memo(
|
|||
stopPropagationAndPreventDefault(keyboardEvent);
|
||||
break;
|
||||
default:
|
||||
setStKeyboardEvent(keyboardEvent);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[
|
||||
addToTimelineKeyboardShortcut,
|
||||
columnToggleKeyboardShortcut,
|
||||
copyKeyboardShortcut,
|
||||
filterForValueKeyboardShortcut,
|
||||
filterOutValueKeyboardShortcut,
|
||||
handleFilterForValue,
|
||||
handleFilterOutValue,
|
||||
handleStartDragToTimeline,
|
||||
handleToggleColumn,
|
||||
ownFocus,
|
||||
toggleTopN,
|
||||
]
|
||||
[ownFocus, toggleTopN]
|
||||
);
|
||||
|
||||
const showFilters = values != null;
|
||||
|
||||
return (
|
||||
<StyledHoverActionsContainer onKeyDown={onKeyDown} ref={panelRef} $showTopN={showTopN}>
|
||||
<EuiFocusTrap
|
||||
disabled={isFocusTrapDisabled({
|
||||
ownFocus,
|
||||
showTopN,
|
||||
})}
|
||||
<EuiFocusTrap
|
||||
disabled={isFocusTrapDisabled({
|
||||
ownFocus,
|
||||
showTopN,
|
||||
})}
|
||||
>
|
||||
<StyledHoverActionsContainer
|
||||
onKeyDown={onKeyDown}
|
||||
$showTopN={showTopN}
|
||||
$showOwnFocus={showOwnFocus}
|
||||
>
|
||||
<EuiScreenReaderOnly>
|
||||
<p>{YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS(field)}</p>
|
||||
|
@ -286,46 +227,58 @@ export const HoverActions: React.FC<Props> = React.memo(
|
|||
|
||||
{showFilters && (
|
||||
<>
|
||||
<FilterForValueButton
|
||||
data-test-subj="hover-actions-filter-for"
|
||||
defaultFocusedButtonRef={defaultFocusedButtonRef}
|
||||
field={field}
|
||||
onClick={handleFilterForValue}
|
||||
ownFocus={ownFocus}
|
||||
showTooltip
|
||||
value={values}
|
||||
/>
|
||||
<FilterOutValueButton
|
||||
data-test-subj="hover-actions-filter-out"
|
||||
field={field}
|
||||
onClick={handleFilterOutValue}
|
||||
ownFocus={ownFocus}
|
||||
showTooltip
|
||||
value={values}
|
||||
/>
|
||||
<div data-test-subj="hover-actions-filter-for">
|
||||
{getFilterForValueButton({
|
||||
defaultFocusedButtonRef,
|
||||
field,
|
||||
filterManager,
|
||||
keyboardEvent: stKeyboardEvent,
|
||||
onFilterAdded,
|
||||
ownFocus,
|
||||
showTooltip: true,
|
||||
value: values,
|
||||
})}
|
||||
</div>
|
||||
<div data-test-subj="hover-actions-filter-out">
|
||||
{getFilterOutValueButton({
|
||||
field,
|
||||
filterManager,
|
||||
keyboardEvent: stKeyboardEvent,
|
||||
onFilterAdded,
|
||||
ownFocus,
|
||||
showTooltip: true,
|
||||
value: values,
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{toggleColumn && (
|
||||
<ColumnToggleButton
|
||||
data-test-subj="hover-actions-toggle-column"
|
||||
field={field}
|
||||
isDisabled={isObjectArray && dataType !== 'geo_point'}
|
||||
isObjectArray={isObjectArray}
|
||||
onClick={handleToggleColumn}
|
||||
ownFocus={ownFocus}
|
||||
value={values}
|
||||
/>
|
||||
<div data-test-subj="hover-actions-toggle-column">
|
||||
{getColumnToggleButton({
|
||||
field,
|
||||
isDisabled: isObjectArray && dataType !== 'geo_point',
|
||||
isObjectArray,
|
||||
keyboardEvent: stKeyboardEvent,
|
||||
ownFocus,
|
||||
showTooltip: true,
|
||||
toggleColumn,
|
||||
value: values,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showFilters && draggableIds != null && (
|
||||
<AddToTimelineButton
|
||||
data-test-subj="hover-actions-add-timeline"
|
||||
field={field}
|
||||
onClick={handleStartDragToTimeline}
|
||||
ownFocus={ownFocus}
|
||||
showTooltip
|
||||
value={values}
|
||||
/>
|
||||
{showFilters && (draggableId != null || !isEmpty(dataProvider)) && (
|
||||
<div data-test-subj="hover-actions-add-timeline">
|
||||
{getAddToTimelineButton({
|
||||
dataProvider,
|
||||
draggableId,
|
||||
field,
|
||||
keyboardEvent: stKeyboardEvent,
|
||||
ownFocus,
|
||||
showTooltip: true,
|
||||
value: values,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{allowTopN({
|
||||
browserField: getAllFieldsByName(browserFields)[field],
|
||||
|
@ -342,18 +295,20 @@ export const HoverActions: React.FC<Props> = React.memo(
|
|||
value={values}
|
||||
/>
|
||||
)}
|
||||
{showFilters && (
|
||||
<CopyButton
|
||||
data-test-subj="hover-actions-copy-button"
|
||||
field={field}
|
||||
isHoverAction
|
||||
ownFocus={ownFocus}
|
||||
showTooltip
|
||||
value={values}
|
||||
/>
|
||||
{field != null && (
|
||||
<div data-test-subj="hover-actions-copy-button">
|
||||
{getCopyButton({
|
||||
field,
|
||||
isHoverAction: true,
|
||||
keyboardEvent: stKeyboardEvent,
|
||||
ownFocus,
|
||||
showTooltip: true,
|
||||
value: values,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</EuiFocusTrap>
|
||||
</StyledHoverActionsContainer>
|
||||
</StyledHoverActionsContainer>
|
||||
</EuiFocusTrap>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState, useRef } from 'react';
|
||||
import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd';
|
||||
import { HoverActions } from '.';
|
||||
|
||||
import { DataProvider } from '../../../../common/types';
|
||||
import { ProviderContentWrapper } from '../drag_and_drop/draggable_wrapper';
|
||||
import { getDraggableId } from '../drag_and_drop/helpers';
|
||||
import { useGetTimelineId } from '../drag_and_drop/use_get_timeline_id_from_dom';
|
||||
|
||||
const draggableContainsLinks = (draggableElement: HTMLDivElement | null) => {
|
||||
const links = draggableElement?.querySelectorAll('.euiLink') ?? [];
|
||||
return links.length > 0;
|
||||
};
|
||||
|
||||
type RenderFunctionProp = (
|
||||
props: DataProvider,
|
||||
provided: DraggableProvided | null,
|
||||
state: DraggableStateSnapshot
|
||||
) => React.ReactNode;
|
||||
|
||||
interface Props {
|
||||
dataProvider: DataProvider;
|
||||
disabled?: boolean;
|
||||
isDraggable?: boolean;
|
||||
inline?: boolean;
|
||||
render: RenderFunctionProp;
|
||||
timelineId?: string;
|
||||
truncate?: boolean;
|
||||
onFilterAdded?: () => void;
|
||||
}
|
||||
|
||||
export const useHoverActions = ({
|
||||
dataProvider,
|
||||
isDraggable,
|
||||
onFilterAdded,
|
||||
render,
|
||||
timelineId,
|
||||
}: Props) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const keyboardHandlerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
|
||||
const [showTopN, setShowTopN] = useState<boolean>(false);
|
||||
const [hoverActionsOwnFocus, setHoverActionsOwnFocus] = useState<boolean>(false);
|
||||
const [goGetTimelineId, setGoGetTimelineId] = useState(false);
|
||||
const timelineIdFind = useGetTimelineId(containerRef, goGetTimelineId);
|
||||
|
||||
const handleClosePopOverTrigger = useCallback(() => {
|
||||
setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
|
||||
setHoverActionsOwnFocus((prevHoverActionsOwnFocus) => {
|
||||
if (prevHoverActionsOwnFocus) {
|
||||
setTimeout(() => {
|
||||
keyboardHandlerRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
return false; // always give up ownership
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setHoverActionsOwnFocus(false);
|
||||
}, 0); // invoked on the next tick, because we want to restore focus first
|
||||
}, [keyboardHandlerRef]);
|
||||
|
||||
const toggleTopN = useCallback(() => {
|
||||
setShowTopN((prevShowTopN) => {
|
||||
const newShowTopN = !prevShowTopN;
|
||||
if (newShowTopN === false) {
|
||||
handleClosePopOverTrigger();
|
||||
}
|
||||
return newShowTopN;
|
||||
});
|
||||
}, [handleClosePopOverTrigger]);
|
||||
|
||||
const hoverContent = useMemo(() => {
|
||||
// display links as additional content in the hover menu to enable keyboard
|
||||
// navigation of links (when the draggable contains them):
|
||||
const additionalContent =
|
||||
hoverActionsOwnFocus && !showTopN && draggableContainsLinks(containerRef.current) ? (
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`draggable-link-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, null, { isDragging: false, isDropAnimating: false })}
|
||||
</ProviderContentWrapper>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<HoverActions
|
||||
additionalContent={additionalContent}
|
||||
closePopOver={handleClosePopOverTrigger}
|
||||
dataProvider={dataProvider}
|
||||
draggableId={isDraggable ? getDraggableId(dataProvider.id) : undefined}
|
||||
field={dataProvider.queryMatch.field}
|
||||
isObjectArray={false}
|
||||
goGetTimelineId={setGoGetTimelineId}
|
||||
onFilterAdded={onFilterAdded}
|
||||
ownFocus={hoverActionsOwnFocus}
|
||||
showOwnFocus={false}
|
||||
showTopN={showTopN}
|
||||
timelineId={timelineId ?? timelineIdFind}
|
||||
toggleTopN={toggleTopN}
|
||||
values={
|
||||
typeof dataProvider.queryMatch.value !== 'number'
|
||||
? dataProvider.queryMatch.value
|
||||
: `${dataProvider.queryMatch.value}`
|
||||
}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
dataProvider,
|
||||
handleClosePopOverTrigger,
|
||||
hoverActionsOwnFocus,
|
||||
isDraggable,
|
||||
onFilterAdded,
|
||||
render,
|
||||
showTopN,
|
||||
timelineId,
|
||||
timelineIdFind,
|
||||
toggleTopN,
|
||||
]);
|
||||
|
||||
const setContainerRef = useCallback((e: HTMLDivElement) => {
|
||||
containerRef.current = e;
|
||||
}, []);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
if (!hoverActionsOwnFocus) {
|
||||
keyboardHandlerRef.current?.focus();
|
||||
}
|
||||
}, [hoverActionsOwnFocus, keyboardHandlerRef]);
|
||||
|
||||
const onCloseRequested = useCallback(() => {
|
||||
setShowTopN(false);
|
||||
|
||||
if (hoverActionsOwnFocus) {
|
||||
setHoverActionsOwnFocus(false);
|
||||
|
||||
setTimeout(() => {
|
||||
onFocus(); // return focus to this draggable on the next tick, because we owned focus
|
||||
}, 0);
|
||||
}
|
||||
}, [onFocus, hoverActionsOwnFocus]);
|
||||
|
||||
const openPopover = useCallback(() => {
|
||||
setHoverActionsOwnFocus(true);
|
||||
}, []);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
closePopOverTrigger,
|
||||
handleClosePopOverTrigger,
|
||||
hoverActionsOwnFocus,
|
||||
hoverContent,
|
||||
keyboardHandlerRef,
|
||||
onCloseRequested,
|
||||
onFocus,
|
||||
openPopover,
|
||||
setContainerRef,
|
||||
showTopN,
|
||||
}),
|
||||
[
|
||||
closePopOverTrigger,
|
||||
handleClosePopOverTrigger,
|
||||
hoverActionsOwnFocus,
|
||||
hoverContent,
|
||||
onCloseRequested,
|
||||
onFocus,
|
||||
openPopover,
|
||||
setContainerRef,
|
||||
showTopN,
|
||||
]
|
||||
);
|
||||
};
|
|
@ -70,67 +70,6 @@ describe('get_anomalies_host_table_columns', () => {
|
|||
expect(columns.some((col) => col.name === i18n.HOST_NAME)).toEqual(false);
|
||||
});
|
||||
|
||||
test('on host page, we should escape the draggable id', () => {
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const column = columns.find((col) => col.name === i18n.SCORE) as Columns<
|
||||
string,
|
||||
AnomaliesByHost
|
||||
>;
|
||||
const anomaly: AnomaliesByHost = {
|
||||
hostName: 'host.name',
|
||||
anomaly: {
|
||||
detectorIndex: 0,
|
||||
entityName: 'entity-name-1',
|
||||
entityValue: 'entity-value-1',
|
||||
influencers: [],
|
||||
jobId: 'job-1',
|
||||
rowId: 'row-1',
|
||||
severity: 100,
|
||||
time: new Date('01/01/2000').valueOf(),
|
||||
source: {
|
||||
job_id: 'job-1',
|
||||
result_type: 'result-1',
|
||||
probability: 50,
|
||||
multi_bucket_impact: 0,
|
||||
record_score: 0,
|
||||
initial_record_score: 0,
|
||||
bucket_span: 0,
|
||||
detector_index: 0,
|
||||
is_interim: true,
|
||||
timestamp: new Date('01/01/2000').valueOf(),
|
||||
by_field_name: 'some field name',
|
||||
by_field_value: 'some field value',
|
||||
partition_field_name: 'partition field name',
|
||||
partition_field_value: 'partition field value',
|
||||
function: 'function-1',
|
||||
function_description: 'description-1',
|
||||
typical: [5, 3],
|
||||
actual: [7, 4],
|
||||
influencers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
if (column != null && column.render != null) {
|
||||
const wrapper = mount(<TestProviders>{column.render('', anomaly)}</TestProviders>);
|
||||
expect(
|
||||
wrapper
|
||||
.find(
|
||||
'[draggableId="draggableId.content.anomalies-host-table-severity-host_name-entity-name-1-entity-value-1-100-job-1"]'
|
||||
)
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
} else {
|
||||
expect(column).not.toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
test('on host page, undefined influencers should turn into an empty column string', () => {
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.page,
|
||||
|
|
|
@ -43,62 +43,6 @@ describe('get_anomalies_network_table_columns', () => {
|
|||
expect(columns.some((col) => col.name === i18n.NETWORK_NAME)).toEqual(false);
|
||||
});
|
||||
|
||||
test('on network page, we should escape the draggable id', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
|
||||
const column = columns.find((col) => col.name === i18n.SCORE) as Columns<
|
||||
string,
|
||||
AnomaliesByNetwork
|
||||
>;
|
||||
const anomaly: AnomaliesByNetwork = {
|
||||
type: 'source.ip',
|
||||
ip: '127.0.0.1',
|
||||
anomaly: {
|
||||
detectorIndex: 0,
|
||||
entityName: 'entity-name-1',
|
||||
entityValue: 'entity-value-1',
|
||||
influencers: [],
|
||||
jobId: 'job-1',
|
||||
rowId: 'row-1',
|
||||
severity: 100,
|
||||
time: new Date('01/01/2000').valueOf(),
|
||||
source: {
|
||||
job_id: 'job-1',
|
||||
result_type: 'result-1',
|
||||
probability: 50,
|
||||
multi_bucket_impact: 0,
|
||||
record_score: 0,
|
||||
initial_record_score: 0,
|
||||
bucket_span: 0,
|
||||
detector_index: 0,
|
||||
is_interim: true,
|
||||
timestamp: new Date('01/01/2000').valueOf(),
|
||||
by_field_name: 'some field name',
|
||||
by_field_value: 'some field value',
|
||||
partition_field_name: 'partition field name',
|
||||
partition_field_value: 'partition field value',
|
||||
function: 'function-1',
|
||||
function_description: 'description-1',
|
||||
typical: [5, 3],
|
||||
actual: [7, 4],
|
||||
influencers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
if (column != null && column.render != null) {
|
||||
const wrapper = mount(<TestProviders>{column.render('', anomaly)}</TestProviders>);
|
||||
expect(
|
||||
wrapper
|
||||
.find(
|
||||
'[draggableId="draggableId.content.anomalies-network-table-severity-127_0_0_1-entity-name-1-entity-value-1-100-job-1"]'
|
||||
)
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
} else {
|
||||
expect(column).not.toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
test('on network page, undefined influencers should turn into an empty column string', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate);
|
||||
const column = columns.find((col) => col.name === i18n.INFLUENCED_BY) as Columns<
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('Table Helpers', () => {
|
|||
displayCount: 0,
|
||||
});
|
||||
const wrapper = mount(<TestProviders>{rowItem}</TestProviders>);
|
||||
expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
|
||||
expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
|
||||
'(Empty String)'
|
||||
);
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ describe('Table Helpers', () => {
|
|||
render: renderer,
|
||||
});
|
||||
const wrapper = mount(<TestProviders>{rowItem}</TestProviders>);
|
||||
expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
|
||||
expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
|
||||
'Hi item1 renderer'
|
||||
);
|
||||
});
|
||||
|
@ -116,7 +116,7 @@ describe('Table Helpers', () => {
|
|||
idPrefix: 'idPrefix',
|
||||
});
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
|
||||
expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
|
||||
'(Empty String)'
|
||||
);
|
||||
});
|
||||
|
@ -163,7 +163,7 @@ describe('Table Helpers', () => {
|
|||
displayCount: 2,
|
||||
});
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.find('[data-test-subj="draggableWrapperDiv"]').hostNodes().length).toBe(2);
|
||||
expect(wrapper.find('[data-test-subj="withHoverActionsButton"]').hostNodes().length).toBe(2);
|
||||
});
|
||||
|
||||
test('it uses custom renderer', () => {
|
||||
|
@ -175,7 +175,7 @@ describe('Table Helpers', () => {
|
|||
render: renderer,
|
||||
});
|
||||
const wrapper = mount(<TestProviders>{rowItems}</TestProviders>);
|
||||
expect(wrapper.find('[data-test-subj="draggable-content-attrName"]').first().text()).toBe(
|
||||
expect(wrapper.find('[data-test-subj="render-content-attrName"]').first().text()).toBe(
|
||||
'Hi item1 renderer'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -60,13 +60,15 @@ export const DirectionBadge = React.memo<{
|
|||
contextId: string;
|
||||
direction?: string | null;
|
||||
eventId: string;
|
||||
}>(({ contextId, eventId, direction }) => (
|
||||
isDraggable?: boolean;
|
||||
}>(({ contextId, eventId, direction, isDraggable }) => (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-direction"
|
||||
eventId={eventId}
|
||||
field={NETWORK_DIRECTION_FIELD_NAME}
|
||||
iconType={getDirectionIcon(direction)}
|
||||
isDraggable={isDraggable}
|
||||
value={direction}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -22,13 +22,15 @@ export const Ip = React.memo<{
|
|||
contextId: string;
|
||||
eventId: string;
|
||||
fieldName: string;
|
||||
isDraggable?: boolean;
|
||||
value?: string | null;
|
||||
}>(({ contextId, eventId, fieldName, value }) => (
|
||||
}>(({ contextId, eventId, fieldName, isDraggable, value }) => (
|
||||
<FormattedFieldValue
|
||||
contextId={contextId}
|
||||
data-test-subj="formatted-ip"
|
||||
eventId={eventId}
|
||||
fieldName={fieldName}
|
||||
isDraggable={isDraggable}
|
||||
fieldType={IP_FIELD_TYPE}
|
||||
value={value}
|
||||
truncate
|
||||
|
|
|
@ -5,6 +5,7 @@ exports[`Port renders correctly against snapshot 1`] = `
|
|||
data-test-subj="port"
|
||||
field="destination.port"
|
||||
id="port-default-draggable-test-abcd-destination.port-443"
|
||||
isDraggable={true}
|
||||
tooltipContent="destination.port"
|
||||
value="443"
|
||||
>
|
||||
|
|
|
@ -29,7 +29,7 @@ export const Port = React.memo<{
|
|||
contextId: string;
|
||||
eventId: string;
|
||||
fieldName: string;
|
||||
isDraggable: boolean;
|
||||
isDraggable?: boolean;
|
||||
value: string | undefined | null;
|
||||
}>(({ contextId, eventId, fieldName, isDraggable, value }) =>
|
||||
isDraggable ? (
|
||||
|
@ -37,6 +37,7 @@ export const Port = React.memo<{
|
|||
data-test-subj="port"
|
||||
field={fieldName}
|
||||
id={`port-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={fieldName}
|
||||
value={value}
|
||||
>
|
||||
|
|
|
@ -73,8 +73,9 @@ const GeoFieldValues = React.memo<{
|
|||
contextId: string;
|
||||
eventId: string;
|
||||
fieldName: string;
|
||||
isDraggable?: boolean;
|
||||
values?: string[] | null;
|
||||
}>(({ contextId, eventId, fieldName, values }) =>
|
||||
}>(({ contextId, eventId, fieldName, isDraggable, values }) =>
|
||||
values != null ? (
|
||||
<>
|
||||
{uniq(values).map((value) => (
|
||||
|
@ -92,6 +93,7 @@ const GeoFieldValues = React.memo<{
|
|||
data-test-subj={fieldName}
|
||||
field={fieldName}
|
||||
id={`geo-field-values-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={fieldName}
|
||||
value={value}
|
||||
/>
|
||||
|
@ -114,7 +116,7 @@ GeoFieldValues.displayName = 'GeoFieldValues';
|
|||
* - `source|destination.geo.city_name`
|
||||
*/
|
||||
export const GeoFields = React.memo<GeoFieldsProps>((props) => {
|
||||
const { contextId, eventId, type } = props;
|
||||
const { contextId, eventId, isDraggable, type } = props;
|
||||
|
||||
const propNameToFieldName = getGeoFieldPropNameToFieldNameMap(type);
|
||||
return (
|
||||
|
@ -124,6 +126,7 @@ export const GeoFields = React.memo<GeoFieldsProps>((props) => {
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
fieldName={geo.fieldName}
|
||||
isDraggable={isDraggable}
|
||||
key={geo.fieldName}
|
||||
values={get(geo.prop, props)}
|
||||
/>
|
||||
|
|
|
@ -36,6 +36,7 @@ export const SourceDestination = React.memo<SourceDestinationProps>(
|
|||
destinationPackets,
|
||||
destinationPort,
|
||||
eventId,
|
||||
isDraggable,
|
||||
networkBytes,
|
||||
networkCommunityId,
|
||||
networkDirection,
|
||||
|
@ -59,8 +60,9 @@ export const SourceDestination = React.memo<SourceDestinationProps>(
|
|||
packets={networkPackets}
|
||||
communityId={networkCommunityId}
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
direction={networkDirection}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
protocol={networkProtocol}
|
||||
transport={transport}
|
||||
/>
|
||||
|
@ -79,6 +81,7 @@ export const SourceDestination = React.memo<SourceDestinationProps>(
|
|||
destinationPackets={destinationPackets}
|
||||
destinationPort={destinationPort}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceBytes={sourceBytes}
|
||||
sourceGeoContinentName={sourceGeoContinentName}
|
||||
sourceGeoCountryName={sourceGeoCountryName}
|
||||
|
|
|
@ -25,9 +25,10 @@ IpPortSeparator.displayName = 'IpPortSeparator';
|
|||
const PortWithSeparator = React.memo<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
port?: string | null;
|
||||
portFieldName: string;
|
||||
}>(({ contextId, eventId, port, portFieldName }) => {
|
||||
}>(({ contextId, eventId, isDraggable, port, portFieldName }) => {
|
||||
return port != null ? (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -39,7 +40,7 @@ const PortWithSeparator = React.memo<{
|
|||
data-test-subj="port"
|
||||
eventId={eventId}
|
||||
fieldName={portFieldName}
|
||||
isDraggable={true}
|
||||
isDraggable={isDraggable}
|
||||
value={port}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -58,9 +59,10 @@ export const IpWithPort = React.memo<{
|
|||
eventId: string;
|
||||
ip?: string | null;
|
||||
ipFieldName: string;
|
||||
isDraggable?: boolean;
|
||||
port?: string | null;
|
||||
portFieldName: string;
|
||||
}>(({ contextId, eventId, ip, ipFieldName, port, portFieldName }) => (
|
||||
}>(({ contextId, eventId, ip, ipFieldName, isDraggable, port, portFieldName }) => (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<Ip
|
||||
|
@ -68,6 +70,7 @@ export const IpWithPort = React.memo<{
|
|||
data-test-subj="ip"
|
||||
eventId={eventId}
|
||||
fieldName={ipFieldName}
|
||||
isDraggable={isDraggable}
|
||||
value={ip}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -75,6 +78,7 @@ export const IpWithPort = React.memo<{
|
|||
<PortWithSeparator
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
port={port}
|
||||
portFieldName={portFieldName}
|
||||
/>
|
||||
|
|
|
@ -45,97 +45,120 @@ export const Network = React.memo<{
|
|||
contextId: string;
|
||||
direction?: string[] | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
packets?: string[] | null;
|
||||
protocol?: string[] | null;
|
||||
transport?: string[] | null;
|
||||
}>(({ bytes, communityId, contextId, direction, eventId, packets, protocol, transport }) => (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" gutterSize="none">
|
||||
{direction != null
|
||||
? uniq(direction).map((dir) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={dir}>
|
||||
<DirectionBadge contextId={contextId} eventId={eventId} direction={dir} />
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
}>(
|
||||
({
|
||||
bytes,
|
||||
communityId,
|
||||
contextId,
|
||||
direction,
|
||||
eventId,
|
||||
isDraggable,
|
||||
packets,
|
||||
protocol,
|
||||
transport,
|
||||
}) => (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" gutterSize="none">
|
||||
{direction != null
|
||||
? uniq(direction).map((dir) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={dir}>
|
||||
<DirectionBadge
|
||||
contextId={contextId}
|
||||
direction={dir}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
|
||||
{protocol != null
|
||||
? uniq(protocol).map((proto) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={proto}>
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-protocol"
|
||||
eventId={eventId}
|
||||
field={NETWORK_PROTOCOL_FIELD_NAME}
|
||||
value={proto}
|
||||
/>
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
{protocol != null
|
||||
? uniq(protocol).map((proto) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={proto}>
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-protocol"
|
||||
eventId={eventId}
|
||||
field={NETWORK_PROTOCOL_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={proto}
|
||||
/>
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
|
||||
{bytes != null
|
||||
? uniq(bytes).map((b) =>
|
||||
!isNaN(Number(b)) ? (
|
||||
<EuiFlexItemMarginRight grow={false} key={b}>
|
||||
{bytes != null
|
||||
? uniq(bytes).map((b) =>
|
||||
!isNaN(Number(b)) ? (
|
||||
<EuiFlexItemMarginRight grow={false} key={b}>
|
||||
<DefaultDraggable
|
||||
field={NETWORK_BYTES_FIELD_NAME}
|
||||
id={`network-default-draggable-${contextId}-${eventId}-${NETWORK_BYTES_FIELD_NAME}-${b}`}
|
||||
isDraggable={isDraggable}
|
||||
value={b}
|
||||
>
|
||||
<Stats size="xs">
|
||||
<span data-test-subj="network-bytes">
|
||||
<PreferenceFormattedBytes value={b} />
|
||||
</span>
|
||||
</Stats>
|
||||
</DefaultDraggable>
|
||||
</EuiFlexItemMarginRight>
|
||||
) : null
|
||||
)
|
||||
: null}
|
||||
|
||||
{packets != null
|
||||
? uniq(packets).map((p) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={p}>
|
||||
<DefaultDraggable
|
||||
field={NETWORK_BYTES_FIELD_NAME}
|
||||
id={`network-default-draggable-${contextId}-${eventId}-${NETWORK_BYTES_FIELD_NAME}-${b}`}
|
||||
value={b}
|
||||
field={NETWORK_PACKETS_FIELD_NAME}
|
||||
id={`network-default-draggable-${contextId}-${eventId}-${NETWORK_PACKETS_FIELD_NAME}-${p}`}
|
||||
isDraggable={isDraggable}
|
||||
value={p}
|
||||
>
|
||||
<Stats size="xs">
|
||||
<span data-test-subj="network-bytes">
|
||||
<PreferenceFormattedBytes value={b} />
|
||||
</span>
|
||||
<span data-test-subj="network-packets">{`${p} ${i18n.PACKETS}`}</span>
|
||||
</Stats>
|
||||
</DefaultDraggable>
|
||||
</EuiFlexItemMarginRight>
|
||||
) : null
|
||||
)
|
||||
: null}
|
||||
))
|
||||
: null}
|
||||
|
||||
{packets != null
|
||||
? uniq(packets).map((p) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={p}>
|
||||
<DefaultDraggable
|
||||
field={NETWORK_PACKETS_FIELD_NAME}
|
||||
id={`network-default-draggable-${contextId}-${eventId}-${NETWORK_PACKETS_FIELD_NAME}-${p}`}
|
||||
value={p}
|
||||
>
|
||||
<Stats size="xs">
|
||||
<span data-test-subj="network-packets">{`${p} ${i18n.PACKETS}`}</span>
|
||||
</Stats>
|
||||
</DefaultDraggable>
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
{transport != null
|
||||
? uniq(transport).map((trans) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={trans}>
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-transport"
|
||||
eventId={eventId}
|
||||
field={NETWORK_TRANSPORT_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={trans}
|
||||
/>
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
|
||||
{transport != null
|
||||
? uniq(transport).map((trans) => (
|
||||
<EuiFlexItemMarginRight grow={false} key={trans}>
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-transport"
|
||||
eventId={eventId}
|
||||
field={NETWORK_TRANSPORT_FIELD_NAME}
|
||||
value={trans}
|
||||
/>
|
||||
</EuiFlexItemMarginRight>
|
||||
))
|
||||
: null}
|
||||
|
||||
{communityId != null
|
||||
? uniq(communityId).map((trans) => (
|
||||
<EuiFlexItem grow={false} key={trans}>
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-community-id"
|
||||
eventId={eventId}
|
||||
field={NETWORK_COMMUNITY_ID_FIELD_NAME}
|
||||
value={trans}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))
|
||||
: null}
|
||||
</EuiFlexGroup>
|
||||
));
|
||||
{communityId != null
|
||||
? uniq(communityId).map((trans) => (
|
||||
<EuiFlexItem grow={false} key={trans}>
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="network-community-id"
|
||||
eventId={eventId}
|
||||
field={NETWORK_COMMUNITY_ID_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={trans}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))
|
||||
: null}
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
);
|
||||
|
||||
Network.displayName = 'Network';
|
||||
|
|
|
@ -56,10 +56,11 @@ Data.displayName = 'Data';
|
|||
const SourceArrow = React.memo<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceBytes: string | undefined;
|
||||
sourceBytesPercent: number | undefined;
|
||||
sourcePackets: string | undefined;
|
||||
}>(({ contextId, eventId, sourceBytes, sourceBytesPercent, sourcePackets }) => {
|
||||
}>(({ contextId, eventId, isDraggable, sourceBytes, sourceBytesPercent, sourcePackets }) => {
|
||||
const sourceArrowHeight =
|
||||
sourceBytesPercent != null
|
||||
? getArrowHeightFromPercent(sourceBytesPercent)
|
||||
|
@ -76,6 +77,7 @@ const SourceArrow = React.memo<{
|
|||
<DefaultDraggable
|
||||
field={SOURCE_BYTES_FIELD_NAME}
|
||||
id={`source-arrow-default-draggable-${contextId}-${eventId}-${SOURCE_BYTES_FIELD_NAME}-${sourceBytes}`}
|
||||
isDraggable={isDraggable}
|
||||
value={sourceBytes}
|
||||
>
|
||||
<Data size="xs">
|
||||
|
@ -101,6 +103,7 @@ const SourceArrow = React.memo<{
|
|||
<DefaultDraggable
|
||||
field={SOURCE_PACKETS_FIELD_NAME}
|
||||
id={`source-arrow-default-draggable-${contextId}-${eventId}-${SOURCE_PACKETS_FIELD_NAME}-${sourcePackets}`}
|
||||
isDraggable={isDraggable}
|
||||
value={sourcePackets}
|
||||
>
|
||||
<Data size="xs">
|
||||
|
@ -129,73 +132,85 @@ SourceArrow.displayName = 'SourceArrow';
|
|||
*/
|
||||
const DestinationArrow = React.memo<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
destinationBytes: string | undefined;
|
||||
destinationBytesPercent: number | undefined;
|
||||
destinationPackets: string | undefined;
|
||||
}>(({ contextId, eventId, destinationBytes, destinationBytesPercent, destinationPackets }) => {
|
||||
const destinationArrowHeight =
|
||||
destinationBytesPercent != null
|
||||
? getArrowHeightFromPercent(destinationBytesPercent)
|
||||
: DEFAULT_ARROW_HEIGHT;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
}>(
|
||||
({
|
||||
contextId,
|
||||
destinationBytes,
|
||||
destinationBytesPercent,
|
||||
destinationPackets,
|
||||
eventId,
|
||||
isDraggable,
|
||||
}) => {
|
||||
const destinationArrowHeight =
|
||||
destinationBytesPercent != null
|
||||
? getArrowHeightFromPercent(destinationBytesPercent)
|
||||
: DEFAULT_ARROW_HEIGHT;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ArrowHead direction="arrowLeft" />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ArrowBody height={destinationArrowHeight} />
|
||||
</EuiFlexItem>
|
||||
|
||||
{destinationBytes != null && !isNaN(Number(destinationBytes)) ? (
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<DefaultDraggable
|
||||
field={DESTINATION_BYTES_FIELD_NAME}
|
||||
id={`destination-arrow-default-draggable-${contextId}-${eventId}-${DESTINATION_BYTES_FIELD_NAME}-${destinationBytes}`}
|
||||
value={destinationBytes}
|
||||
>
|
||||
<Data size="xs">
|
||||
{destinationBytesPercent != null ? (
|
||||
<Percent data-test-subj="destination-bytes-percent">
|
||||
{`(${numeral(destinationBytesPercent).format('0.00')}%)`}
|
||||
</Percent>
|
||||
) : null}
|
||||
<span data-test-subj="destination-bytes">
|
||||
<PreferenceFormattedBytes value={destinationBytes} />
|
||||
</span>
|
||||
</Data>
|
||||
</DefaultDraggable>
|
||||
<ArrowHead direction="arrowLeft" />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ArrowBody height={destinationArrowHeight} />
|
||||
</EuiFlexItem>
|
||||
|
||||
{destinationPackets != null && !isNaN(Number(destinationPackets)) ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DefaultDraggable
|
||||
field={DESTINATION_PACKETS_FIELD_NAME}
|
||||
id={`destination-arrow-default-draggable-${contextId}-${eventId}-${DESTINATION_PACKETS_FIELD_NAME}-${destinationPackets}`}
|
||||
value={destinationPackets}
|
||||
>
|
||||
<Data size="xs">
|
||||
<span data-test-subj="destination-packets">{`${numeral(destinationPackets).format(
|
||||
'0,0'
|
||||
)} ${i18n.PACKETS}`}</span>
|
||||
</Data>
|
||||
</DefaultDraggable>
|
||||
<ArrowBody height={destinationArrowHeight} />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ArrowBody height={destinationArrowHeight} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
{destinationBytes != null && !isNaN(Number(destinationBytes)) ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DefaultDraggable
|
||||
field={DESTINATION_BYTES_FIELD_NAME}
|
||||
id={`destination-arrow-default-draggable-${contextId}-${eventId}-${DESTINATION_BYTES_FIELD_NAME}-${destinationBytes}`}
|
||||
isDraggable={isDraggable}
|
||||
value={destinationBytes}
|
||||
>
|
||||
<Data size="xs">
|
||||
{destinationBytesPercent != null ? (
|
||||
<Percent data-test-subj="destination-bytes-percent">
|
||||
{`(${numeral(destinationBytesPercent).format('0.00')}%)`}
|
||||
</Percent>
|
||||
) : null}
|
||||
<span data-test-subj="destination-bytes">
|
||||
<PreferenceFormattedBytes value={destinationBytes} />
|
||||
</span>
|
||||
</Data>
|
||||
</DefaultDraggable>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ArrowBody height={destinationArrowHeight} />
|
||||
</EuiFlexItem>
|
||||
|
||||
{destinationPackets != null && !isNaN(Number(destinationPackets)) ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DefaultDraggable
|
||||
field={DESTINATION_PACKETS_FIELD_NAME}
|
||||
id={`destination-arrow-default-draggable-${contextId}-${eventId}-${DESTINATION_PACKETS_FIELD_NAME}-${destinationPackets}`}
|
||||
isDraggable={isDraggable}
|
||||
value={destinationPackets}
|
||||
>
|
||||
<Data size="xs">
|
||||
<span data-test-subj="destination-packets">{`${numeral(destinationPackets).format(
|
||||
'0,0'
|
||||
)} ${i18n.PACKETS}`}</span>
|
||||
</Data>
|
||||
</DefaultDraggable>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ArrowBody height={destinationArrowHeight} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DestinationArrow.displayName = 'DestinationArrow';
|
||||
|
||||
|
@ -208,67 +223,79 @@ export const SourceDestinationArrows = React.memo<{
|
|||
destinationBytes?: string[] | null;
|
||||
destinationPackets?: string[] | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceBytes?: string[] | null;
|
||||
sourcePackets?: string[] | null;
|
||||
}>(({ contextId, destinationBytes, destinationPackets, eventId, sourceBytes, sourcePackets }) => {
|
||||
const maybeSourceBytes =
|
||||
sourceBytes != null && hasOneValue(sourceBytes) ? sourceBytes[0] : undefined;
|
||||
}>(
|
||||
({
|
||||
contextId,
|
||||
destinationBytes,
|
||||
destinationPackets,
|
||||
eventId,
|
||||
isDraggable,
|
||||
sourceBytes,
|
||||
sourcePackets,
|
||||
}) => {
|
||||
const maybeSourceBytes =
|
||||
sourceBytes != null && hasOneValue(sourceBytes) ? sourceBytes[0] : undefined;
|
||||
|
||||
const maybeSourcePackets =
|
||||
sourcePackets != null && hasOneValue(sourcePackets) ? sourcePackets[0] : undefined;
|
||||
const maybeSourcePackets =
|
||||
sourcePackets != null && hasOneValue(sourcePackets) ? sourcePackets[0] : undefined;
|
||||
|
||||
const maybeDestinationBytes =
|
||||
destinationBytes != null && hasOneValue(destinationBytes) ? destinationBytes[0] : undefined;
|
||||
const maybeDestinationBytes =
|
||||
destinationBytes != null && hasOneValue(destinationBytes) ? destinationBytes[0] : undefined;
|
||||
|
||||
const maybeDestinationPackets =
|
||||
destinationPackets != null && hasOneValue(destinationPackets)
|
||||
? destinationPackets[0]
|
||||
: undefined;
|
||||
const maybeDestinationPackets =
|
||||
destinationPackets != null && hasOneValue(destinationPackets)
|
||||
? destinationPackets[0]
|
||||
: undefined;
|
||||
|
||||
const maybeSourceBytesPercent =
|
||||
maybeSourceBytes != null && maybeDestinationBytes != null
|
||||
? getPercent({
|
||||
numerator: Number(maybeSourceBytes),
|
||||
denominator: Number(maybeSourceBytes) + Number(maybeDestinationBytes),
|
||||
})
|
||||
: undefined;
|
||||
const maybeSourceBytesPercent =
|
||||
maybeSourceBytes != null && maybeDestinationBytes != null
|
||||
? getPercent({
|
||||
numerator: Number(maybeSourceBytes),
|
||||
denominator: Number(maybeSourceBytes) + Number(maybeDestinationBytes),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const maybeDestinationBytesPercent =
|
||||
maybeSourceBytesPercent != null ? 100 - maybeSourceBytesPercent : undefined;
|
||||
const maybeDestinationBytesPercent =
|
||||
maybeSourceBytesPercent != null ? 100 - maybeSourceBytesPercent : undefined;
|
||||
|
||||
return (
|
||||
<SourceDestinationArrowsContainer
|
||||
alignItems="center"
|
||||
data-test-subj="source-destination-arrows-container"
|
||||
justifyContent="center"
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
>
|
||||
{maybeSourceBytes != null ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<SourceArrow
|
||||
contextId={contextId}
|
||||
sourceBytes={maybeSourceBytes}
|
||||
sourcePackets={maybeSourcePackets}
|
||||
sourceBytesPercent={maybeSourceBytesPercent}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
{maybeDestinationBytes != null ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DestinationArrow
|
||||
contextId={contextId}
|
||||
destinationBytes={maybeDestinationBytes}
|
||||
destinationPackets={maybeDestinationPackets}
|
||||
destinationBytesPercent={maybeDestinationBytesPercent}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</SourceDestinationArrowsContainer>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<SourceDestinationArrowsContainer
|
||||
alignItems="center"
|
||||
data-test-subj="source-destination-arrows-container"
|
||||
justifyContent="center"
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
>
|
||||
{maybeSourceBytes != null ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<SourceArrow
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceBytes={maybeSourceBytes}
|
||||
sourcePackets={maybeSourcePackets}
|
||||
sourceBytesPercent={maybeSourceBytesPercent}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
{maybeDestinationBytes != null ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DestinationArrow
|
||||
contextId={contextId}
|
||||
destinationBytes={maybeDestinationBytes}
|
||||
destinationPackets={maybeDestinationPackets}
|
||||
destinationBytesPercent={maybeDestinationBytesPercent}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</SourceDestinationArrowsContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SourceDestinationArrows.displayName = 'SourceDestinationArrows';
|
||||
|
|
|
@ -958,6 +958,7 @@ describe('SourceDestinationIp', () => {
|
|||
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
|
||||
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
|
||||
isDraggable={true}
|
||||
sourceGeoContinentName={asArrayIfExists(
|
||||
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
|
||||
)}
|
||||
|
@ -979,7 +980,6 @@ describe('SourceDestinationIp', () => {
|
|||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
removeExternalLinkText(
|
||||
wrapper.find('[data-test-subj="draggable-content-source.port"]').first().text()
|
||||
|
@ -1011,6 +1011,7 @@ describe('SourceDestinationIp', () => {
|
|||
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
|
||||
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
|
||||
isDraggable={true}
|
||||
sourceGeoContinentName={asArrayIfExists(
|
||||
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
|
||||
)}
|
||||
|
@ -1064,6 +1065,7 @@ describe('SourceDestinationIp', () => {
|
|||
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
|
||||
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
|
||||
isDraggable={true}
|
||||
sourceGeoContinentName={asArrayIfExists(
|
||||
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
|
||||
)}
|
||||
|
@ -1118,6 +1120,7 @@ describe('SourceDestinationIp', () => {
|
|||
destinationIp={undefined}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
|
||||
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
|
||||
isDraggable={true}
|
||||
sourceGeoContinentName={asArrayIfExists(
|
||||
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
|
||||
)}
|
||||
|
@ -1271,6 +1274,7 @@ describe('SourceDestinationIp', () => {
|
|||
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, getMockNetflowData()))}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, getMockNetflowData()))}
|
||||
eventId={get(ID_FIELD_NAME, getMockNetflowData())}
|
||||
isDraggable={true}
|
||||
sourceGeoContinentName={asArrayIfExists(
|
||||
get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, getMockNetflowData())
|
||||
)}
|
||||
|
|
|
@ -88,54 +88,67 @@ const IpAdressesWithPorts = React.memo<{
|
|||
destinationIp?: string[] | null;
|
||||
destinationPort?: Array<number | string | null> | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceIp?: string[] | null;
|
||||
sourcePort?: Array<number | string | null> | null;
|
||||
type: SourceDestinationType;
|
||||
}>(({ contextId, destinationIp, destinationPort, eventId, sourceIp, sourcePort, type }) => {
|
||||
const ip = type === 'source' ? sourceIp : destinationIp;
|
||||
const ipFieldName = type === 'source' ? SOURCE_IP_FIELD_NAME : DESTINATION_IP_FIELD_NAME;
|
||||
const port = type === 'source' ? sourcePort : destinationPort;
|
||||
const portFieldName = type === 'source' ? SOURCE_PORT_FIELD_NAME : DESTINATION_PORT_FIELD_NAME;
|
||||
}>(
|
||||
({
|
||||
contextId,
|
||||
destinationIp,
|
||||
destinationPort,
|
||||
eventId,
|
||||
isDraggable,
|
||||
sourceIp,
|
||||
sourcePort,
|
||||
type,
|
||||
}) => {
|
||||
const ip = type === 'source' ? sourceIp : destinationIp;
|
||||
const ipFieldName = type === 'source' ? SOURCE_IP_FIELD_NAME : DESTINATION_IP_FIELD_NAME;
|
||||
const port = type === 'source' ? sourcePort : destinationPort;
|
||||
const portFieldName = type === 'source' ? SOURCE_PORT_FIELD_NAME : DESTINATION_PORT_FIELD_NAME;
|
||||
|
||||
if (ip == null) {
|
||||
return null; // if ip is not populated as an array, ports will be ignored
|
||||
if (ip == null) {
|
||||
return null; // if ip is not populated as an array, ports will be ignored
|
||||
}
|
||||
|
||||
// IMPORTANT: The ip and port arrays are parallel arrays; the port at
|
||||
// index `i` corresponds with the ip address at index `i`. We must
|
||||
// preserve the relationships between the parallel arrays:
|
||||
const ipPortPairs: IpPortPair[] =
|
||||
port != null && ip.length === port.length
|
||||
? ip.map((address, i) => ({
|
||||
ip: address,
|
||||
port: port[i] != null ? `${port[i]}` : null, // use the corresponding port in the parallel array
|
||||
}))
|
||||
: ip.map((address) => ({
|
||||
ip: address,
|
||||
port: null, // drop the port, because the length of the parallel ip and port arrays is different
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
{uniqWith(deepEqual, ipPortPairs).map(
|
||||
(ipPortPair) =>
|
||||
ipPortPair.ip != null && (
|
||||
<EuiFlexItem grow={false} key={ipPortPair.ip}>
|
||||
<IpWithPort
|
||||
contextId={contextId}
|
||||
data-test-subj={`${type}-ip-and-port`}
|
||||
eventId={eventId}
|
||||
ip={ipPortPair.ip}
|
||||
ipFieldName={ipFieldName}
|
||||
isDraggable={isDraggable}
|
||||
port={ipPortPair.port}
|
||||
portFieldName={portFieldName}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
// IMPORTANT: The ip and port arrays are parallel arrays; the port at
|
||||
// index `i` corresponds with the ip address at index `i`. We must
|
||||
// preserve the relationships between the parallel arrays:
|
||||
const ipPortPairs: IpPortPair[] =
|
||||
port != null && ip.length === port.length
|
||||
? ip.map((address, i) => ({
|
||||
ip: address,
|
||||
port: port[i] != null ? `${port[i]}` : null, // use the corresponding port in the parallel array
|
||||
}))
|
||||
: ip.map((address) => ({
|
||||
ip: address,
|
||||
port: null, // drop the port, because the length of the parallel ip and port arrays is different
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
{uniqWith(deepEqual, ipPortPairs).map(
|
||||
(ipPortPair) =>
|
||||
ipPortPair.ip != null && (
|
||||
<EuiFlexItem grow={false} key={ipPortPair.ip}>
|
||||
<IpWithPort
|
||||
contextId={contextId}
|
||||
data-test-subj={`${type}-ip-and-port`}
|
||||
eventId={eventId}
|
||||
ip={ipPortPair.ip}
|
||||
ipFieldName={ipFieldName}
|
||||
port={ipPortPair.port}
|
||||
portFieldName={portFieldName}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
);
|
||||
|
||||
IpAdressesWithPorts.displayName = 'IpAdressesWithPorts';
|
||||
|
||||
|
@ -159,6 +172,7 @@ export const SourceDestinationIp = React.memo<SourceDestinationIpProps>(
|
|||
destinationIp,
|
||||
destinationPort,
|
||||
eventId,
|
||||
isDraggable,
|
||||
sourceGeoContinentName,
|
||||
sourceGeoCountryName,
|
||||
sourceGeoCountryIsoCode,
|
||||
|
@ -189,6 +203,7 @@ export const SourceDestinationIp = React.memo<SourceDestinationIpProps>(
|
|||
destinationIp={destinationIp}
|
||||
destinationPort={destinationPort}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceIp={sourceIp}
|
||||
sourcePort={sourcePort}
|
||||
type={type}
|
||||
|
@ -202,7 +217,7 @@ export const SourceDestinationIp = React.memo<SourceDestinationIpProps>(
|
|||
data-test-subj="port"
|
||||
eventId={eventId}
|
||||
fieldName={`${type}.port`}
|
||||
isDraggable={true}
|
||||
isDraggable={isDraggable}
|
||||
value={port}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -219,6 +234,7 @@ export const SourceDestinationIp = React.memo<SourceDestinationIpProps>(
|
|||
destinationGeoRegionName={destinationGeoRegionName}
|
||||
destinationGeoCityName={destinationGeoCityName}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceGeoContinentName={sourceGeoContinentName}
|
||||
sourceGeoCountryName={sourceGeoCountryName}
|
||||
sourceGeoCountryIsoCode={sourceGeoCountryIsoCode}
|
||||
|
|
|
@ -32,6 +32,7 @@ export const SourceDestinationWithArrows = React.memo<SourceDestinationWithArrow
|
|||
destinationPackets,
|
||||
destinationPort,
|
||||
eventId,
|
||||
isDraggable,
|
||||
sourceBytes,
|
||||
sourceGeoContinentName,
|
||||
sourceGeoCountryName,
|
||||
|
@ -54,6 +55,7 @@ export const SourceDestinationWithArrows = React.memo<SourceDestinationWithArrow
|
|||
destinationIp={destinationIp}
|
||||
destinationPort={destinationPort}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceGeoContinentName={sourceGeoContinentName}
|
||||
sourceGeoCountryName={sourceGeoCountryName}
|
||||
sourceGeoCountryIsoCode={sourceGeoCountryIsoCode}
|
||||
|
@ -70,6 +72,7 @@ export const SourceDestinationWithArrows = React.memo<SourceDestinationWithArrow
|
|||
destinationBytes={destinationBytes}
|
||||
destinationPackets={destinationPackets}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceBytes={sourceBytes}
|
||||
sourcePackets={sourcePackets}
|
||||
/>
|
||||
|
@ -85,6 +88,7 @@ export const SourceDestinationWithArrows = React.memo<SourceDestinationWithArrow
|
|||
destinationIp={destinationIp}
|
||||
destinationPort={destinationPort}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceGeoContinentName={sourceGeoContinentName}
|
||||
sourceGeoCountryName={sourceGeoCountryName}
|
||||
sourceGeoCountryIsoCode={sourceGeoCountryIsoCode}
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface GeoFieldsProps {
|
|||
destinationGeoRegionName?: string[] | null;
|
||||
destinationGeoCityName?: string[] | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceGeoContinentName?: string[] | null;
|
||||
sourceGeoCountryName?: string[] | null;
|
||||
sourceGeoCountryIsoCode?: string[] | null;
|
||||
|
@ -37,6 +38,7 @@ export interface SourceDestinationProps {
|
|||
destinationPort?: string[] | null;
|
||||
direction?: string[] | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
networkBytes?: string[] | null;
|
||||
networkCommunityId?: string[] | null;
|
||||
networkDirection?: string[] | null;
|
||||
|
@ -63,6 +65,7 @@ export interface SourceDestinationIpProps {
|
|||
destinationIp?: string[] | null;
|
||||
destinationPort?: Array<number | string | null> | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceGeoContinentName?: string[] | null;
|
||||
sourceGeoCountryName?: string[] | null;
|
||||
sourceGeoCountryIsoCode?: string[] | null;
|
||||
|
@ -85,6 +88,7 @@ export interface SourceDestinationWithArrowsProps {
|
|||
destinationPackets?: string[] | null;
|
||||
destinationPort?: string[] | null;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceBytes?: string[] | null;
|
||||
sourceGeoContinentName?: string[] | null;
|
||||
sourceGeoCountryName?: string[] | null;
|
||||
|
|
|
@ -40,8 +40,9 @@ export const CertificateFingerprint = React.memo<{
|
|||
certificateType: CertificateType;
|
||||
contextId: string;
|
||||
fieldName: string;
|
||||
isDraggable?: boolean;
|
||||
value?: string | null;
|
||||
}>(({ eventId, certificateType, contextId, fieldName, value }) => {
|
||||
}>(({ eventId, certificateType, contextId, fieldName, isDraggable, value }) => {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
|
@ -49,6 +50,7 @@ export const CertificateFingerprint = React.memo<{
|
|||
eventId={eventId}
|
||||
field={fieldName}
|
||||
iconType="snowflake"
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={
|
||||
<EuiText size="xs">
|
||||
<span>{fieldName}</span>
|
||||
|
|
|
@ -26,6 +26,7 @@ export const Duration = React.memo<{
|
|||
isDraggable ? (
|
||||
<DefaultDraggable
|
||||
id={`duration-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
// @ts-expect-error
|
||||
name={name}
|
||||
field={fieldName}
|
||||
|
|
|
@ -10,6 +10,7 @@ exports[`Field Renderers #autonomousSystemRenderer it renders correctly against
|
|||
<DefaultDraggable
|
||||
field="source.as.organization.name"
|
||||
id="autonomous-system-renderer-default-draggable-ip-overview-source.as.organization.name"
|
||||
isDraggable={false}
|
||||
value="Test Org"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -24,6 +25,7 @@ exports[`Field Renderers #autonomousSystemRenderer it renders correctly against
|
|||
<DefaultDraggable
|
||||
field="source.as.number"
|
||||
id="autonomous-system-renderer-default-draggable-ip-overview-source.as.number"
|
||||
isDraggable={false}
|
||||
value="12345"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -58,6 +60,7 @@ exports[`Field Renderers #hostIdRenderer it renders correctly against snapshot 1
|
|||
},
|
||||
}
|
||||
}
|
||||
isDraggable={false}
|
||||
render={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
@ -79,6 +82,7 @@ exports[`Field Renderers #hostNameRenderer it renders correctly against snapshot
|
|||
},
|
||||
}
|
||||
}
|
||||
isDraggable={false}
|
||||
render={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
@ -94,6 +98,7 @@ exports[`Field Renderers #locationRenderer it renders correctly against snapshot
|
|||
<DefaultDraggable
|
||||
field="source.geo.city_name"
|
||||
id="location-renderer-default-draggable-ip-overview-source.geo.city_name"
|
||||
isDraggable={false}
|
||||
value={
|
||||
Array [
|
||||
"New York",
|
||||
|
@ -108,6 +113,7 @@ exports[`Field Renderers #locationRenderer it renders correctly against snapshot
|
|||
<DefaultDraggable
|
||||
field="source.geo.region_name"
|
||||
id="location-renderer-default-draggable-ip-overview-source.geo.region_name"
|
||||
isDraggable={false}
|
||||
value={
|
||||
Array [
|
||||
"New York",
|
||||
|
|
|
@ -56,6 +56,7 @@ export const locationRenderer = (
|
|||
id={`location-renderer-default-draggable-${IpOverviewId}-${
|
||||
contextID ? `${contextID}-` : ''
|
||||
}${fieldName}`}
|
||||
isDraggable={false}
|
||||
field={fieldName}
|
||||
value={locationValue}
|
||||
/>
|
||||
|
@ -84,6 +85,7 @@ export const autonomousSystemRenderer = (
|
|||
id={`autonomous-system-renderer-default-draggable-${IpOverviewId}-${
|
||||
contextID ? `${contextID}-` : ''
|
||||
}${flowTarget}.as.organization.name`}
|
||||
isDraggable={false}
|
||||
field={`${flowTarget}.as.organization.name`}
|
||||
value={as.organization.name}
|
||||
/>
|
||||
|
@ -94,6 +96,7 @@ export const autonomousSystemRenderer = (
|
|||
id={`autonomous-system-renderer-default-draggable-${IpOverviewId}-${
|
||||
contextID ? `${contextID}-` : ''
|
||||
}${flowTarget}.as.number`}
|
||||
isDraggable={false}
|
||||
field={`${flowTarget}.as.number`}
|
||||
value={`${as.number}`}
|
||||
/>
|
||||
|
@ -123,6 +126,7 @@ export const hostIdRenderer = ({
|
|||
id={`host-id-renderer-default-draggable-${IpOverviewId}-${
|
||||
contextID ? `${contextID}-` : ''
|
||||
}host-id`}
|
||||
isDraggable={false}
|
||||
field="host.id"
|
||||
value={host.id[0]}
|
||||
>
|
||||
|
@ -154,6 +158,7 @@ export const hostNameRenderer = (
|
|||
id={`host-name-renderer-default-draggable-${IpOverviewId}-${
|
||||
contextID ? `${contextID}-` : ''
|
||||
}host-name`}
|
||||
isDraggable={false}
|
||||
field={'host.name'}
|
||||
value={host.name[0]}
|
||||
>
|
||||
|
@ -204,7 +209,7 @@ export const DefaultFieldRendererComponent: React.FC<DefaultFieldRendererProps>
|
|||
</>
|
||||
)}
|
||||
{typeof rowItem === 'string' && (
|
||||
<DefaultDraggable id={id} field={attrName} value={rowItem}>
|
||||
<DefaultDraggable id={id} isDraggable={false} field={attrName} value={rowItem}>
|
||||
{render ? render(rowItem) : rowItem}
|
||||
</DefaultDraggable>
|
||||
)}
|
||||
|
|
|
@ -59,11 +59,11 @@ describe('FieldName', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
await waitFor(() => {
|
||||
wrapper.find('[data-test-subj="withHoverActionsButton"]').at(0).simulate('mouseenter');
|
||||
wrapper.find('[data-test-subj="withHoverActionsButton"]').simulate('mouseenter');
|
||||
wrapper.update();
|
||||
jest.runAllTimers();
|
||||
wrapper.update();
|
||||
expect(wrapper.find('[data-test-subj="copy-to-clipboard"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-test-subj="hover-actions-copy-button"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -11,11 +11,9 @@ import styled from 'styled-components';
|
|||
|
||||
import { OnUpdateColumns } from '../timeline/events';
|
||||
import { WithHoverActions } from '../../../common/components/with_hover_actions';
|
||||
import {
|
||||
DraggableWrapperHoverContent,
|
||||
useGetTimelineId,
|
||||
} from '../../../common/components/drag_and_drop/draggable_wrapper_hover_content';
|
||||
import { useGetTimelineId } from '../../../common/components/drag_and_drop/use_get_timeline_id_from_dom';
|
||||
import { ColumnHeaderOptions } from '../../../../common';
|
||||
import { HoverActions } from '../../../common/components/hover_actions';
|
||||
|
||||
/**
|
||||
* The name of a (draggable) field
|
||||
|
@ -112,9 +110,10 @@ export const FieldName = React.memo<{
|
|||
|
||||
const hoverContent = useMemo(
|
||||
() => (
|
||||
<DraggableWrapperHoverContent
|
||||
<HoverActions
|
||||
closePopOver={handleClosePopOverTrigger}
|
||||
field={fieldId}
|
||||
isObjectArray={false}
|
||||
ownFocus={hoverActionsOwnFocus}
|
||||
showTopN={showTopN}
|
||||
toggleTopN={toggleTopN}
|
||||
|
|
|
@ -132,6 +132,7 @@ const NonDecoratedIpComponent: React.FC<{
|
|||
return (
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProviderProp}
|
||||
isDraggable={isDraggable}
|
||||
key={key}
|
||||
render={render}
|
||||
truncate={truncate}
|
||||
|
@ -237,6 +238,7 @@ const AddressLinksItemComponent: React.FC<AddressLinksItemProps> = ({
|
|||
return (
|
||||
<DraggableWrapper
|
||||
dataProvider={dataProviderProp}
|
||||
isDraggable={isDraggable}
|
||||
key={key}
|
||||
render={render}
|
||||
truncate={truncate}
|
||||
|
|
|
@ -30,14 +30,16 @@ export const Ja3Fingerprint = React.memo<{
|
|||
eventId: string;
|
||||
contextId: string;
|
||||
fieldName: string;
|
||||
isDraggable?: boolean;
|
||||
value?: string | null;
|
||||
}>(({ contextId, eventId, fieldName, value }) => (
|
||||
}>(({ contextId, eventId, fieldName, isDraggable, value }) => (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
data-test-subj="ja3-hash"
|
||||
eventId={eventId}
|
||||
field={fieldName}
|
||||
iconType="snowflake"
|
||||
isDraggable={isDraggable}
|
||||
value={value}
|
||||
>
|
||||
<Ja3FingerprintLabel data-test-subj="ja3-fingerprint-label">
|
||||
|
|
|
@ -23,6 +23,7 @@ import { JA3_HASH_FIELD_NAME, Ja3Fingerprint } from '../../ja3_fingerprint';
|
|||
export const Fingerprints = React.memo<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
tlsClientCertificateFingerprintSha1?: string[] | null;
|
||||
tlsFingerprintsJa3Hash?: string[] | null;
|
||||
tlsServerCertificateFingerprintSha1?: string[] | null;
|
||||
|
@ -30,6 +31,7 @@ export const Fingerprints = React.memo<{
|
|||
({
|
||||
contextId,
|
||||
eventId,
|
||||
isDraggable,
|
||||
tlsClientCertificateFingerprintSha1,
|
||||
tlsFingerprintsJa3Hash,
|
||||
tlsServerCertificateFingerprintSha1,
|
||||
|
@ -48,6 +50,7 @@ export const Fingerprints = React.memo<{
|
|||
eventId={eventId}
|
||||
fieldName={JA3_HASH_FIELD_NAME}
|
||||
contextId={contextId}
|
||||
isDraggable={isDraggable}
|
||||
value={ja3}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -61,6 +64,7 @@ export const Fingerprints = React.memo<{
|
|||
certificateType="client"
|
||||
contextId={contextId}
|
||||
fieldName={TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={clientCert}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -74,6 +78,7 @@ export const Fingerprints = React.memo<{
|
|||
certificateType="server"
|
||||
contextId={contextId}
|
||||
fieldName={TLS_SERVER_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={serverCert}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -37,6 +37,7 @@ export const Netflow = React.memo<NetflowProps>(
|
|||
eventId,
|
||||
eventEnd,
|
||||
eventStart,
|
||||
isDraggable,
|
||||
networkBytes,
|
||||
networkCommunityId,
|
||||
networkDirection,
|
||||
|
@ -82,6 +83,7 @@ export const Netflow = React.memo<NetflowProps>(
|
|||
eventId={eventId}
|
||||
eventEnd={eventEnd}
|
||||
eventStart={eventStart}
|
||||
isDraggable={isDraggable}
|
||||
networkBytes={networkBytes}
|
||||
networkCommunityId={networkCommunityId}
|
||||
networkDirection={networkDirection}
|
||||
|
@ -105,6 +107,7 @@ export const Netflow = React.memo<NetflowProps>(
|
|||
<Fingerprints
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
tlsClientCertificateFingerprintSha1={tlsClientCertificateFingerprintSha1}
|
||||
tlsFingerprintsJa3Hash={tlsFingerprintsJa3Hash}
|
||||
tlsServerCertificateFingerprintSha1={tlsServerCertificateFingerprintSha1}
|
||||
|
|
|
@ -38,7 +38,8 @@ export const DurationEventStartEnd = React.memo<{
|
|||
eventId: string;
|
||||
eventEnd?: string[] | null;
|
||||
eventStart?: string[] | null;
|
||||
}>(({ contextId, eventDuration, eventId, eventEnd, eventStart }) => (
|
||||
isDraggable?: boolean;
|
||||
}>(({ contextId, eventDuration, eventId, eventEnd, eventStart, isDraggable }) => (
|
||||
<EuiFlexGroup
|
||||
alignItems="flexStart"
|
||||
data-test-subj="duration-and-start-group"
|
||||
|
@ -53,6 +54,7 @@ export const DurationEventStartEnd = React.memo<{
|
|||
data-test-subj="event-duration"
|
||||
field={EVENT_DURATION_FIELD_NAME}
|
||||
id={`duration-event-start-end-default-draggable-${contextId}-${eventId}-${EVENT_DURATION_FIELD_NAME}-${duration}`}
|
||||
isDraggable={isDraggable}
|
||||
// @ts-expect-error
|
||||
name={name}
|
||||
tooltipContent={null}
|
||||
|
@ -76,6 +78,7 @@ export const DurationEventStartEnd = React.memo<{
|
|||
data-test-subj="event-start"
|
||||
field={EVENT_START_FIELD_NAME}
|
||||
id={`duration-event-start-end-default-draggable-${contextId}-${eventId}-${EVENT_START_FIELD_NAME}-${start}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={null}
|
||||
value={start}
|
||||
>
|
||||
|
@ -94,6 +97,7 @@ export const DurationEventStartEnd = React.memo<{
|
|||
data-test-subj="event-end"
|
||||
field={EVENT_END_FIELD_NAME}
|
||||
id={`duration-event-start-end-default-draggable-${contextId}-${eventId}-${EVENT_END_FIELD_NAME}-${end}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={null}
|
||||
value={end}
|
||||
>
|
||||
|
|
|
@ -48,6 +48,7 @@ export const NetflowColumns = React.memo<NetflowColumnsProps>(
|
|||
eventId,
|
||||
eventEnd,
|
||||
eventStart,
|
||||
isDraggable,
|
||||
networkBytes,
|
||||
networkCommunityId,
|
||||
networkDirection,
|
||||
|
@ -76,6 +77,7 @@ export const NetflowColumns = React.memo<NetflowColumnsProps>(
|
|||
<UserProcess
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
processName={processName}
|
||||
userName={userName}
|
||||
/>
|
||||
|
@ -88,6 +90,7 @@ export const NetflowColumns = React.memo<NetflowColumnsProps>(
|
|||
eventId={eventId}
|
||||
eventEnd={eventEnd}
|
||||
eventStart={eventStart}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</EuiFlexItemMarginRight>
|
||||
|
||||
|
@ -104,6 +107,7 @@ export const NetflowColumns = React.memo<NetflowColumnsProps>(
|
|||
destinationPackets={destinationPackets}
|
||||
destinationPort={destinationPort}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
networkBytes={networkBytes}
|
||||
networkCommunityId={networkCommunityId}
|
||||
networkDirection={networkDirection}
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface NetflowColumnsProps {
|
|||
eventId: string;
|
||||
eventEnd?: string[] | null;
|
||||
eventStart?: string[] | null;
|
||||
isDraggable?: boolean;
|
||||
networkBytes?: string[] | null;
|
||||
networkCommunityId?: string[] | null;
|
||||
networkDirection?: string[] | null;
|
||||
|
|
|
@ -22,9 +22,10 @@ export const USER_NAME_FIELD_NAME = 'user.name';
|
|||
export const UserProcess = React.memo<{
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
processName?: string[] | null;
|
||||
userName?: string[] | null;
|
||||
}>(({ contextId, eventId, processName, userName }) => (
|
||||
}>(({ contextId, eventId, isDraggable, processName, userName }) => (
|
||||
<EuiFlexGroup
|
||||
alignItems="flexStart"
|
||||
data-test-subj="user-process"
|
||||
|
@ -40,6 +41,7 @@ export const UserProcess = React.memo<{
|
|||
data-test-subj="user-name"
|
||||
eventId={eventId}
|
||||
field={USER_NAME_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={user}
|
||||
iconType="user"
|
||||
/>
|
||||
|
@ -55,6 +57,7 @@ export const UserProcess = React.memo<{
|
|||
data-test-subj="process-name"
|
||||
eventId={eventId}
|
||||
field={PROCESS_NAME_FIELD_NAME}
|
||||
isDraggable={isDraggable}
|
||||
value={process}
|
||||
iconType="console"
|
||||
/>
|
||||
|
|
|
@ -20,6 +20,7 @@ export interface NetflowProps {
|
|||
eventId: string;
|
||||
eventEnd?: string[] | null;
|
||||
eventStart?: string[] | null;
|
||||
isDraggable?: boolean;
|
||||
networkBytes?: string[] | null;
|
||||
networkCommunityId?: string[] | null;
|
||||
networkDirection?: string[] | null;
|
||||
|
|
|
@ -26,6 +26,7 @@ const AlertsExampleComponent: React.FC = () => {
|
|||
{alertsRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndpointProcessExecutionMalwarePreventionAlert,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const AuditdExampleComponent: React.FC = () => {
|
|||
{auditdRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[26].ecs,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const AuditdFileExampleComponent: React.FC = () => {
|
|||
{auditdFileRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[27].ecs,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const LibraryExampleComponent: React.FC = () => {
|
|||
{libraryRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndpointLibraryLoadEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -16,6 +16,7 @@ const NetflowExampleComponent: React.FC = () => (
|
|||
{netflowRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: getMockNetflowData(),
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const RegistryExampleComponent: React.FC = () => {
|
|||
{registryRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndpointRegistryModificationEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -16,6 +16,7 @@ const SuricataExampleComponent: React.FC = () => (
|
|||
{suricataRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[2].ecs,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const SystemExampleComponent: React.FC = () => {
|
|||
{systemRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameTerminationEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -19,6 +19,7 @@ const SystemDnsExampleComponent: React.FC = () => {
|
|||
{systemDnsRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameDnsRequest,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const SystemEndgameProcessExampleComponent: React.FC = () => {
|
|||
{systemEndgameProcessRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameCreationEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const SystemFileExampleComponent: React.FC = () => {
|
|||
{systemFileRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameFileDeleteEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ const SystemFimExampleComponent: React.FC = () => {
|
|||
{systemFimRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameFileCreateEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -21,6 +21,7 @@ const SystemSecurityEventExampleComponent: React.FC = () => {
|
|||
{systemSecurityEventRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameUserLogon,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -22,6 +22,7 @@ const SystemSocketExampleComponent: React.FC = () => {
|
|||
{systemSocketRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameIpv4ConnectionAcceptEvent,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -16,6 +16,7 @@ const ThreatMatchExampleComponent: React.FC = () => (
|
|||
{threatMatchRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[31].ecs,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -16,6 +16,7 @@ const ZeekExampleComponent: React.FC = () => (
|
|||
{zeekRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[13].ecs,
|
||||
isDraggable: false,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
|
|
|
@ -80,6 +80,7 @@ export const StatefulRowRenderer = ({
|
|||
{rowRenderer.renderRow({
|
||||
browserFields,
|
||||
data: event.ecs,
|
||||
isDraggable: true,
|
||||
timelineId,
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,7 @@ exports[`empty_column_renderer renders correctly against snapshot 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
isDraggable={true}
|
||||
key="empty-column-renderer-draggable-wrapper-test-source.ip-1-source.ip"
|
||||
render={[Function]}
|
||||
/>
|
||||
|
|
|
@ -42,6 +42,7 @@ export const AgentStatuses = React.memo(
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={fieldName}
|
||||
value={`${agentStatus}`}
|
||||
>
|
||||
|
@ -60,6 +61,7 @@ export const AgentStatuses = React.memo(
|
|||
<DefaultDraggable
|
||||
field={isolationFieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${isolationFieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={isolationFieldName}
|
||||
value={`${isIsolated}`}
|
||||
>
|
||||
|
|
|
@ -15,9 +15,10 @@ interface Props {
|
|||
contextId: string;
|
||||
eventId: string;
|
||||
processTitle: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props) => {
|
||||
export const ArgsComponent = ({ args, contextId, eventId, processTitle, isDraggable }: Props) => {
|
||||
if (isNillEmptyOrNotFinite(args) && isNillEmptyOrNotFinite(processTitle)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -31,6 +32,7 @@ export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props)
|
|||
contextId={`${contextId}-args-${i}-${arg}`}
|
||||
eventId={eventId}
|
||||
field="process.args"
|
||||
isDraggable={isDraggable}
|
||||
value={arg}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -42,6 +44,7 @@ export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props)
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="process.title"
|
||||
isDraggable={isDraggable}
|
||||
value={processTitle}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
|
|
@ -98,6 +98,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
|
|||
},
|
||||
}
|
||||
}
|
||||
isDraggable={true}
|
||||
text="connected using"
|
||||
timelineId="test"
|
||||
/>
|
||||
|
@ -222,6 +223,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
|
|||
}
|
||||
}
|
||||
fileIcon="document"
|
||||
isDraggable={true}
|
||||
text="opened file using"
|
||||
timelineId="test"
|
||||
/>
|
||||
|
|
|
@ -36,6 +36,7 @@ interface Props {
|
|||
workingDirectory: string | null | undefined;
|
||||
args: string[] | null | undefined;
|
||||
session: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const AuditdGenericLine = React.memo<Props>(
|
||||
|
@ -55,6 +56,7 @@ export const AuditdGenericLine = React.memo<Props>(
|
|||
result,
|
||||
session,
|
||||
text,
|
||||
isDraggable,
|
||||
}) => (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" gutterSize="none" wrap={true}>
|
||||
<SessionUserHostWorkingDir
|
||||
|
@ -66,6 +68,7 @@ export const AuditdGenericLine = React.memo<Props>(
|
|||
secondary={secondary}
|
||||
workingDirectory={workingDirectory}
|
||||
session={session}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
{processExecutable != null && (
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
|
@ -81,9 +84,16 @@ export const AuditdGenericLine = React.memo<Props>(
|
|||
processPid={processPid}
|
||||
processName={processName}
|
||||
processExecutable={processExecutable}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
<Args eventId={id} args={args} contextId={contextId} processTitle={processTitle} />
|
||||
<Args
|
||||
eventId={id}
|
||||
args={args}
|
||||
contextId={contextId}
|
||||
isDraggable={isDraggable}
|
||||
processTitle={processTitle}
|
||||
/>
|
||||
{result != null && (
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
{i18n.WITH_RESULT}
|
||||
|
@ -94,6 +104,7 @@ export const AuditdGenericLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="auditd.result"
|
||||
isDraggable={isDraggable}
|
||||
queryValue={result}
|
||||
value={result}
|
||||
/>
|
||||
|
@ -107,13 +118,14 @@ AuditdGenericLine.displayName = 'AuditdGenericLine';
|
|||
interface GenericDetailsProps {
|
||||
browserFields: BrowserFields;
|
||||
data: Ecs;
|
||||
isDraggable?: boolean;
|
||||
contextId: string;
|
||||
text: string;
|
||||
timelineId: string;
|
||||
}
|
||||
|
||||
export const AuditdGenericDetails = React.memo<GenericDetailsProps>(
|
||||
({ data, contextId, text, timelineId }) => {
|
||||
({ data, contextId, isDraggable, text, timelineId }) => {
|
||||
const id = data._id;
|
||||
const session: string | null | undefined = get('auditd.session[0]', data);
|
||||
const hostName: string | null | undefined = get('host.name[0]', data);
|
||||
|
@ -146,9 +158,10 @@ export const AuditdGenericDetails = React.memo<GenericDetailsProps>(
|
|||
primary={primary}
|
||||
result={result}
|
||||
secondary={secondary}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<NetflowRenderer data={data} timelineId={timelineId} />
|
||||
<NetflowRenderer data={data} isDraggable={isDraggable} timelineId={timelineId} />
|
||||
</Details>
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -38,6 +38,7 @@ interface Props {
|
|||
workingDirectory: string | null | undefined;
|
||||
args: string[] | null | undefined;
|
||||
session: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const AuditdGenericFileLine = React.memo<Props>(
|
||||
|
@ -59,6 +60,7 @@ export const AuditdGenericFileLine = React.memo<Props>(
|
|||
session,
|
||||
text,
|
||||
fileIcon,
|
||||
isDraggable,
|
||||
}) => (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" gutterSize="none" wrap={true}>
|
||||
<SessionUserHostWorkingDir
|
||||
|
@ -70,6 +72,7 @@ export const AuditdGenericFileLine = React.memo<Props>(
|
|||
secondary={secondary}
|
||||
workingDirectory={workingDirectory}
|
||||
session={session}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
{(filePath != null || processExecutable != null) && (
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
|
@ -81,6 +84,7 @@ export const AuditdGenericFileLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="file.path"
|
||||
isDraggable={isDraggable}
|
||||
value={filePath}
|
||||
iconType={fileIcon}
|
||||
/>
|
||||
|
@ -96,12 +100,19 @@ export const AuditdGenericFileLine = React.memo<Props>(
|
|||
endgamePid={undefined}
|
||||
endgameProcessName={undefined}
|
||||
eventId={id}
|
||||
isDraggable={isDraggable}
|
||||
processPid={processPid}
|
||||
processName={processName}
|
||||
processExecutable={processExecutable}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
<Args eventId={id} args={args} contextId={contextId} processTitle={processTitle} />
|
||||
<Args
|
||||
eventId={id}
|
||||
args={args}
|
||||
contextId={contextId}
|
||||
isDraggable={isDraggable}
|
||||
processTitle={processTitle}
|
||||
/>
|
||||
{result != null && (
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
{i18n.WITH_RESULT}
|
||||
|
@ -112,6 +123,7 @@ export const AuditdGenericFileLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="auditd.result"
|
||||
isDraggable={isDraggable}
|
||||
queryValue={result}
|
||||
value={result}
|
||||
/>
|
||||
|
@ -124,15 +136,16 @@ AuditdGenericFileLine.displayName = 'AuditdGenericFileLine';
|
|||
|
||||
interface GenericDetailsProps {
|
||||
browserFields: BrowserFields;
|
||||
data: Ecs;
|
||||
contextId: string;
|
||||
data: Ecs;
|
||||
text: string;
|
||||
fileIcon: IconType;
|
||||
timelineId: string;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const AuditdGenericFileDetails = React.memo<GenericDetailsProps>(
|
||||
({ data, contextId, text, fileIcon = 'document', timelineId }) => {
|
||||
({ data, contextId, text, fileIcon = 'document', timelineId, isDraggable }) => {
|
||||
const id = data._id;
|
||||
const session: string | null | undefined = get('auditd.session[0]', data);
|
||||
const hostName: string | null | undefined = get('host.name[0]', data);
|
||||
|
@ -169,9 +182,10 @@ export const AuditdGenericFileDetails = React.memo<GenericDetailsProps>(
|
|||
secondary={secondary}
|
||||
fileIcon={fileIcon}
|
||||
result={result}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<NetflowRenderer data={data} timelineId={timelineId} />
|
||||
<NetflowRenderer data={data} isDraggable={isDraggable} timelineId={timelineId} />
|
||||
</Details>
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -55,6 +55,7 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields,
|
||||
data: auditd,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
|
||||
|
@ -84,6 +85,7 @@ describe('GenericRowRenderer', () => {
|
|||
const children = connectedToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: auditd,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -117,6 +119,7 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields,
|
||||
data: auditdFile,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
|
||||
|
@ -146,6 +149,7 @@ describe('GenericRowRenderer', () => {
|
|||
const children = fileToRenderer.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: auditdFile,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -36,11 +36,12 @@ export const createGenericAuditRowRenderer = ({
|
|||
action.toLowerCase() === actionName
|
||||
);
|
||||
},
|
||||
renderRow: ({ browserFields, data, timelineId }) => (
|
||||
renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
|
||||
<RowRendererContainer>
|
||||
<AuditdGenericDetails
|
||||
browserFields={browserFields}
|
||||
data={data}
|
||||
isDraggable={isDraggable}
|
||||
contextId={`${actionName}-${timelineId}`}
|
||||
text={text}
|
||||
timelineId={timelineId}
|
||||
|
@ -69,14 +70,15 @@ export const createGenericFileRowRenderer = ({
|
|||
action.toLowerCase() === actionName
|
||||
);
|
||||
},
|
||||
renderRow: ({ browserFields, data, timelineId }) => (
|
||||
renderRow: ({ browserFields, data, isDraggable, timelineId }) => (
|
||||
<RowRendererContainer>
|
||||
<AuditdGenericFileDetails
|
||||
browserFields={browserFields}
|
||||
data={data}
|
||||
contextId={`${actionName}-${timelineId}`}
|
||||
text={text}
|
||||
data={data}
|
||||
fileIcon={fileIcon}
|
||||
isDraggable={isDraggable}
|
||||
text={text}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</RowRendererContainer>
|
||||
|
|
|
@ -21,69 +21,77 @@ interface Props {
|
|||
eventId: string;
|
||||
primary: string | null | undefined;
|
||||
secondary: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const PrimarySecondary = React.memo<Props>(({ contextId, eventId, primary, secondary }) => {
|
||||
if (nilOrUnSet(primary) && nilOrUnSet(secondary)) {
|
||||
return null;
|
||||
} else if (!nilOrUnSet(primary) && nilOrUnSet(secondary)) {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.primary"
|
||||
value={primary}
|
||||
iconType="user"
|
||||
/>
|
||||
);
|
||||
} else if (nilOrUnSet(primary) && !nilOrUnSet(secondary)) {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.secondary"
|
||||
value={secondary}
|
||||
iconType="user"
|
||||
/>
|
||||
);
|
||||
} else if (primary === secondary) {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.secondary"
|
||||
value={secondary}
|
||||
iconType="user"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.primary"
|
||||
value={primary}
|
||||
iconType="user"
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
{i18n.AS}
|
||||
</TokensFlexItem>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.secondary"
|
||||
value={secondary}
|
||||
iconType="user"
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
export const PrimarySecondary = React.memo<Props>(
|
||||
({ contextId, eventId, primary, secondary, isDraggable }) => {
|
||||
if (nilOrUnSet(primary) && nilOrUnSet(secondary)) {
|
||||
return null;
|
||||
} else if (!nilOrUnSet(primary) && nilOrUnSet(secondary)) {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.primary"
|
||||
isDraggable={isDraggable}
|
||||
value={primary}
|
||||
iconType="user"
|
||||
/>
|
||||
);
|
||||
} else if (nilOrUnSet(primary) && !nilOrUnSet(secondary)) {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.secondary"
|
||||
isDraggable={isDraggable}
|
||||
value={secondary}
|
||||
iconType="user"
|
||||
/>
|
||||
);
|
||||
} else if (primary === secondary) {
|
||||
return (
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.secondary"
|
||||
isDraggable={isDraggable}
|
||||
value={secondary}
|
||||
iconType="user"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.primary"
|
||||
isDraggable={isDraggable}
|
||||
value={primary}
|
||||
iconType="user"
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
{i18n.AS}
|
||||
</TokensFlexItem>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
<DraggableBadge
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="auditd.summary.actor.secondary"
|
||||
isDraggable={isDraggable}
|
||||
value={secondary}
|
||||
iconType="user"
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
PrimarySecondary.displayName = 'PrimarySecondary';
|
||||
|
||||
|
@ -93,10 +101,11 @@ interface PrimarySecondaryUserInfoProps {
|
|||
userName: string | null | undefined;
|
||||
primary: string | null | undefined;
|
||||
secondary: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const PrimarySecondaryUserInfo = React.memo<PrimarySecondaryUserInfoProps>(
|
||||
({ contextId, eventId, userName, primary, secondary }) => {
|
||||
({ contextId, eventId, userName, primary, secondary, isDraggable }) => {
|
||||
if (nilOrUnSet(userName) && nilOrUnSet(primary) && nilOrUnSet(secondary)) {
|
||||
return null;
|
||||
} else if (
|
||||
|
@ -111,6 +120,7 @@ export const PrimarySecondaryUserInfo = React.memo<PrimarySecondaryUserInfoProps
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="user.name"
|
||||
isDraggable={isDraggable}
|
||||
value={userName}
|
||||
iconType="user"
|
||||
/>
|
||||
|
@ -121,6 +131,7 @@ export const PrimarySecondaryUserInfo = React.memo<PrimarySecondaryUserInfoProps
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="user.name"
|
||||
isDraggable={isDraggable}
|
||||
value={userName}
|
||||
iconType="user"
|
||||
/>
|
||||
|
@ -130,6 +141,7 @@ export const PrimarySecondaryUserInfo = React.memo<PrimarySecondaryUserInfoProps
|
|||
<PrimarySecondary
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
/>
|
||||
|
|
|
@ -23,10 +23,21 @@ interface Props {
|
|||
secondary: string | null | undefined;
|
||||
workingDirectory: string | null | undefined;
|
||||
session: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const SessionUserHostWorkingDir = React.memo<Props>(
|
||||
({ eventId, contextId, hostName, userName, primary, secondary, workingDirectory, session }) => (
|
||||
({
|
||||
eventId,
|
||||
contextId,
|
||||
hostName,
|
||||
userName,
|
||||
primary,
|
||||
secondary,
|
||||
workingDirectory,
|
||||
session,
|
||||
isDraggable,
|
||||
}) => (
|
||||
<>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
{i18n.SESSION}
|
||||
|
@ -38,6 +49,7 @@ export const SessionUserHostWorkingDir = React.memo<Props>(
|
|||
field="auditd.session"
|
||||
value={session}
|
||||
iconType="number"
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
|
@ -47,6 +59,7 @@ export const SessionUserHostWorkingDir = React.memo<Props>(
|
|||
userName={userName}
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
{hostName != null && (
|
||||
|
@ -59,6 +72,7 @@ export const SessionUserHostWorkingDir = React.memo<Props>(
|
|||
eventId={eventId}
|
||||
workingDirectory={workingDirectory}
|
||||
hostName={hostName}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -26,6 +26,7 @@ export const Bytes = React.memo<{
|
|||
isDraggable ? (
|
||||
<DefaultDraggable
|
||||
id={`bytes-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
// @ts-expect-error
|
||||
name={name}
|
||||
field={fieldName}
|
||||
|
|
|
@ -24,6 +24,7 @@ exports[`threatMatchRowRenderer #renderRow renders correctly against snapshot 1`
|
|||
}
|
||||
}
|
||||
eventId="1"
|
||||
isDraggable={true}
|
||||
/>
|
||||
</styled.div>
|
||||
</RowRendererContainer>
|
||||
|
|
|
@ -26,6 +26,7 @@ interface IndicatorDetailsProps {
|
|||
indicatorProvider: string | undefined;
|
||||
indicatorReference: string | undefined;
|
||||
indicatorType: string | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const IndicatorDetails: React.FC<IndicatorDetailsProps> = ({
|
||||
|
@ -35,6 +36,7 @@ export const IndicatorDetails: React.FC<IndicatorDetailsProps> = ({
|
|||
indicatorProvider,
|
||||
indicatorReference,
|
||||
indicatorType,
|
||||
isDraggable,
|
||||
}) => (
|
||||
<EuiFlexGroup
|
||||
alignItems="flexStart"
|
||||
|
@ -51,6 +53,7 @@ export const IndicatorDetails: React.FC<IndicatorDetailsProps> = ({
|
|||
data-test-subj="threat-match-indicator-details-indicator-type"
|
||||
eventId={eventId}
|
||||
field={INDICATOR_MATCHED_TYPE}
|
||||
isDraggable={isDraggable}
|
||||
value={indicatorType}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -71,6 +74,7 @@ export const IndicatorDetails: React.FC<IndicatorDetailsProps> = ({
|
|||
data-test-subj="threat-match-indicator-details-indicator-dataset"
|
||||
eventId={eventId}
|
||||
field={INDICATOR_DATASET}
|
||||
isDraggable={isDraggable}
|
||||
value={indicatorDataset}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -92,6 +96,7 @@ export const IndicatorDetails: React.FC<IndicatorDetailsProps> = ({
|
|||
data-test-subj="threat-match-indicator-details-indicator-provider"
|
||||
eventId={eventId}
|
||||
field={INDICATOR_PROVIDER}
|
||||
isDraggable={isDraggable}
|
||||
value={indicatorProvider}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -108,6 +113,7 @@ export const IndicatorDetails: React.FC<IndicatorDetailsProps> = ({
|
|||
data-test-subj="threat-match-indicator-details-indicator-reference"
|
||||
eventId={eventId}
|
||||
fieldName={INDICATOR_REFERENCE}
|
||||
isDraggable={isDraggable}
|
||||
value={indicatorReference}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -16,6 +16,7 @@ import { HorizontalSpacer } from './helpers';
|
|||
interface MatchDetailsProps {
|
||||
contextId: string;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
sourceField: string;
|
||||
sourceValue: string;
|
||||
}
|
||||
|
@ -23,6 +24,7 @@ interface MatchDetailsProps {
|
|||
export const MatchDetails: React.FC<MatchDetailsProps> = ({
|
||||
contextId,
|
||||
eventId,
|
||||
isDraggable,
|
||||
sourceField,
|
||||
sourceValue,
|
||||
}) => (
|
||||
|
@ -40,6 +42,7 @@ export const MatchDetails: React.FC<MatchDetailsProps> = ({
|
|||
data-test-subj="threat-match-details-source-field"
|
||||
eventId={eventId}
|
||||
field={INDICATOR_MATCHED_FIELD}
|
||||
isDraggable={isDraggable}
|
||||
value={sourceField}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -57,6 +60,7 @@ export const MatchDetails: React.FC<MatchDetailsProps> = ({
|
|||
data-test-subj="threat-match-details-source-value"
|
||||
eventId={eventId}
|
||||
field={sourceField}
|
||||
isDraggable={isDraggable}
|
||||
value={sourceValue}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -28,6 +28,7 @@ export interface ThreatMatchRowProps {
|
|||
indicatorProvider: string | undefined;
|
||||
indicatorReference: string | undefined;
|
||||
indicatorType: string | undefined;
|
||||
isDraggable?: boolean;
|
||||
sourceField: string;
|
||||
sourceValue: string;
|
||||
}
|
||||
|
@ -36,10 +37,12 @@ export const ThreatMatchRow = ({
|
|||
contextId,
|
||||
data,
|
||||
eventId,
|
||||
isDraggable,
|
||||
}: {
|
||||
contextId: string;
|
||||
data: Fields;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
}) => {
|
||||
const props = {
|
||||
contextId,
|
||||
|
@ -48,6 +51,7 @@ export const ThreatMatchRow = ({
|
|||
indicatorReference: get(data, EVENT_REFERENCE)[0] as string | undefined,
|
||||
indicatorProvider: get(data, PROVIDER)[0] as string | undefined,
|
||||
indicatorType: get(data, MATCHED_TYPE)[0] as string | undefined,
|
||||
isDraggable,
|
||||
sourceField: get(data, MATCHED_FIELD)[0] as string,
|
||||
sourceValue: get(data, MATCHED_ATOMIC)[0] as string,
|
||||
};
|
||||
|
@ -62,6 +66,7 @@ export const ThreatMatchRowView = ({
|
|||
indicatorProvider,
|
||||
indicatorReference,
|
||||
indicatorType,
|
||||
isDraggable,
|
||||
sourceField,
|
||||
sourceValue,
|
||||
}: ThreatMatchRowProps) => {
|
||||
|
@ -76,6 +81,7 @@ export const ThreatMatchRowView = ({
|
|||
<MatchDetails
|
||||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
sourceField={sourceField}
|
||||
sourceValue={sourceValue}
|
||||
/>
|
||||
|
@ -88,6 +94,7 @@ export const ThreatMatchRowView = ({
|
|||
indicatorProvider={indicatorProvider}
|
||||
indicatorReference={indicatorReference}
|
||||
indicatorType={indicatorType}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -56,6 +56,7 @@ describe('threatMatchRowRenderer', () => {
|
|||
const children = threatMatchRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: threatMatchData,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = shallow(<span>{children}</span>);
|
||||
|
|
|
@ -20,7 +20,7 @@ const SpacedContainer = styled.div`
|
|||
margin: ${({ theme }) => theme.eui.paddingSizes.s} 0;
|
||||
`;
|
||||
|
||||
export const ThreatMatchRows: RowRenderer['renderRow'] = ({ data, timelineId }) => {
|
||||
export const ThreatMatchRows: RowRenderer['renderRow'] = ({ data, isDraggable, timelineId }) => {
|
||||
const indicators = get(data, 'threat.indicator') as Fields[];
|
||||
const eventId = get(data, ID_FIELD_NAME);
|
||||
|
||||
|
@ -31,7 +31,12 @@ export const ThreatMatchRows: RowRenderer['renderRow'] = ({ data, timelineId })
|
|||
const contextId = `threat-match-row-${timelineId}-${eventId}-${index}`;
|
||||
return (
|
||||
<Fragment key={contextId}>
|
||||
<ThreatMatchRow contextId={contextId} data={indicator} eventId={eventId} />
|
||||
<ThreatMatchRow
|
||||
contextId={contextId}
|
||||
data={indicator}
|
||||
eventId={eventId}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
{index < indicators.length - 1 && <EuiHorizontalRule margin="s" />}
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -20,46 +20,50 @@ interface Props {
|
|||
browserFields: BrowserFields;
|
||||
contextId: string;
|
||||
data: Ecs;
|
||||
isDraggable?: boolean;
|
||||
timelineId: string;
|
||||
}
|
||||
|
||||
export const DnsRequestEventDetails = React.memo<Props>(({ data, contextId, timelineId }) => {
|
||||
const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', data);
|
||||
const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', data);
|
||||
const dnsResolvedIp: string | null | undefined = get('dns.resolved_ip[0]', data);
|
||||
const dnsResponseCode: string | null | undefined = get('dns.response_code[0]', data);
|
||||
const eventCode: string | null | undefined = get('event.code[0]', data);
|
||||
const hostName: string | null | undefined = get('host.name[0]', data);
|
||||
const id = data._id;
|
||||
const processExecutable: string | null | undefined = get('process.executable[0]', data);
|
||||
const processName: string | null | undefined = get('process.name[0]', data);
|
||||
const processPid: number | null | undefined = get('process.pid[0]', data);
|
||||
const userDomain: string | null | undefined = get('user.domain[0]', data);
|
||||
const userName: string | null | undefined = get('user.name[0]', data);
|
||||
const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
|
||||
export const DnsRequestEventDetails = React.memo<Props>(
|
||||
({ data, contextId, isDraggable, timelineId }) => {
|
||||
const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', data);
|
||||
const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', data);
|
||||
const dnsResolvedIp: string | null | undefined = get('dns.resolved_ip[0]', data);
|
||||
const dnsResponseCode: string | null | undefined = get('dns.response_code[0]', data);
|
||||
const eventCode: string | null | undefined = get('event.code[0]', data);
|
||||
const hostName: string | null | undefined = get('host.name[0]', data);
|
||||
const id = data._id;
|
||||
const processExecutable: string | null | undefined = get('process.executable[0]', data);
|
||||
const processName: string | null | undefined = get('process.name[0]', data);
|
||||
const processPid: number | null | undefined = get('process.pid[0]', data);
|
||||
const userDomain: string | null | undefined = get('user.domain[0]', data);
|
||||
const userName: string | null | undefined = get('user.name[0]', data);
|
||||
const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
|
||||
|
||||
return (
|
||||
<Details>
|
||||
<DnsRequestEventDetailsLine
|
||||
contextId={contextId}
|
||||
dnsQuestionName={dnsQuestionName}
|
||||
dnsQuestionType={dnsQuestionType}
|
||||
dnsResolvedIp={dnsResolvedIp}
|
||||
dnsResponseCode={dnsResponseCode}
|
||||
eventCode={eventCode}
|
||||
hostName={hostName}
|
||||
id={id}
|
||||
processExecutable={processExecutable}
|
||||
processName={processName}
|
||||
processPid={processPid}
|
||||
userDomain={userDomain}
|
||||
userName={userName}
|
||||
winlogEventId={winlogEventId}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<NetflowRenderer data={data} timelineId={timelineId} />
|
||||
</Details>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Details>
|
||||
<DnsRequestEventDetailsLine
|
||||
contextId={contextId}
|
||||
dnsQuestionName={dnsQuestionName}
|
||||
dnsQuestionType={dnsQuestionType}
|
||||
dnsResolvedIp={dnsResolvedIp}
|
||||
dnsResponseCode={dnsResponseCode}
|
||||
eventCode={eventCode}
|
||||
hostName={hostName}
|
||||
id={id}
|
||||
isDraggable={isDraggable}
|
||||
processExecutable={processExecutable}
|
||||
processName={processName}
|
||||
processPid={processPid}
|
||||
userDomain={userDomain}
|
||||
userName={userName}
|
||||
winlogEventId={winlogEventId}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<NetflowRenderer data={data} isDraggable={isDraggable} timelineId={timelineId} />
|
||||
</Details>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DnsRequestEventDetails.displayName = 'DnsRequestEventDetails';
|
||||
|
|
|
@ -24,6 +24,7 @@ interface Props {
|
|||
eventCode: string | null | undefined;
|
||||
hostName: string | null | undefined;
|
||||
id: string;
|
||||
isDraggable?: boolean;
|
||||
processExecutable: string | null | undefined;
|
||||
processName: string | null | undefined;
|
||||
processPid: number | null | undefined;
|
||||
|
@ -42,6 +43,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
eventCode,
|
||||
hostName,
|
||||
id,
|
||||
isDraggable,
|
||||
processExecutable,
|
||||
processName,
|
||||
processPid,
|
||||
|
@ -56,6 +58,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
hostName={hostName}
|
||||
isDraggable={isDraggable}
|
||||
userDomain={userDomain}
|
||||
userName={userName}
|
||||
workingDirectory={undefined}
|
||||
|
@ -71,6 +74,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="dns.question.name"
|
||||
isDraggable={isDraggable}
|
||||
value={dnsQuestionName}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -87,6 +91,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="dns.question.type"
|
||||
isDraggable={isDraggable}
|
||||
value={dnsQuestionType}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -103,6 +108,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="dns.resolved_ip"
|
||||
isDraggable={isDraggable}
|
||||
value={dnsResolvedIp}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -122,6 +128,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="dns.response_code"
|
||||
isDraggable={isDraggable}
|
||||
value={dnsResponseCode}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -141,6 +148,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
endgamePid={undefined}
|
||||
endgameProcessName={undefined}
|
||||
eventId={id}
|
||||
isDraggable={isDraggable}
|
||||
processPid={processPid}
|
||||
processName={processName}
|
||||
processExecutable={processExecutable}
|
||||
|
@ -155,6 +163,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="event.code"
|
||||
isDraggable={isDraggable}
|
||||
value={eventCode}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -165,6 +174,7 @@ export const DnsRequestEventDetailsLine = React.memo<Props>(
|
|||
eventId={id}
|
||||
iconType="logoWindows"
|
||||
field="winlog.event_id"
|
||||
isDraggable={isDraggable}
|
||||
value={winlogEventId}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
|
|
@ -60,6 +60,7 @@ export const emptyColumnRenderer: ColumnRenderer = {
|
|||
kqlQuery: '',
|
||||
and: [],
|
||||
}}
|
||||
isDraggable={isDraggable}
|
||||
key={`empty-column-renderer-draggable-wrapper-${timelineId}-${columnName}-${eventId}-${field.id}`}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
|
|
|
@ -20,65 +20,75 @@ interface Props {
|
|||
browserFields: BrowserFields;
|
||||
contextId: string;
|
||||
data: Ecs;
|
||||
isDraggable?: boolean;
|
||||
timelineId: string;
|
||||
}
|
||||
|
||||
export const EndgameSecurityEventDetails = React.memo<Props>(({ data, contextId, timelineId }) => {
|
||||
const endgameLogonType: number | null | undefined = get('endgame.logon_type[0]', data);
|
||||
const endgameSubjectDomainName: string | null | undefined = get(
|
||||
'endgame.subject_domain_name[0]',
|
||||
data
|
||||
);
|
||||
const endgameSubjectLogonId: string | null | undefined = get('endgame.subject_logon_id[0]', data);
|
||||
const endgameSubjectUserName: string | null | undefined = get(
|
||||
'endgame.subject_user_name[0]',
|
||||
data
|
||||
);
|
||||
const endgameTargetLogonId: string | null | undefined = get('endgame.target_logon_id[0]', data);
|
||||
const endgameTargetDomainName: string | null | undefined = get(
|
||||
'endgame.target_domain_name[0]',
|
||||
data
|
||||
);
|
||||
const endgameTargetUserName: string | null | undefined = get('endgame.target_user_name[0]', data);
|
||||
const eventAction: string | null | undefined = get('event.action[0]', data);
|
||||
const eventCode: string | null | undefined = get('event.code[0]', data);
|
||||
const eventOutcome: string | null | undefined = get('event.outcome[0]', data);
|
||||
const hostName: string | null | undefined = get('host.name[0]', data);
|
||||
const id = data._id;
|
||||
const processExecutable: string | null | undefined = get('process.executable[0]', data);
|
||||
const processName: string | null | undefined = get('process.name[0]', data);
|
||||
const processPid: number | null | undefined = get('process.pid[0]', data);
|
||||
const userDomain: string | null | undefined = get('user.domain[0]', data);
|
||||
const userName: string | null | undefined = get('user.name[0]', data);
|
||||
const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
|
||||
export const EndgameSecurityEventDetails = React.memo<Props>(
|
||||
({ data, contextId, isDraggable, timelineId }) => {
|
||||
const endgameLogonType: number | null | undefined = get('endgame.logon_type[0]', data);
|
||||
const endgameSubjectDomainName: string | null | undefined = get(
|
||||
'endgame.subject_domain_name[0]',
|
||||
data
|
||||
);
|
||||
const endgameSubjectLogonId: string | null | undefined = get(
|
||||
'endgame.subject_logon_id[0]',
|
||||
data
|
||||
);
|
||||
const endgameSubjectUserName: string | null | undefined = get(
|
||||
'endgame.subject_user_name[0]',
|
||||
data
|
||||
);
|
||||
const endgameTargetLogonId: string | null | undefined = get('endgame.target_logon_id[0]', data);
|
||||
const endgameTargetDomainName: string | null | undefined = get(
|
||||
'endgame.target_domain_name[0]',
|
||||
data
|
||||
);
|
||||
const endgameTargetUserName: string | null | undefined = get(
|
||||
'endgame.target_user_name[0]',
|
||||
data
|
||||
);
|
||||
const eventAction: string | null | undefined = get('event.action[0]', data);
|
||||
const eventCode: string | null | undefined = get('event.code[0]', data);
|
||||
const eventOutcome: string | null | undefined = get('event.outcome[0]', data);
|
||||
const hostName: string | null | undefined = get('host.name[0]', data);
|
||||
const id = data._id;
|
||||
const processExecutable: string | null | undefined = get('process.executable[0]', data);
|
||||
const processName: string | null | undefined = get('process.name[0]', data);
|
||||
const processPid: number | null | undefined = get('process.pid[0]', data);
|
||||
const userDomain: string | null | undefined = get('user.domain[0]', data);
|
||||
const userName: string | null | undefined = get('user.name[0]', data);
|
||||
const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data);
|
||||
|
||||
return (
|
||||
<Details>
|
||||
<EndgameSecurityEventDetailsLine
|
||||
contextId={contextId}
|
||||
endgameLogonType={endgameLogonType}
|
||||
endgameSubjectDomainName={endgameSubjectDomainName}
|
||||
endgameSubjectLogonId={endgameSubjectLogonId}
|
||||
endgameSubjectUserName={endgameSubjectUserName}
|
||||
endgameTargetDomainName={endgameTargetDomainName}
|
||||
endgameTargetLogonId={endgameTargetLogonId}
|
||||
endgameTargetUserName={endgameTargetUserName}
|
||||
eventAction={eventAction}
|
||||
eventCode={eventCode}
|
||||
eventOutcome={eventOutcome}
|
||||
hostName={hostName}
|
||||
id={id}
|
||||
processExecutable={processExecutable}
|
||||
processName={processName}
|
||||
processPid={processPid}
|
||||
userDomain={userDomain}
|
||||
userName={userName}
|
||||
winlogEventId={winlogEventId}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<NetflowRenderer data={data} timelineId={timelineId} />
|
||||
</Details>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Details>
|
||||
<EndgameSecurityEventDetailsLine
|
||||
contextId={contextId}
|
||||
endgameLogonType={endgameLogonType}
|
||||
endgameSubjectDomainName={endgameSubjectDomainName}
|
||||
endgameSubjectLogonId={endgameSubjectLogonId}
|
||||
endgameSubjectUserName={endgameSubjectUserName}
|
||||
endgameTargetDomainName={endgameTargetDomainName}
|
||||
endgameTargetLogonId={endgameTargetLogonId}
|
||||
endgameTargetUserName={endgameTargetUserName}
|
||||
eventAction={eventAction}
|
||||
eventCode={eventCode}
|
||||
eventOutcome={eventOutcome}
|
||||
hostName={hostName}
|
||||
id={id}
|
||||
isDraggable={isDraggable}
|
||||
processExecutable={processExecutable}
|
||||
processName={processName}
|
||||
processPid={processPid}
|
||||
userDomain={userDomain}
|
||||
userName={userName}
|
||||
winlogEventId={winlogEventId}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<NetflowRenderer data={data} timelineId={timelineId} />
|
||||
</Details>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EndgameSecurityEventDetails.displayName = 'EndgameSecurityEventDetails';
|
||||
|
|
|
@ -38,6 +38,7 @@ interface Props {
|
|||
eventOutcome: string | null | undefined;
|
||||
hostName: string | null | undefined;
|
||||
id: string;
|
||||
isDraggable?: boolean;
|
||||
processExecutable: string | null | undefined;
|
||||
processName: string | null | undefined;
|
||||
processPid: number | null | undefined;
|
||||
|
@ -61,6 +62,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
eventOutcome,
|
||||
hostName,
|
||||
id,
|
||||
isDraggable,
|
||||
processExecutable,
|
||||
processName,
|
||||
processPid,
|
||||
|
@ -95,6 +97,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
eventId={id}
|
||||
hostName={hostName}
|
||||
hostNameSeparator={hostNameSeparator}
|
||||
isDraggable={isDraggable}
|
||||
userDomain={domain}
|
||||
userDomainField={userDomainField}
|
||||
userName={user}
|
||||
|
@ -116,6 +119,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="endgame.logon_type"
|
||||
isDraggable={isDraggable}
|
||||
queryValue={String(endgameLogonType)}
|
||||
value={`${endgameLogonType} - ${getHumanReadableLogonType(endgameLogonType)}`}
|
||||
/>
|
||||
|
@ -136,6 +140,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="endgame.target_logon_id"
|
||||
isDraggable={isDraggable}
|
||||
value={endgameTargetLogonId}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -155,6 +160,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
endgamePid={undefined}
|
||||
endgameProcessName={undefined}
|
||||
eventId={id}
|
||||
isDraggable={isDraggable}
|
||||
processPid={processPid}
|
||||
processName={processName}
|
||||
processExecutable={processExecutable}
|
||||
|
@ -176,6 +182,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="endgame.subject_user_name"
|
||||
isDraggable={isDraggable}
|
||||
iconType="user"
|
||||
value={endgameSubjectUserName}
|
||||
/>
|
||||
|
@ -197,6 +204,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="endgame.subject_domain_name"
|
||||
isDraggable={isDraggable}
|
||||
value={endgameSubjectDomainName}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -216,6 +224,7 @@ export const EndgameSecurityEventDetailsLine = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={id}
|
||||
field="endgame.subject_logon_id"
|
||||
isDraggable={isDraggable}
|
||||
value={endgameSubjectLogonId}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
|
|
@ -15,12 +15,13 @@ interface Props {
|
|||
contextId: string;
|
||||
endgameExitCode: string | null | undefined;
|
||||
eventId: string;
|
||||
isDraggable?: boolean;
|
||||
processExitCode: number | null | undefined;
|
||||
text: string | null | undefined;
|
||||
}
|
||||
|
||||
export const ExitCodeDraggable = React.memo<Props>(
|
||||
({ contextId, endgameExitCode, eventId, processExitCode, text }) => {
|
||||
({ contextId, endgameExitCode, eventId, isDraggable, processExitCode, text }) => {
|
||||
if (isNillEmptyOrNotFinite(processExitCode) && isNillEmptyOrNotFinite(endgameExitCode)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -39,6 +40,7 @@ export const ExitCodeDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="process.exit_code"
|
||||
isDraggable={isDraggable}
|
||||
value={`${processExitCode}`}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
@ -50,6 +52,7 @@ export const ExitCodeDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="endgame.exit_code"
|
||||
isDraggable={isDraggable}
|
||||
value={endgameExitCode}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
|
|
|
@ -20,6 +20,7 @@ interface Props {
|
|||
fileName: string | null | undefined;
|
||||
filePath: string | null | undefined;
|
||||
fileExtOriginalPath: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const FileDraggable = React.memo<Props>(
|
||||
|
@ -31,6 +32,7 @@ export const FileDraggable = React.memo<Props>(
|
|||
fileExtOriginalPath,
|
||||
fileName,
|
||||
filePath,
|
||||
isDraggable,
|
||||
}) => {
|
||||
if (
|
||||
isNillEmptyOrNotFinite(fileName) &&
|
||||
|
@ -52,6 +54,7 @@ export const FileDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="file.name"
|
||||
isDraggable={isDraggable}
|
||||
value={fileName}
|
||||
iconType="document"
|
||||
/>
|
||||
|
@ -62,6 +65,7 @@ export const FileDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="endgame.file_name"
|
||||
isDraggable={isDraggable}
|
||||
value={endgameFileName}
|
||||
iconType="document"
|
||||
/>
|
||||
|
@ -80,6 +84,7 @@ export const FileDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="file.path"
|
||||
isDraggable={isDraggable}
|
||||
value={filePath}
|
||||
iconType="document"
|
||||
/>
|
||||
|
@ -90,6 +95,7 @@ export const FileDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="endgame.file_path"
|
||||
isDraggable={isDraggable}
|
||||
value={endgameFilePath}
|
||||
iconType="document"
|
||||
/>
|
||||
|
@ -106,6 +112,7 @@ export const FileDraggable = React.memo<Props>(
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="file.Ext.original.path"
|
||||
isDraggable={isDraggable}
|
||||
value={fileExtOriginalPath}
|
||||
iconType="document"
|
||||
/>
|
||||
|
|
|
@ -21,9 +21,10 @@ interface Props {
|
|||
contextId: string;
|
||||
eventId: string;
|
||||
fileHashSha256: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const FileHash = React.memo<Props>(({ contextId, eventId, fileHashSha256 }) => {
|
||||
export const FileHash = React.memo<Props>(({ contextId, eventId, fileHashSha256, isDraggable }) => {
|
||||
if (isNillEmptyOrNotFinite(fileHashSha256)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -35,6 +36,7 @@ export const FileHash = React.memo<Props>(({ contextId, eventId, fileHashSha256
|
|||
contextId={contextId}
|
||||
eventId={eventId}
|
||||
field="file.hash.sha256"
|
||||
isDraggable={isDraggable}
|
||||
iconType="number"
|
||||
value={fileHashSha256}
|
||||
/>
|
||||
|
|
|
@ -86,6 +86,7 @@ const FormattedFieldValueComponent: React.FC<{
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={null}
|
||||
value={`${value}`}
|
||||
>
|
||||
|
@ -214,6 +215,7 @@ const FormattedFieldValueComponent: React.FC<{
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
value={`${value}`}
|
||||
tooltipContent={
|
||||
fieldType === DATE_FIELD_TYPE || fieldType === EVENT_DURATION_FIELD_NAME
|
||||
|
|
|
@ -82,6 +82,7 @@ export const RenderRuleName: React.FC<RenderRuleNameProps> = ({
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={value}
|
||||
value={value}
|
||||
>
|
||||
|
@ -95,6 +96,7 @@ export const RenderRuleName: React.FC<RenderRuleNameProps> = ({
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${ruleId}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={value}
|
||||
value={`${value}`}
|
||||
>
|
||||
|
@ -150,6 +152,7 @@ export const renderEventModule = ({
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${moduleName}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={value}
|
||||
value={value}
|
||||
>
|
||||
|
@ -218,6 +221,7 @@ export const renderUrl = ({
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}-${urlName}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={value}
|
||||
value={value}
|
||||
>
|
||||
|
|
|
@ -54,6 +54,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonSuricata,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
|
||||
|
@ -66,6 +67,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: nonSuricata,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -81,6 +83,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: suricata,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -99,6 +102,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: suricata,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -117,6 +121,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: zeek,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -135,6 +140,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: system,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
@ -153,6 +159,7 @@ describe('get_column_renderer', () => {
|
|||
const row = rowRenderer?.renderRow({
|
||||
browserFields: mockBrowserFields,
|
||||
data: auditd,
|
||||
isDraggable: true,
|
||||
timelineId: 'test',
|
||||
});
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -93,6 +93,7 @@ const HostNameComponent: React.FC<Props> = ({
|
|||
<DefaultDraggable
|
||||
field={fieldName}
|
||||
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
|
||||
isDraggable={isDraggable}
|
||||
tooltipContent={fieldName}
|
||||
value={hostName}
|
||||
>
|
||||
|
|
|
@ -17,10 +17,11 @@ interface Props {
|
|||
eventId: string;
|
||||
hostName: string | null | undefined;
|
||||
workingDirectory: string | null | undefined;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const HostWorkingDir = React.memo<Props>(
|
||||
({ contextId, eventId, hostName, workingDirectory }) => (
|
||||
({ contextId, eventId, hostName, workingDirectory, isDraggable }) => (
|
||||
<>
|
||||
<TokensFlexItem grow={false} component="span">
|
||||
<DraggableBadge
|
||||
|
@ -28,6 +29,7 @@ export const HostWorkingDir = React.memo<Props>(
|
|||
eventId={eventId}
|
||||
field="host.name"
|
||||
value={hostName}
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
{workingDirectory != null && (
|
||||
|
@ -42,6 +44,7 @@ export const HostWorkingDir = React.memo<Props>(
|
|||
field="process.working_directory"
|
||||
value={workingDirectory}
|
||||
iconType="folderOpen"
|
||||
isDraggable={isDraggable}
|
||||
/>
|
||||
</TokensFlexItem>
|
||||
</>
|
||||
|
|
|
@ -60,52 +60,58 @@ import {
|
|||
interface NetflowRendererProps {
|
||||
data: Ecs;
|
||||
timelineId: string;
|
||||
isDraggable?: boolean;
|
||||
}
|
||||
|
||||
export const NetflowRenderer = React.memo<NetflowRendererProps>(({ data, timelineId }) => (
|
||||
<Netflow
|
||||
contextId={`netflow-renderer-${timelineId}-${data._id}`}
|
||||
destinationBytes={asArrayIfExists(get(DESTINATION_BYTES_FIELD_NAME, data))}
|
||||
destinationGeoContinentName={asArrayIfExists(
|
||||
get(DESTINATION_GEO_CONTINENT_NAME_FIELD_NAME, data)
|
||||
)}
|
||||
destinationGeoCountryName={asArrayIfExists(get(DESTINATION_GEO_COUNTRY_NAME_FIELD_NAME, data))}
|
||||
destinationGeoCountryIsoCode={asArrayIfExists(
|
||||
get(DESTINATION_GEO_COUNTRY_ISO_CODE_FIELD_NAME, data)
|
||||
)}
|
||||
destinationGeoRegionName={asArrayIfExists(get(DESTINATION_GEO_REGION_NAME_FIELD_NAME, data))}
|
||||
destinationGeoCityName={asArrayIfExists(get(DESTINATION_GEO_CITY_NAME_FIELD_NAME, data))}
|
||||
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, data))}
|
||||
destinationPackets={asArrayIfExists(get(DESTINATION_PACKETS_FIELD_NAME, data))}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, data))}
|
||||
eventDuration={asArrayIfExists(get(EVENT_DURATION_FIELD_NAME, data))}
|
||||
eventId={get(ID_FIELD_NAME, data)}
|
||||
eventEnd={asArrayIfExists(get(EVENT_END_FIELD_NAME, data))}
|
||||
eventStart={asArrayIfExists(get(EVENT_START_FIELD_NAME, data))}
|
||||
networkBytes={asArrayIfExists(get(NETWORK_BYTES_FIELD_NAME, data))}
|
||||
networkCommunityId={asArrayIfExists(get(NETWORK_COMMUNITY_ID_FIELD_NAME, data))}
|
||||
networkDirection={asArrayIfExists(get(NETWORK_DIRECTION_FIELD_NAME, data))}
|
||||
networkPackets={asArrayIfExists(get(NETWORK_PACKETS_FIELD_NAME, data))}
|
||||
networkProtocol={asArrayIfExists(get(NETWORK_PROTOCOL_FIELD_NAME, data))}
|
||||
sourceBytes={asArrayIfExists(get(SOURCE_BYTES_FIELD_NAME, data))}
|
||||
sourceGeoContinentName={asArrayIfExists(get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, data))}
|
||||
sourceGeoCountryName={asArrayIfExists(get(SOURCE_GEO_COUNTRY_NAME_FIELD_NAME, data))}
|
||||
sourceGeoCountryIsoCode={asArrayIfExists(get(SOURCE_GEO_COUNTRY_ISO_CODE_FIELD_NAME, data))}
|
||||
sourceGeoRegionName={asArrayIfExists(get(SOURCE_GEO_REGION_NAME_FIELD_NAME, data))}
|
||||
sourceGeoCityName={asArrayIfExists(get(SOURCE_GEO_CITY_NAME_FIELD_NAME, data))}
|
||||
sourceIp={asArrayIfExists(get(SOURCE_IP_FIELD_NAME, data))}
|
||||
sourcePackets={asArrayIfExists(get(SOURCE_PACKETS_FIELD_NAME, data))}
|
||||
sourcePort={asArrayIfExists(get(SOURCE_PORT_FIELD_NAME, data))}
|
||||
tlsClientCertificateFingerprintSha1={asArrayIfExists(
|
||||
get(TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME, data)
|
||||
)}
|
||||
tlsFingerprintsJa3Hash={asArrayIfExists(get(JA3_HASH_FIELD_NAME, data))}
|
||||
tlsServerCertificateFingerprintSha1={asArrayIfExists(
|
||||
get(TLS_SERVER_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME, data)
|
||||
)}
|
||||
transport={asArrayIfExists(get(NETWORK_TRANSPORT_FIELD_NAME, data))}
|
||||
userName={undefined}
|
||||
/>
|
||||
));
|
||||
export const NetflowRenderer = React.memo<NetflowRendererProps>(
|
||||
({ data, timelineId, isDraggable }) => (
|
||||
<Netflow
|
||||
contextId={`netflow-renderer-${timelineId}-${data._id}`}
|
||||
destinationBytes={asArrayIfExists(get(DESTINATION_BYTES_FIELD_NAME, data))}
|
||||
destinationGeoContinentName={asArrayIfExists(
|
||||
get(DESTINATION_GEO_CONTINENT_NAME_FIELD_NAME, data)
|
||||
)}
|
||||
destinationGeoCountryName={asArrayIfExists(
|
||||
get(DESTINATION_GEO_COUNTRY_NAME_FIELD_NAME, data)
|
||||
)}
|
||||
destinationGeoCountryIsoCode={asArrayIfExists(
|
||||
get(DESTINATION_GEO_COUNTRY_ISO_CODE_FIELD_NAME, data)
|
||||
)}
|
||||
destinationGeoRegionName={asArrayIfExists(get(DESTINATION_GEO_REGION_NAME_FIELD_NAME, data))}
|
||||
destinationGeoCityName={asArrayIfExists(get(DESTINATION_GEO_CITY_NAME_FIELD_NAME, data))}
|
||||
destinationIp={asArrayIfExists(get(DESTINATION_IP_FIELD_NAME, data))}
|
||||
destinationPackets={asArrayIfExists(get(DESTINATION_PACKETS_FIELD_NAME, data))}
|
||||
destinationPort={asArrayIfExists(get(DESTINATION_PORT_FIELD_NAME, data))}
|
||||
eventDuration={asArrayIfExists(get(EVENT_DURATION_FIELD_NAME, data))}
|
||||
eventId={get(ID_FIELD_NAME, data)}
|
||||
eventEnd={asArrayIfExists(get(EVENT_END_FIELD_NAME, data))}
|
||||
eventStart={asArrayIfExists(get(EVENT_START_FIELD_NAME, data))}
|
||||
isDraggable={isDraggable}
|
||||
networkBytes={asArrayIfExists(get(NETWORK_BYTES_FIELD_NAME, data))}
|
||||
networkCommunityId={asArrayIfExists(get(NETWORK_COMMUNITY_ID_FIELD_NAME, data))}
|
||||
networkDirection={asArrayIfExists(get(NETWORK_DIRECTION_FIELD_NAME, data))}
|
||||
networkPackets={asArrayIfExists(get(NETWORK_PACKETS_FIELD_NAME, data))}
|
||||
networkProtocol={asArrayIfExists(get(NETWORK_PROTOCOL_FIELD_NAME, data))}
|
||||
sourceBytes={asArrayIfExists(get(SOURCE_BYTES_FIELD_NAME, data))}
|
||||
sourceGeoContinentName={asArrayIfExists(get(SOURCE_GEO_CONTINENT_NAME_FIELD_NAME, data))}
|
||||
sourceGeoCountryName={asArrayIfExists(get(SOURCE_GEO_COUNTRY_NAME_FIELD_NAME, data))}
|
||||
sourceGeoCountryIsoCode={asArrayIfExists(get(SOURCE_GEO_COUNTRY_ISO_CODE_FIELD_NAME, data))}
|
||||
sourceGeoRegionName={asArrayIfExists(get(SOURCE_GEO_REGION_NAME_FIELD_NAME, data))}
|
||||
sourceGeoCityName={asArrayIfExists(get(SOURCE_GEO_CITY_NAME_FIELD_NAME, data))}
|
||||
sourceIp={asArrayIfExists(get(SOURCE_IP_FIELD_NAME, data))}
|
||||
sourcePackets={asArrayIfExists(get(SOURCE_PACKETS_FIELD_NAME, data))}
|
||||
sourcePort={asArrayIfExists(get(SOURCE_PORT_FIELD_NAME, data))}
|
||||
tlsClientCertificateFingerprintSha1={asArrayIfExists(
|
||||
get(TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME, data)
|
||||
)}
|
||||
tlsFingerprintsJa3Hash={asArrayIfExists(get(JA3_HASH_FIELD_NAME, data))}
|
||||
tlsServerCertificateFingerprintSha1={asArrayIfExists(
|
||||
get(TLS_SERVER_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME, data)
|
||||
)}
|
||||
transport={asArrayIfExists(get(NETWORK_TRANSPORT_FIELD_NAME, data))}
|
||||
userName={undefined}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
NetflowRenderer.displayName = 'NetflowRenderer';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue