[Security Solution][Investigations] Fix overlapping tooltips (#117874)
* feat: allow to pass `tooltipPosition` to `DefaultDraggable` * fix: prevent suricata field name and google url tooltips from overlapping * test: add test for passing the tooltipPosition * chore: distinguish between type imports and regular imports * test: update suricata signature snapshots * test: make sure that suricata signature tooltips do not overlap Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
0b5a434d19
commit
d0e30f5475
|
@ -20,3 +20,18 @@ export const formatMitreAttackDescription = (mitre: Mitre[]) => {
|
|||
)
|
||||
.join('');
|
||||
};
|
||||
|
||||
export const elementsOverlap = ($element1: JQuery<HTMLElement>, $element2: JQuery<HTMLElement>) => {
|
||||
const rectA = $element1[0].getBoundingClientRect();
|
||||
const rectB = $element2[0].getBoundingClientRect();
|
||||
|
||||
// If they don't overlap horizontally, they don't overlap
|
||||
if (rectA.right < rectB.left || rectB.right < rectA.left) {
|
||||
return false;
|
||||
} else if (rectA.bottom < rectB.top || rectB.bottom < rectA.top) {
|
||||
// If they don't overlap vertically, they don't overlap
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,12 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { elementsOverlap } from '../../helpers/rules';
|
||||
import {
|
||||
TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN,
|
||||
TIMELINE_ROW_RENDERERS_MODAL_CLOSE_BUTTON,
|
||||
TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX,
|
||||
TIMELINE_ROW_RENDERERS_SEARCHBOX,
|
||||
TIMELINE_SHOW_ROW_RENDERERS_GEAR,
|
||||
TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE,
|
||||
TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE_TOOLTIP,
|
||||
TIMELINE_ROW_RENDERERS_SURICATA_LINK_TOOLTIP,
|
||||
} from '../../screens/timeline';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
|
||||
|
@ -81,4 +85,22 @@ describe('Row renderers', () => {
|
|||
|
||||
cy.wait('@updateTimeline').its('response.statusCode').should('eq', 200);
|
||||
});
|
||||
|
||||
describe('Suricata', () => {
|
||||
it('Signature tooltips do not overlap', () => {
|
||||
// Hover the signature to show the tooltips
|
||||
cy.get(TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE)
|
||||
.parents('.euiPopover__anchor')
|
||||
.trigger('mouseover');
|
||||
|
||||
cy.get(TIMELINE_ROW_RENDERERS_SURICATA_LINK_TOOLTIP).then(($googleLinkTooltip) => {
|
||||
cy.get(TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE_TOOLTIP).then(($signatureTooltip) => {
|
||||
expect(
|
||||
elementsOverlap($googleLinkTooltip, $signatureTooltip),
|
||||
'tooltips do not overlap'
|
||||
).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -245,6 +245,12 @@ export const TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX = `${TIMELINE_ROW_RENDE
|
|||
|
||||
export const TIMELINE_ROW_RENDERERS_SEARCHBOX = `${TIMELINE_ROW_RENDERERS_MODAL} input[type="search"]`;
|
||||
|
||||
export const TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE = `${TIMELINE_ROW_RENDERERS_MODAL} [data-test-subj="render-content-suricata.eve.alert.signature"]`;
|
||||
|
||||
export const TIMELINE_ROW_RENDERERS_SURICATA_LINK_TOOLTIP = `[data-test-subj="externalLinkTooltip"]`;
|
||||
|
||||
export const TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE_TOOLTIP = `[data-test-subj="suricata.eve.alert.signature-tooltip"]`;
|
||||
|
||||
export const TIMELINE_SHOW_ROW_RENDERERS_GEAR = '[data-test-subj="show-row-renderers-gear"]';
|
||||
|
||||
export const TIMELINE_TABS = '[data-test-subj="timeline"] .euiTabs';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { DRAGGABLE_KEYBOARD_INSTRUCTIONS_NOT_DRAGGING_SCREEN_READER_ONLY } from '../drag_and_drop/translations';
|
||||
import { TestProviders } from '../../mock';
|
||||
|
@ -326,5 +327,21 @@ describe('draggables', () => {
|
|||
|
||||
expect(wrapper.find('[data-test-subj="some-field-tooltip"]').first().exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('it uses the specified tooltipPosition', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DraggableBadge
|
||||
contextId="context-id"
|
||||
eventId="event-id"
|
||||
field="some-field"
|
||||
value="some value"
|
||||
tooltipPosition="top"
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(EuiToolTip).first().props().position).toEqual('top');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiToolTip, IconType } from '@elastic/eui';
|
||||
import { EuiBadge, EuiToolTip } from '@elastic/eui';
|
||||
import type { IconType, ToolTipPositions } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -29,6 +30,7 @@ export interface DefaultDraggableType {
|
|||
children?: React.ReactNode;
|
||||
timelineId?: string;
|
||||
tooltipContent?: React.ReactNode;
|
||||
tooltipPosition?: ToolTipPositions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,11 +62,13 @@ export const Content = React.memo<{
|
|||
children?: React.ReactNode;
|
||||
field: string;
|
||||
tooltipContent?: React.ReactNode;
|
||||
tooltipPosition?: ToolTipPositions;
|
||||
value?: string | null;
|
||||
}>(({ children, field, tooltipContent, value }) =>
|
||||
}>(({ children, field, tooltipContent, tooltipPosition, value }) =>
|
||||
!tooltipContentIsExplicitlyNull(tooltipContent) ? (
|
||||
<EuiToolTip
|
||||
data-test-subj={`${field}-tooltip`}
|
||||
position={tooltipPosition}
|
||||
content={getDefaultWhenTooltipIsUnspecified({ tooltipContent, field })}
|
||||
>
|
||||
<>{children ? children : value}</>
|
||||
|
@ -88,6 +92,7 @@ Content.displayName = 'Content';
|
|||
* @param children - defaults to displaying `value`, this allows an arbitrary visualization to be displayed in lieu of the default behavior
|
||||
* @param tooltipContent - defaults to displaying `field`, pass `null` to
|
||||
* prevent a tooltip from being displayed, or pass arbitrary content
|
||||
* @param tooltipPosition - defaults to eui's default tooltip position
|
||||
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
|
||||
* @param hideTopN - defaults to `false`, when true, the option to aggregate this field will be hidden
|
||||
*/
|
||||
|
@ -102,6 +107,7 @@ export const DefaultDraggable = React.memo<DefaultDraggableType>(
|
|||
children,
|
||||
timelineId,
|
||||
tooltipContent,
|
||||
tooltipPosition,
|
||||
queryValue,
|
||||
}) => {
|
||||
const dataProviderProp: DataProvider = useMemo(
|
||||
|
@ -128,11 +134,16 @@ export const DefaultDraggable = React.memo<DefaultDraggableType>(
|
|||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
<Content field={field} tooltipContent={tooltipContent} value={value}>
|
||||
<Content
|
||||
field={field}
|
||||
tooltipContent={tooltipContent}
|
||||
tooltipPosition={tooltipPosition}
|
||||
value={value}
|
||||
>
|
||||
{children}
|
||||
</Content>
|
||||
),
|
||||
[children, field, tooltipContent, value]
|
||||
[children, field, tooltipContent, tooltipPosition, value]
|
||||
);
|
||||
|
||||
if (value == null) return null;
|
||||
|
|
|
@ -26,6 +26,7 @@ exports[`SuricataSignature rendering it renders the default SuricataSignature 1`
|
|||
data-test-subj="draggable-signature-link"
|
||||
field="suricata.eve.alert.signature"
|
||||
id="suricata-signature-default-draggable-test-doc-id-123-suricata.eve.alert.signature"
|
||||
tooltipPosition="bottom"
|
||||
value="ET SCAN ATTACK Hello"
|
||||
>
|
||||
<div>
|
||||
|
|
|
@ -130,6 +130,7 @@ export const SuricataSignature = React.memo<{
|
|||
id={`suricata-signature-default-draggable-${contextId}-${id}-${SURICATA_SIGNATURE_FIELD_NAME}`}
|
||||
isDraggable={isDraggable}
|
||||
value={signature}
|
||||
tooltipPosition="bottom"
|
||||
>
|
||||
<div>
|
||||
<GoogleLink link={signature}>
|
||||
|
|
Loading…
Reference in a new issue