[8.15] [Security Solution] Fix - Notes Flyout Product Feedback (#188129) (#188401)

# Backport

This will backport the following commits from `main` to `8.15`:
- [[Security Solution] Fix - Notes Flyout Product Feedback
(#188129)](https://github.com/elastic/kibana/pull/188129)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jatin
Kathuria","email":"jatin.kathuria@elastic.co"},"sourceCommit":{"committedDate":"2024-07-12T17:20:19Z","message":"[Security
Solution] Fix - Notes Flyout Product Feedback (#188129)\n\n#
Summary\r\n\r\nFixes below bugs based on feedback from
@paulewing.\r\n\r\n\r\n## Event Details Toggle in
Notes\r\n\r\n@paulewing requested to remove the event toggle
\r\n\r\n|Before|After|\r\n|---|---|\r\n|![Bildschirmfoto 2024-07-11 um
17
48\r\n15](2b45d3a9-6f1a-4f05-8824-10e2c6265266)|\r\n![Bildschirmfoto
2024-07-11 um 17
46\r\n01](b02c06ff-f556-4894-a588-a88bcdd8bc8c)|\r\n\r\n\r\n##
Notes Flyout remains open when switching
tabs\r\n|Before|After|\r\n|---|---|\r\n|<video\r\nsrc=\"0e010c22-4539-4428-9b1b-3b323a9f491c\"\r\n/>|\r\n\r\n\r\n##
Notes Flyout should be resizable\r\n\r\nAs shown in above video, notes
flyout is now
resizable.","sha":"309b907e59df245236c24f7a3b121488da9dc3e4","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Feature:Timeline","Team:Threat
Hunting:Investigations","backport:prev-minor","v8.16.0"],"number":188129,"url":"https://github.com/elastic/kibana/pull/188129","mergeCommit":{"message":"[Security
Solution] Fix - Notes Flyout Product Feedback (#188129)\n\n#
Summary\r\n\r\nFixes below bugs based on feedback from
@paulewing.\r\n\r\n\r\n## Event Details Toggle in
Notes\r\n\r\n@paulewing requested to remove the event toggle
\r\n\r\n|Before|After|\r\n|---|---|\r\n|![Bildschirmfoto 2024-07-11 um
17
48\r\n15](2b45d3a9-6f1a-4f05-8824-10e2c6265266)|\r\n![Bildschirmfoto
2024-07-11 um 17
46\r\n01](b02c06ff-f556-4894-a588-a88bcdd8bc8c)|\r\n\r\n\r\n##
Notes Flyout remains open when switching
tabs\r\n|Before|After|\r\n|---|---|\r\n|<video\r\nsrc=\"0e010c22-4539-4428-9b1b-3b323a9f491c\"\r\n/>|\r\n\r\n\r\n##
Notes Flyout should be resizable\r\n\r\nAs shown in above video, notes
flyout is now
resizable.","sha":"309b907e59df245236c24f7a3b121488da9dc3e4"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/188129","number":188129,"mergeCommit":{"message":"[Security
Solution] Fix - Notes Flyout Product Feedback (#188129)\n\n#
Summary\r\n\r\nFixes below bugs based on feedback from
@paulewing.\r\n\r\n\r\n## Event Details Toggle in
Notes\r\n\r\n@paulewing requested to remove the event toggle
\r\n\r\n|Before|After|\r\n|---|---|\r\n|![Bildschirmfoto 2024-07-11 um
17
48\r\n15](2b45d3a9-6f1a-4f05-8824-10e2c6265266)|\r\n![Bildschirmfoto
2024-07-11 um 17
46\r\n01](b02c06ff-f556-4894-a588-a88bcdd8bc8c)|\r\n\r\n\r\n##
Notes Flyout remains open when switching
tabs\r\n|Before|After|\r\n|---|---|\r\n|<video\r\nsrc=\"0e010c22-4539-4428-9b1b-3b323a9f491c\"\r\n/>|\r\n\r\n\r\n##
Notes Flyout should be resizable\r\n\r\nAs shown in above video, notes
flyout is now
resizable.","sha":"309b907e59df245236c24f7a3b121488da9dc3e4"}}]}]
BACKPORT-->
This commit is contained in:
Jatin Kathuria 2024-07-16 12:04:42 +02:00 committed by GitHub
parent 02533fa7e7
commit bb0aeff31a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 356 additions and 355 deletions

View file

@ -51,6 +51,7 @@ export interface NoteCardsProps {
eventId?: string;
timelineId: string;
onCancel?: () => void;
showToggleEventDetailsAction?: boolean;
}
/** A view for entering and reviewing notes */
@ -65,6 +66,7 @@ export const NoteCards = React.memo<NoteCardsProps>(
eventId,
timelineId,
onCancel,
showToggleEventDetailsAction = true,
}) => {
const [newNote, setNewNote] = useState('');
@ -109,7 +111,11 @@ export const NoteCards = React.memo<NoteCardsProps>(
<EuiScreenReaderOnly data-test-subj="screenReaderOnly">
<p>{i18n.YOU_ARE_VIEWING_NOTES(ariaRowindex)}</p>
</EuiScreenReaderOnly>
<NotePreviews timelineId={timelineId} notes={notes} />
<NotePreviews
timelineId={timelineId}
notes={notes}
showToggleEventDetailsAction={showToggleEventDetailsAction}
/>
</NotesContainer>
</NotePreviewsContainer>
) : null}

View file

@ -239,6 +239,63 @@ describe('NotePreviews', () => {
expect(wrapper.find('[data-test-subj="delete-note"] button').prop('disabled')).toBeFalsy();
});
test('should render toggle event details action by default', () => {
const timeline = mockTimelineResults[0];
(useDeepEqualSelector as jest.Mock).mockReturnValue(timeline);
const wrapper = mountWithI18nProvider(
<TestProviders>
<NotePreviews
notes={[
{
noteId: 'noteId1',
note: 'enabled delete',
savedObjectId: 'test-id',
updated: note2updated,
updatedBy: 'alice',
},
]}
showTimelineDescription
timelineId="test-timeline-id"
/>
</TestProviders>,
{
wrappingComponent: createReactQueryWrapper(),
}
);
expect(wrapper.find('[data-test-subj="notes-toggle-event-details"]').exists()).toBeTruthy();
});
test('should not render toggle event details action when showToggleEventDetailsAction is false ', () => {
const timeline = mockTimelineResults[0];
(useDeepEqualSelector as jest.Mock).mockReturnValue(timeline);
const wrapper = mountWithI18nProvider(
<TestProviders>
<NotePreviews
notes={[
{
noteId: 'noteId1',
note: 'enabled delete',
savedObjectId: 'test-id',
updated: note2updated,
updatedBy: 'alice',
},
]}
showTimelineDescription
timelineId="test-timeline-id"
showToggleEventDetailsAction={false}
/>
</TestProviders>,
{
wrappingComponent: createReactQueryWrapper(),
}
);
expect(wrapper.find('[data-test-subj="notes-toggle-event-details"]').exists()).toBeFalsy();
});
describe('Delete Notes', () => {
it('should delete note correctly', async () => {
const timeline = {

View file

@ -101,6 +101,7 @@ const ToggleEventDetailsButtonComponent: React.FC<ToggleEventDetailsButtonProps>
return (
<EuiButtonIcon
data-test-subj="notes-toggle-event-details"
title={i18n.TOGGLE_EXPAND_EVENT_DETAILS}
aria-label={i18n.TOGGLE_EXPAND_EVENT_DETAILS}
color="text"
@ -204,10 +205,32 @@ const NoteActions = React.memo<{
savedObjectId?: string | null;
confirmingNoteId?: string | null;
eventIdToNoteIds?: Record<string, string[]>;
}>(({ eventId, timelineId, noteId, confirmingNoteId, eventIdToNoteIds, savedObjectId }) => {
return eventId && timelineId ? (
<>
<ToggleEventDetailsButton eventId={eventId} timelineId={timelineId} />
showToggleEventDetailsAction?: boolean;
}>(
({
eventId,
timelineId,
noteId,
confirmingNoteId,
eventIdToNoteIds,
savedObjectId,
showToggleEventDetailsAction = true,
}) => {
return eventId && timelineId ? (
<>
{showToggleEventDetailsAction ? (
<ToggleEventDetailsButton eventId={eventId} timelineId={timelineId} />
) : null}
<DeleteNoteButton
noteId={noteId}
eventId={eventId}
confirmingNoteId={confirmingNoteId}
savedObjectId={savedObjectId}
timelineId={timelineId}
eventIdToNoteIds={eventIdToNoteIds}
/>
</>
) : (
<DeleteNoteButton
noteId={noteId}
eventId={eventId}
@ -216,18 +239,9 @@ const NoteActions = React.memo<{
timelineId={timelineId}
eventIdToNoteIds={eventIdToNoteIds}
/>
</>
) : (
<DeleteNoteButton
noteId={noteId}
eventId={eventId}
confirmingNoteId={confirmingNoteId}
savedObjectId={savedObjectId}
timelineId={timelineId}
eventIdToNoteIds={eventIdToNoteIds}
/>
);
});
);
}
);
NoteActions.displayName = 'NoteActions';
/**
@ -238,10 +252,11 @@ interface NotePreviewsProps {
notes?: TimelineResultNote[] | null;
timelineId?: string;
showTimelineDescription?: boolean;
showToggleEventDetailsAction?: boolean;
}
export const NotePreviews = React.memo<NotePreviewsProps>(
({ notes, timelineId, showTimelineDescription }) => {
({ notes, timelineId, showTimelineDescription, showToggleEventDetailsAction = true }) => {
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const getTimelineNotes = useMemo(() => getTimelineNoteSelector(), []);
const timeline = useDeepEqualSelector((state) =>
@ -315,6 +330,7 @@ export const NotePreviews = React.memo<NotePreviewsProps>(
savedObjectId={note.savedObjectId}
confirmingNoteId={timeline?.confirmingNoteId}
eventIdToNoteIds={eventIdToNoteIds}
showToggleEventDetailsAction={showToggleEventDetailsAction}
/>
),
timelineAvatar: (
@ -326,7 +342,13 @@ export const NotePreviews = React.memo<NotePreviewsProps>(
),
};
}),
[eventIdToNoteIds, notes, timelineId, timeline?.confirmingNoteId]
[
eventIdToNoteIds,
notes,
timelineId,
timeline?.confirmingNoteId,
showToggleEventDetailsAction,
]
);
const commentList = useMemo(

View file

@ -7,9 +7,10 @@
import React from 'react';
import {
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiFlyoutResizable,
EuiOutsideClickDetector,
EuiTitle,
useGeneratedHtmlId,
} from '@elastic/eui';
@ -32,7 +33,7 @@ export type NotesFlyoutProps = {
* z-index override is needed because otherwise NotesFlyout appears below
* Timeline Modal as they both have same z-index of 1000
*/
const NotesFlyoutContainer = styled(EuiFlyout)`
const NotesFlyoutContainer = styled(EuiFlyoutResizable)`
/*
* We want the width of flyout to be less than 50% of screen because
* otherwise it interferes with the delete notes modal
@ -55,33 +56,37 @@ export const NotesFlyout = React.memo(function NotesFlyout(props: NotesFlyoutPro
}
return (
<NotesFlyoutContainer
ownFocus={false}
className="timeline-notes-flyout"
data-test-subj="timeline-notes-flyout"
onClose={onClose}
aria-labelledby={notesFlyoutTitleId}
maxWidth={750}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>{i18n.NOTES}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<NoteCards
ariaRowindex={0}
associateNote={associateNote}
className="notes-in-flyout"
data-test-subj="note-cards"
notes={notes}
showAddNote={true}
toggleShowAddNote={toggleShowAddNote}
eventId={eventId}
timelineId={timelineId}
onCancel={onCancel}
/>
</EuiFlyoutBody>
</NotesFlyoutContainer>
<EuiOutsideClickDetector onOutsideClick={onClose}>
<NotesFlyoutContainer
ownFocus={false}
className="timeline-notes-flyout"
data-test-subj="timeline-notes-flyout"
onClose={onClose}
aria-labelledby={notesFlyoutTitleId}
minWidth={500}
maxWidth={1400}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>{i18n.NOTES}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<NoteCards
ariaRowindex={0}
associateNote={associateNote}
className="notes-in-flyout"
data-test-subj="note-cards"
notes={notes}
showAddNote={true}
toggleShowAddNote={toggleShowAddNote}
eventId={eventId}
timelineId={timelineId}
onCancel={onCancel}
showToggleEventDetailsAction={false}
/>
</EuiFlyoutBody>
</NotesFlyoutContainer>
</EuiOutsideClickDetector>
);
});

View file

@ -6,9 +6,10 @@
*/
import React from 'react';
import { TimelineId } from '../../../../../common/types';
import { TimelineId, TimelineTabs } from '../../../../../common/types';
import { renderHook, act } from '@testing-library/react-hooks/dom';
import { createMockStore, mockGlobalState, TestProviders } from '../../../../common/mock';
import type { UseNotesInFlyoutArgs } from './use_notes_in_flyout';
import { useNotesInFlyout } from './use_notes_in_flyout';
import { waitFor } from '@testing-library/react';
import { useDispatch } from 'react-redux';
@ -85,11 +86,13 @@ const refetchMock = jest.fn();
const renderTestHook = () => {
return renderHook(
() =>
(props?: Partial<UseNotesInFlyoutArgs>) =>
useNotesInFlyout({
eventIdToNoteIds: mockEventIdToNoteIds,
timelineId: TimelineId.test,
refetch: refetchMock,
activeTab: TimelineTabs.query,
...props,
}),
{
wrapper: ({ children }) => (
@ -198,4 +201,33 @@ describe('useNotesInFlyout', () => {
expect(result.current.isNotesFlyoutVisible).toBe(false);
});
it('should close the flyout when activeTab is changed', () => {
const { result, rerender, waitForNextUpdate } = renderTestHook();
act(() => {
result.current.setNotesEventId('event-1');
});
act(() => {
result.current.showNotesFlyout();
});
expect(result.current.isNotesFlyoutVisible).toBe(true);
act(() => {
// no change in active Tab
rerender({ activeTab: TimelineTabs.query });
});
expect(result.current.isNotesFlyoutVisible).toBe(true);
act(() => {
rerender({ activeTab: TimelineTabs.eql });
});
waitForNextUpdate();
expect(result.current.isNotesFlyoutVisible).toBe(false);
});
});

View file

@ -5,16 +5,18 @@
* 2.0.
*/
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import type { TimelineTabs } from '../../../../../common/types';
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
import { appSelectors } from '../../../../common/store';
import { timelineActions } from '../../../store';
interface UseNotesInFlyoutArgs {
export interface UseNotesInFlyoutArgs {
eventIdToNoteIds: Record<string, string[]>;
refetch?: () => void;
timelineId: string;
activeTab: TimelineTabs;
}
const EMPTY_STRING_ARRAY: string[] = [];
@ -36,12 +38,19 @@ export const useNotesInFlyout = (args: UseNotesInFlyoutArgs) => {
setIsNotesFlyoutVisible(true);
}, []);
const { eventIdToNoteIds, refetch, timelineId } = args;
const { eventIdToNoteIds, refetch, timelineId, activeTab } = args;
const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []);
const notesById = useDeepEqualSelector(getNotesByIds);
useEffect(() => {
if (activeTab) {
// if activeTab changes, close the notes flyout
closeNotesFlyout();
}
}, [activeTab, closeNotesFlyout]);
const dispatch = useDispatch();
const noteIds: string[] = useMemo(

View file

@ -163,6 +163,7 @@ export const EqlTabContentComponent: React.FC<Props> = ({
eventIdToNoteIds,
refetch,
timelineId,
activeTab: TimelineTabs.eql,
});
const onToggleShowNotes = useCallback(

View file

@ -199,6 +199,7 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
eventIdToNoteIds,
refetch,
timelineId,
activeTab: TimelineTabs.pinned,
});
const onToggleShowNotes = useCallback(

View file

@ -229,6 +229,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
eventIdToNoteIds,
refetch,
timelineId,
activeTab,
});
const onToggleShowNotes = useCallback(

View file

@ -27,7 +27,6 @@ import { createStartServicesMock } from '../../../../../common/lib/kibana/kibana
import type { StartServices } from '../../../../../types';
import { useKibana } from '../../../../../common/lib/kibana';
import { useDispatch } from 'react-redux';
import { timelineActions } from '../../../../store';
import type { ExperimentalFeatures } from '../../../../../../common';
import { allowedExperimentalValues } from '../../../../../../common';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
@ -35,7 +34,7 @@ import { defaultUdtHeaders } from '../../unified_components/default_headers';
import { defaultColumnHeaderType } from '../../body/column_headers/default_headers';
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
import { getEndpointPrivilegesInitialStateMock } from '../../../../../common/components/user_privileges/endpoint/mocks';
import userEvent from '@testing-library/user-event';
import * as timelineActions from '../../../../store/actions';
jest.mock('../../../../../common/components/user_privileges');
@ -172,6 +171,10 @@ describe('query tab with unified timeline', () => {
});
beforeEach(() => {
Object.defineProperty(window, '__@hello-pangea/dnd-disable-dev-warnings', {
value: true,
writable: false,
});
useTimelineEventsMock = jest.fn(() => [
false,
{
@ -776,332 +779,196 @@ describe('query tab with unified timeline', () => {
describe('Leading actions - notes', () => {
describe('securitySolutionNotesEnabled = true', () => {
describe('expandableFlyoutDisabled = false', () => {
beforeEach(() => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineEnabled') {
return true;
}
if (feature === 'securitySolutionNotesEnabled') {
return true;
}
return allowedExperimentalValues[feature];
})
);
});
it(
'should have the notification dot & correct tooltip',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1);
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible();
fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible();
expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent(
'1 Note available. Click to view it & add more.'
);
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to add notes through expandable flyout',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(mockOpenFlyout).toHaveBeenCalled();
});
},
SPECIAL_TEST_TIMEOUT
beforeEach(() => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineEnabled') {
return true;
}
if (feature === 'securitySolutionNotesEnabled') {
return true;
}
return allowedExperimentalValues[feature];
})
);
});
describe('expandableFlyoutDisabled = true', () => {
beforeEach(() => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineEnabled') {
return true;
}
if (feature === 'expandableFlyoutDisabled') {
return true;
}
if (feature === 'securitySolutionNotesEnabled') {
return true;
}
return allowedExperimentalValues[feature];
})
);
});
it(
'should have the notification dot & correct tooltip',
async () => {
renderTestComponents();
it(
'should have the notification dot & correct tooltip',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1);
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1);
expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible();
fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible();
expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent(
'1 Note available. Click to view it & add more.'
);
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to add notes through expandable flyout',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible();
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible();
expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent(
'1 Note available. Click to view it & add more.'
);
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to add notes using EuiFlyout',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be cancel adding notes',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1');
expect(screen.getByTestId('cancel')).not.toBeDisabled();
fireEvent.click(screen.getByTestId('cancel'));
await waitFor(() => {
expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument();
});
},
SPECIAL_TEST_TIMEOUT
);
});
await waitFor(() => {
expect(mockOpenFlyout).toHaveBeenCalled();
});
},
SPECIAL_TEST_TIMEOUT
);
});
describe('securitySolutionNotesEnabled = false', () => {
describe('expandableFlyoutDisabled = false', () => {
beforeEach(() => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineEnabled') {
return true;
}
if (feature === 'securitySolutionNotesEnabled') {
return false;
}
return allowedExperimentalValues[feature];
})
);
});
it(
'should have the notification dot & correct tooltip',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1);
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible();
fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible();
expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent(
'1 Note available. Click to view it & add more.'
);
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to add notes using EuiFlyout',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be cancel adding notes',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1');
expect(screen.getByTestId('cancel')).not.toBeDisabled();
fireEvent.click(screen.getByTestId('cancel'));
await waitFor(() => {
expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument();
});
},
SPECIAL_TEST_TIMEOUT
beforeEach(() => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineEnabled') {
return true;
}
if (feature === 'securitySolutionNotesEnabled') {
return false;
}
return allowedExperimentalValues[feature];
})
);
});
describe('expandableFlyoutDisabled = true', () => {
beforeEach(() => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
jest.fn((feature: keyof ExperimentalFeatures) => {
if (feature === 'unifiedComponentsInTimelineEnabled') {
return true;
}
if (feature === 'expandableFlyoutDisabled') {
return true;
}
if (feature === 'securitySolutionNotesEnabled') {
return true;
}
return allowedExperimentalValues[feature];
})
);
});
it(
'should have the notification dot & correct tooltip',
async () => {
renderTestComponents();
it(
'should have the notification dot & correct tooltip',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1);
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1);
expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible();
fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible();
expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent(
'1 Note available. Click to view it & add more.'
);
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to add notes using EuiFlyout',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible();
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
},
SPECIAL_TEST_TIMEOUT
);
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible();
expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent(
'1 Note available. Click to view it & add more.'
);
it(
'should cancel adding notes',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
expect(screen.getByTestId('cancel')).not.toBeDisabled();
fireEvent.click(screen.getByTestId('cancel'));
await waitFor(() => {
expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument();
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to delete notes',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('delete-note')).toBeVisible();
});
const noteDeleteSpy = jest.spyOn(timelineActions, 'setConfirmingNoteId');
fireEvent.click(screen.getByTestId('delete-note'));
await waitFor(() => {
expect(noteDeleteSpy).toHaveBeenCalled();
expect(noteDeleteSpy).toHaveBeenCalledWith({
confirmingNoteId: '1',
id: TimelineId.test,
});
},
SPECIAL_TEST_TIMEOUT
);
it(
'should be able to add notes using EuiFlyout',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
});
},
SPECIAL_TEST_TIMEOUT
);
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
it(
'should not show toggle event details action',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
},
SPECIAL_TEST_TIMEOUT
);
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
it(
'should be cancel adding notes',
async () => {
renderTestComponents();
expect(await screen.findByTestId('discoverDocTable')).toBeVisible();
await waitFor(() => {
expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled();
});
fireEvent.click(screen.getByTestId('timeline-notes-button-small'));
await waitFor(() => {
expect(screen.getByTestId('add-note-container')).toBeVisible();
});
userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1');
expect(screen.getByTestId('cancel')).not.toBeDisabled();
fireEvent.click(screen.getByTestId('cancel'));
await waitFor(() => {
expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument();
});
},
SPECIAL_TEST_TIMEOUT
);
});
await waitFor(() => {
expect(screen.queryByTestId('notes-toggle-event-details')).not.toBeInTheDocument();
});
},
SPECIAL_TEST_TIMEOUT
);
});
});