Skip to content

Commit d4656bb

Browse files
[Security solutions][Intelligence] Upgrade ioc flyout (elastic#230593)
## Summary Fixes elastic#189870. Upgrading intelligence flyout look-and-feel to be more like the alert's flyout. ## Screenshots <img width="1282" height="1222" alt="Screenshot 2025-08-29 at 12 19 51" src="https://github.com/user-attachments/assets/6b9825ae-32a5-4dc7-9133-94c0cac770e2" /> <img width="1281" height="1222" alt="Screenshot 2025-08-29 at 12 20 05" src="https://github.com/user-attachments/assets/4196e583-2d70-429b-9f83-655cd67cf00a" /> <img width="1281" height="1222" alt="Screenshot 2025-08-29 at 12 20 16" src="https://github.com/user-attachments/assets/7fd7949d-1317-4437-919b-bdbe9585b750" /> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 15b74e3 commit d4656bb

27 files changed

Lines changed: 273 additions & 460 deletions

File tree

x-pack/platform/plugins/private/translations/translations/de-DE.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38113,8 +38113,6 @@
3811338113
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewMessage": "Der Untersuchungsleitfaden ist in der Warnungsvorschau nicht verfügbar.",
3811438114
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewTitle": "Untersuchungsleitfaden",
3811538115
"xpack.securitySolution.flyout.right.investigation.sectionTitle": "Untersuchung",
38116-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel": "In die Zwischenablage kopieren",
38117-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel": "In die Zwischenablage kopieren",
3811838116
"xpack.securitySolution.flyout.right.network.networkPreviewTitle": "Netzwerkdetails anzeigen",
3811938117
"xpack.securitySolution.flyout.right.notes.addNoteButtonLabel": "Notiz hinzufügen",
3812038118
"xpack.securitySolution.flyout.right.notes.fetchNotesErrorLabel": "Fehler beim Abrufen von Anmerkungen",

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38216,8 +38216,6 @@
3821638216
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewMessage": "Le guide d'investigation n'est pas disponible dans l'aperçu des alertes.",
3821738217
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewTitle": "Guide d'investigation",
3821838218
"xpack.securitySolution.flyout.right.investigation.sectionTitle": "Investigation",
38219-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel": "Copier dans le presse-papiers",
38220-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel": "Copier dans le presse-papiers",
3822138219
"xpack.securitySolution.flyout.right.network.networkPreviewTitle": "Aperçu des détails du réseau",
3822238220
"xpack.securitySolution.flyout.right.notes.addNoteButtonLabel": "Ajouter la note",
3822338221
"xpack.securitySolution.flyout.right.notes.fetchNotesErrorLabel": "Erreur lors de la récupération des notes",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38260,8 +38260,6 @@
3826038260
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewMessage": "調査ガイドはアラートプレビューでは使用できません。",
3826138261
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewTitle": "調査ガイド",
3826238262
"xpack.securitySolution.flyout.right.investigation.sectionTitle": "調査",
38263-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel": "クリップボードにコピー",
38264-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel": "クリップボードにコピー",
3826538263
"xpack.securitySolution.flyout.right.network.networkPreviewTitle": "ネットワーク詳細をプレビュー",
3826638264
"xpack.securitySolution.flyout.right.notes.addNoteButtonLabel": "メモを追加",
3826738265
"xpack.securitySolution.flyout.right.notes.fetchNotesErrorLabel": "メモの取得エラー",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38244,8 +38244,6 @@
3824438244
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewMessage": "调查指南在告警预览中不可用。",
3824538245
"xpack.securitySolution.flyout.right.investigation.investigationGuide.previewTitle": "调查指南",
3824638246
"xpack.securitySolution.flyout.right.investigation.sectionTitle": "调查",
38247-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel": "复制到剪贴板",
38248-
"xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel": "复制到剪贴板",
3824938247
"xpack.securitySolution.flyout.right.network.networkPreviewTitle": "预览网络详情",
3825038248
"xpack.securitySolution.flyout.right.notes.addNoteButtonLabel": "添加备注",
3825138249
"xpack.securitySolution.flyout.right.notes.fetchNotesErrorLabel": "提取备注时出错",

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/tabs/json_tab.test.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { render } from '@testing-library/react';
1010
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
1111
import { DocumentDetailsContext } from '../../shared/context';
1212
import { JsonTab } from './json_tab';
13-
import { JSON_TAB_CONTENT_TEST_ID, JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID } from './test_ids';
13+
import {
14+
JSON_TAB_CONTENT_TEST_ID,
15+
JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID,
16+
} from '../../../shared/components/json_tab';
17+
import { PREFIX } from '../../../shared/test_ids';
1418

1519
jest.mock('@elastic/eui', () => ({
1620
...jest.requireActual('@elastic/eui'),
@@ -37,13 +41,13 @@ describe('<JsonTab />', () => {
3741
it('should render json code editor component', () => {
3842
const { getByTestId } = renderJsonTab();
3943

40-
expect(getByTestId(JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument();
44+
expect(getByTestId(PREFIX + JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument();
4145
});
4246

4347
it('should copy to clipboard', () => {
4448
const { getByTestId } = renderJsonTab();
4549

46-
const copyToClipboardButton = getByTestId(JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID);
50+
const copyToClipboardButton = getByTestId(PREFIX + JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID);
4751
expect(copyToClipboardButton).toBeInTheDocument();
4852
});
4953
});

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/tabs/json_tab.tsx

Lines changed: 8 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,88 +5,23 @@
55
* 2.0.
66
*/
77

8-
import React, { memo, useEffect, useRef, useState } from 'react';
9-
import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public';
10-
import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
11-
import { i18n } from '@kbn/i18n';
12-
import { FormattedMessage } from '@kbn/i18n-react';
13-
import { JSON_TAB_CONTENT_TEST_ID, JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID } from './test_ids';
8+
import React, { memo } from 'react';
149
import { useDocumentDetailsContext } from '../../shared/context';
15-
16-
const FLYOUT_BODY_PADDING = 24;
17-
const COPY_TO_CLIPBOARD_BUTTON_HEIGHT = 24;
18-
const FLYOUT_FOOTER_HEIGHT = 72;
10+
import { JsonTab as SharedJsonTab } from '../../../shared/components/json_tab';
11+
import { PREFIX } from '../../../shared/test_ids';
1912

2013
/**
2114
* Json view displayed in the document details expandable flyout right section
2215
*/
2316
export const JsonTab = memo(() => {
2417
const { searchHit, isRulePreview } = useDocumentDetailsContext();
25-
const jsonValue = JSON.stringify(searchHit, null, 2);
26-
27-
const flexGroupElement = useRef<HTMLDivElement>(null);
28-
const [editorHeight, setEditorHeight] = useState<number>();
29-
30-
useEffect(() => {
31-
const topPosition = flexGroupElement?.current?.getBoundingClientRect().top || 0;
32-
const footerOffset = isRulePreview ? 0 : FLYOUT_FOOTER_HEIGHT;
33-
const height =
34-
window.innerHeight -
35-
topPosition -
36-
COPY_TO_CLIPBOARD_BUTTON_HEIGHT -
37-
FLYOUT_BODY_PADDING -
38-
footerOffset;
39-
40-
if (height === 0) {
41-
return;
42-
}
43-
44-
setEditorHeight(height);
45-
}, [setEditorHeight, isRulePreview]);
4618

4719
return (
48-
<EuiFlexGroup
49-
ref={flexGroupElement}
50-
direction="column"
51-
gutterSize="none"
52-
data-test-subj={JSON_TAB_CONTENT_TEST_ID}
53-
>
54-
<EuiFlexItem>
55-
<EuiFlexGroup justifyContent={'flexEnd'}>
56-
<EuiFlexItem grow={false}>
57-
<EuiCopy textToCopy={jsonValue}>
58-
{(copy) => (
59-
<EuiButtonEmpty
60-
iconType={'copyClipboard'}
61-
size={'xs'}
62-
aria-label={i18n.translate(
63-
'xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel',
64-
{
65-
defaultMessage: 'Copy to clipboard',
66-
}
67-
)}
68-
data-test-subj={JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID}
69-
onClick={copy}
70-
onKeyDown={copy}
71-
>
72-
<FormattedMessage
73-
id="xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel"
74-
defaultMessage="Copy to clipboard"
75-
/>
76-
</EuiButtonEmpty>
77-
)}
78-
</EuiCopy>
79-
</EuiFlexItem>
80-
</EuiFlexGroup>
81-
</EuiFlexItem>
82-
<EuiFlexItem>
83-
<JsonCodeEditor
84-
json={searchHit as unknown as Record<string, unknown>}
85-
height={editorHeight}
86-
hasLineNumbers={true}
87-
/>
88-
</EuiFlexItem>
89-
</EuiFlexGroup>
20+
<SharedJsonTab
21+
value={searchHit as unknown as Record<string, unknown>}
22+
showFooterOffset={isRulePreview}
23+
data-test-subj={PREFIX}
24+
/>
9025
);
9126
});
9227

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/tabs/test_ids.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,3 @@ import { PREFIX } from '../../../shared/test_ids';
99

1010
export const TABLE_TAB_CONTENT_TEST_ID = `${PREFIX}DocumentTable` as const;
1111
export const TABLE_TAB_SEARCH_INPUT_TEST_ID = `${PREFIX}DocumentTableSearchInput` as const;
12-
export const JSON_TAB_CONTENT_TEST_ID = 'jsonView' as const;
13-
export const JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID = `${PREFIX}JsonTabCopyToClipboard` as const;

x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs/asset_document.test.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ import { FLYOUT_BODY_TEST_ID } from './test_ids';
1313
import { DocumentDetailsContext } from '../../../document_details/shared/context';
1414
import { mockContextValue } from '../../../document_details/shared/mocks/mock_context';
1515
import userEvent from '@testing-library/user-event';
16-
import {
17-
JSON_TAB_CONTENT_TEST_ID,
18-
TABLE_TAB_CONTENT_TEST_ID,
19-
} from '../../../document_details/right/tabs/test_ids';
16+
import { TABLE_TAB_CONTENT_TEST_ID } from '../../../document_details/right/tabs/test_ids';
17+
import { JSON_TAB_CONTENT_TEST_ID } from '../../../shared/components/json_tab';
18+
import { PREFIX } from '../../../shared/test_ids';
2019

2120
describe('AssetDocumentTab', () => {
2221
it('renders', () => {
@@ -54,7 +53,7 @@ describe('AssetDocumentTab', () => {
5453

5554
await userEvent.click(getByTitle('JSON'));
5655

57-
expect(getByTestId(JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument();
56+
expect(getByTestId(PREFIX + JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument();
5857
});
5958

6059
it('should select table tab when path tab is table', async () => {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React, { memo, useEffect, useRef, useState } from 'react';
9+
import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public';
10+
import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
11+
import { i18n } from '@kbn/i18n';
12+
import { FormattedMessage } from '@kbn/i18n-react';
13+
14+
export const JSON_TAB_CONTENT_TEST_ID = 'jsonView' as const;
15+
export const JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID = `JsonTabCopyToClipboard` as const;
16+
17+
// import { useDocumentDetailsContext } from './context';
18+
19+
const FLYOUT_BODY_PADDING = 24;
20+
const COPY_TO_CLIPBOARD_BUTTON_HEIGHT = 24;
21+
const FLYOUT_FOOTER_HEIGHT = 72;
22+
23+
export interface JsonTabProps {
24+
/**
25+
* The data-test-subj to prefix the component one's
26+
*/
27+
['data-test-subj']?: string;
28+
/**
29+
* Use to influence the height of the JsonCodeEditor (in some place the flyout does not have a footer).
30+
*/
31+
showFooterOffset: boolean;
32+
/**
33+
* Json value to render in the JsonCodeEditor
34+
*/
35+
value: Record<string, unknown>;
36+
}
37+
38+
/**
39+
* Json view displayed in the document details expandable flyout right section and in the indicator flyout.
40+
*/
41+
export const JsonTab = memo(
42+
({ value, showFooterOffset, 'data-test-subj': dataTestSubj }: JsonTabProps) => {
43+
const jsonValue = JSON.stringify(value, null, 2);
44+
45+
const flexGroupElement = useRef<HTMLDivElement>(null);
46+
const [editorHeight, setEditorHeight] = useState<number>();
47+
48+
useEffect(() => {
49+
const topPosition = flexGroupElement?.current?.getBoundingClientRect().top || 0;
50+
const footerOffset = showFooterOffset ? 0 : FLYOUT_FOOTER_HEIGHT;
51+
const height =
52+
window.innerHeight -
53+
topPosition -
54+
COPY_TO_CLIPBOARD_BUTTON_HEIGHT -
55+
FLYOUT_BODY_PADDING -
56+
footerOffset;
57+
58+
if (height === 0) {
59+
return;
60+
}
61+
62+
setEditorHeight(height);
63+
}, [setEditorHeight, showFooterOffset]);
64+
65+
return (
66+
<EuiFlexGroup
67+
ref={flexGroupElement}
68+
direction="column"
69+
gutterSize="none"
70+
data-test-subj={`${dataTestSubj}${JSON_TAB_CONTENT_TEST_ID}`}
71+
>
72+
<EuiFlexItem>
73+
<EuiFlexGroup justifyContent={'flexEnd'}>
74+
<EuiFlexItem grow={false}>
75+
<EuiCopy textToCopy={jsonValue}>
76+
{(copy) => (
77+
<EuiButtonEmpty
78+
iconType={'copyClipboard'}
79+
size={'xs'}
80+
aria-label={i18n.translate(
81+
'xpack.securitySolution.flyout.shared.jsonTab.copyToClipboardButtonAriaLabel',
82+
{
83+
defaultMessage: 'Copy to clipboard',
84+
}
85+
)}
86+
data-test-subj={`${dataTestSubj}${JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID}`}
87+
onClick={copy}
88+
onKeyDown={copy}
89+
>
90+
<FormattedMessage
91+
id="xpack.securitySolution.flyout.shared.jsonTab.copyToClipboardButtonLabel"
92+
defaultMessage="Copy to clipboard"
93+
/>
94+
</EuiButtonEmpty>
95+
)}
96+
</EuiCopy>
97+
</EuiFlexItem>
98+
</EuiFlexGroup>
99+
</EuiFlexItem>
100+
<EuiFlexItem>
101+
<JsonCodeEditor json={value} height={editorHeight} hasLineNumbers={true} />
102+
</EuiFlexItem>
103+
</EuiFlexGroup>
104+
);
105+
}
106+
);
107+
108+
JsonTab.displayName = 'JsonTab';

x-pack/solutions/security/plugins/security_solution/public/threat_intelligence/modules/indicators/components/flyout/block.tsx

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,15 @@
88
import { EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
99
import type { FC } from 'react';
1010
import React from 'react';
11-
import { css, euiStyled } from '@kbn/kibana-react-plugin/common';
1211
import type { Indicator } from '../../../../../../common/threat_intelligence/types/indicator';
1312
import { IndicatorFieldValue } from '../common/field_value';
1413
import { IndicatorFieldLabel } from '../common/field_label';
15-
import { IndicatorValueActions } from './indicator_value_actions';
16-
17-
/**
18-
* Show actions wrapper on hover. This is a helper component, limited only to Block
19-
*/
20-
const VisibleOnHover = euiStyled.div`
21-
${({ theme }) => css`
22-
& {
23-
height: 100%;
24-
}
25-
26-
& .actionsWrapper {
27-
visibility: hidden;
28-
display: inline-block;
29-
margin-inline-start: ${theme.eui.euiSizeS};
30-
}
31-
32-
&:hover .actionsWrapper {
33-
visibility: visible;
34-
}
35-
`}
36-
`;
14+
import {
15+
CellActionsMode,
16+
SecurityCellActions,
17+
SecurityCellActionsTrigger,
18+
} from '../../../../../common/components/cell_actions';
19+
import { getIndicatorFieldAndValue } from '../../utils/field_value';
3720

3821
const panelProps = {
3922
color: 'subdued' as const,
@@ -56,24 +39,23 @@ export const IndicatorBlock: FC<IndicatorBlockProps> = ({
5639
indicator,
5740
'data-test-subj': dataTestSubj,
5841
}) => {
42+
const { key, value } = getIndicatorFieldAndValue(indicator, field);
43+
5944
return (
60-
<EuiPanel {...panelProps}>
61-
<VisibleOnHover data-test-subj={`${dataTestSubj}Item`}>
45+
<EuiPanel {...panelProps} data-test-subj={`${dataTestSubj}Item`}>
46+
<SecurityCellActions
47+
data={{ field: key, value }}
48+
mode={CellActionsMode.HOVER_DOWN}
49+
triggerId={SecurityCellActionsTrigger.DEFAULT}
50+
>
6251
<EuiText>
6352
<IndicatorFieldLabel field={field} />
6453
</EuiText>
6554
<EuiSpacer size="s" />
6655
<EuiText size="s">
6756
<IndicatorFieldValue indicator={indicator} field={field} />
68-
<span className="actionsWrapper">
69-
<IndicatorValueActions
70-
indicator={indicator}
71-
field={field}
72-
data-test-subj={dataTestSubj}
73-
/>
74-
</span>
7557
</EuiText>
76-
</VisibleOnHover>
58+
</SecurityCellActions>
7759
</EuiPanel>
7860
);
7961
};

0 commit comments

Comments
 (0)