mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.16] [Security Solution][Notes] - switch the securitySolutionNotesEnables feature flag to securitySolutionNotesDisabled (#196778) (#198205)
# Backport This will backport the following commits from `main` to `8.16`: - [[Security Solution][Notes] - switch the securitySolutionNotesEnables feature flag to securitySolutionNotesDisabled (#196778)](https://github.com/elastic/kibana/pull/196778) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Philippe Oberti","email":"philippe.oberti@elastic.co"},"sourceCommit":{"committedDate":"2024-10-29T21:00:20Z","message":"[Security Solution][Notes] - switch the securitySolutionNotesEnables feature flag to securitySolutionNotesDisabled (#196778)\n\n## Summary\r\n\r\nThis PR switches the `securitySolutionNotesEnabled` to\r\n`securitySolutionNotesDisabled` (with a `false` value by default) to\r\nenable the new Notes functionality in `8.16`.\r\nCustomers can set the new `securitySolutionNotesDisabled` feature flag\r\nto true in their environment if they want to go back to the old notes\r\nsystem.\r\n\r\nThe PR also fixes a tiny bug with the badge showing the number of notes\r\nin the Timeline Notes tab. The new system was not taking into account a\r\ntimeline description, so if the timeline had a description the number of\r\nnotes was always 1 lower than the actual number of notes displayed\r\nbelow. This issue was highlighted by a Cypress test!\r\n\r\nThe goal is to remove the old system entirely within a few releases\r\n(maybe `8.18` or `9.0`).\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\nhttps://github.com/elastic/kibana/issues/189879","sha":"4fb4282509e0a5f7605433a5ef8f9e9085647282","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["backport","release_note:skip","v9.0.0","Team:Threat Hunting:Investigations","v8.16.0"],"title":"[Security Solution][Notes] - switch the securitySolutionNotesEnables feature flag to securitySolutionNotesDisabled","number":196778,"url":"https://github.com/elastic/kibana/pull/196778","mergeCommit":{"message":"[Security Solution][Notes] - switch the securitySolutionNotesEnables feature flag to securitySolutionNotesDisabled (#196778)\n\n## Summary\r\n\r\nThis PR switches the `securitySolutionNotesEnabled` to\r\n`securitySolutionNotesDisabled` (with a `false` value by default) to\r\nenable the new Notes functionality in `8.16`.\r\nCustomers can set the new `securitySolutionNotesDisabled` feature flag\r\nto true in their environment if they want to go back to the old notes\r\nsystem.\r\n\r\nThe PR also fixes a tiny bug with the badge showing the number of notes\r\nin the Timeline Notes tab. The new system was not taking into account a\r\ntimeline description, so if the timeline had a description the number of\r\nnotes was always 1 lower than the actual number of notes displayed\r\nbelow. This issue was highlighted by a Cypress test!\r\n\r\nThe goal is to remove the old system entirely within a few releases\r\n(maybe `8.18` or `9.0`).\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\nhttps://github.com/elastic/kibana/issues/189879","sha":"4fb4282509e0a5f7605433a5ef8f9e9085647282"}},"sourceBranch":"main","suggestedTargetBranches":["8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196778","number":196778,"mergeCommit":{"message":"[Security Solution][Notes] - switch the securitySolutionNotesEnables feature flag to securitySolutionNotesDisabled (#196778)\n\n## Summary\r\n\r\nThis PR switches the `securitySolutionNotesEnabled` to\r\n`securitySolutionNotesDisabled` (with a `false` value by default) to\r\nenable the new Notes functionality in `8.16`.\r\nCustomers can set the new `securitySolutionNotesDisabled` feature flag\r\nto true in their environment if they want to go back to the old notes\r\nsystem.\r\n\r\nThe PR also fixes a tiny bug with the badge showing the number of notes\r\nin the Timeline Notes tab. The new system was not taking into account a\r\ntimeline description, so if the timeline had a description the number of\r\nnotes was always 1 lower than the actual number of notes displayed\r\nbelow. This issue was highlighted by a Cypress test!\r\n\r\nThe goal is to remove the old system entirely within a few releases\r\n(maybe `8.18` or `9.0`).\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\nhttps://github.com/elastic/kibana/issues/189879","sha":"4fb4282509e0a5f7605433a5ef8f9e9085647282"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Philippe Oberti <philippe.oberti@elastic.co>
This commit is contained in:
parent
8b362b9d12
commit
80ade8b15f
27 changed files with 158 additions and 130 deletions
|
@ -94,9 +94,9 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
endpointManagementSpaceAwarenessEnabled: false,
|
||||
|
||||
/**
|
||||
* Enables new notes
|
||||
* Disables new notes
|
||||
*/
|
||||
securitySolutionNotesEnabled: false,
|
||||
securitySolutionNotesDisabled: false,
|
||||
|
||||
/**
|
||||
* Disables entity and alert previews
|
||||
|
|
|
@ -93,8 +93,8 @@ const RowActionComponent = ({
|
|||
[columnHeaders, timelineNonEcsData]
|
||||
);
|
||||
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const handleOnEventDetailPanelOpened = useCallback(() => {
|
||||
|
@ -175,12 +175,12 @@ const RowActionComponent = ({
|
|||
showCheckboxes={showCheckboxes}
|
||||
tabType={tabType}
|
||||
timelineId={tableId}
|
||||
toggleShowNotes={securitySolutionNotesEnabled ? toggleShowNotes : undefined}
|
||||
toggleShowNotes={securitySolutionNotesDisabled ? undefined : toggleShowNotes}
|
||||
width={width}
|
||||
setEventsLoading={setEventsLoading}
|
||||
setEventsDeleted={setEventsDeleted}
|
||||
refetch={refetch}
|
||||
showNotes={securitySolutionNotesEnabled ? true : false}
|
||||
showNotes={!securitySolutionNotesDisabled}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -16,6 +16,9 @@ import { useGlobalFullScreen } from '../../containers/use_full_screen';
|
|||
import { licenseService } from '../../hooks/use_license';
|
||||
import { mockHistory } from '../../mock/router';
|
||||
import { DEFAULT_EVENTS_STACK_BY_VALUE } from './histogram_configurations';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
|
||||
|
||||
jest.mock('../../hooks/use_experimental_features');
|
||||
|
||||
const mockGetDefaultControlColumn = jest.fn();
|
||||
jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({
|
||||
|
@ -95,6 +98,7 @@ describe('EventsQueryTabBody', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
@ -106,7 +110,7 @@ describe('EventsQueryTabBody', () => {
|
|||
);
|
||||
|
||||
expect(queryByText('MockedStatefulEventsViewer')).toBeInTheDocument();
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4);
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('renders the matrix histogram when globalFullScreen is false', () => {
|
||||
|
@ -186,7 +190,19 @@ describe('EventsQueryTabBody', () => {
|
|||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('only have 4 columns on Action bar for non-Enterprise user', () => {
|
||||
it('should have 5 columns on Action bar for non-Enterprise user', () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<EventsQueryTabBody {...commonProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('should have 4 columns on Action bar for non-Enterprise user and securitySolutionNotesDisabled is true', () => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<EventsQueryTabBody {...commonProps} />
|
||||
|
@ -196,9 +212,8 @@ describe('EventsQueryTabBody', () => {
|
|||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4);
|
||||
});
|
||||
|
||||
it('shows 5 columns on Action bar for Enterprise user', () => {
|
||||
it('should 6 columns on Action bar for Enterprise user', () => {
|
||||
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
|
||||
|
||||
licenseServiceMock.isEnterprise.mockReturnValue(true);
|
||||
|
||||
render(
|
||||
|
@ -207,6 +222,20 @@ describe('EventsQueryTabBody', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(6);
|
||||
});
|
||||
|
||||
it('should 6 columns on Action bar for Enterprise user and securitySolutionNotesDisabled is true', () => {
|
||||
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
|
||||
licenseServiceMock.isEnterprise.mockReturnValue(true);
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<EventsQueryTabBody {...commonProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,10 +75,10 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
|
|||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
const isEnterprisePlus = useLicense().isEnterprise();
|
||||
let ACTION_BUTTON_COUNT = isEnterprisePlus ? 6 : 5;
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
if (!securitySolutionNotesEnabled) {
|
||||
if (securitySolutionNotesDisabled) {
|
||||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
const leadingControlColumns = useMemo(
|
||||
|
|
|
@ -255,8 +255,8 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
onEventDetailsPanelOpened();
|
||||
}, [activeStep, incrementStep, isTourAnchor, isTourShown, onEventDetailsPanelOpened]);
|
||||
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
/* only applicable for new event based notes */
|
||||
|
@ -270,7 +270,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
|
||||
/* note ids associated with the document AND attached to the current timeline, used for pinning */
|
||||
const timelineNoteIds = useMemo(() => {
|
||||
if (securitySolutionNotesEnabled) {
|
||||
if (!securitySolutionNotesDisabled) {
|
||||
// if timeline is unsaved, there is no notes associated to timeline yet
|
||||
return savedObjectId ? documentBasedNotesInTimeline.map((note) => note.noteId) : [];
|
||||
}
|
||||
|
@ -280,13 +280,13 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
eventId,
|
||||
documentBasedNotesInTimeline,
|
||||
savedObjectId,
|
||||
securitySolutionNotesEnabled,
|
||||
securitySolutionNotesDisabled,
|
||||
]);
|
||||
|
||||
/* note count of the document */
|
||||
const notesCount = useMemo(
|
||||
() => (securitySolutionNotesEnabled ? documentBasedNotes.length : timelineNoteIds.length),
|
||||
[documentBasedNotes, timelineNoteIds, securitySolutionNotesEnabled]
|
||||
() => (securitySolutionNotesDisabled ? timelineNoteIds.length : documentBasedNotes.length),
|
||||
[documentBasedNotes, timelineNoteIds, securitySolutionNotesDisabled]
|
||||
);
|
||||
|
||||
// we hide the analyzer icon if the data is not available for the resolver
|
||||
|
|
|
@ -47,10 +47,10 @@ export const getUseActionColumnHook =
|
|||
}
|
||||
|
||||
// we only want to show the note icon if the new notes system feature flag is enabled
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
if (!securitySolutionNotesEnabled) {
|
||||
if (securitySolutionNotesDisabled) {
|
||||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ export const LeftPanel: FC<Partial<DocumentDetailsProps>> = memo(({ path }) => {
|
|||
const { openLeftPanel } = useExpandableFlyoutApi();
|
||||
const { eventId, indexName, scopeId, getFieldsData, isPreview } = useDocumentDetailsContext();
|
||||
const eventKind = getField(getFieldsData('event.kind'));
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const [visualizationInFlyoutEnabled] = useUiSetting$<boolean>(
|
||||
|
@ -49,14 +49,14 @@ export const LeftPanel: FC<Partial<DocumentDetailsProps>> = memo(({ path }) => {
|
|||
eventKind === EventKind.signal
|
||||
? [tabs.insightsTab, tabs.investigationTab, tabs.responseTab]
|
||||
: [tabs.insightsTab];
|
||||
if (securitySolutionNotesEnabled && !isPreview) {
|
||||
if (!securitySolutionNotesDisabled && !isPreview) {
|
||||
tabList.push(tabs.notesTab);
|
||||
}
|
||||
if (visualizationInFlyoutEnabled && !isPreview) {
|
||||
return [tabs.visualizeTab, ...tabList];
|
||||
}
|
||||
return tabList;
|
||||
}, [eventKind, isPreview, securitySolutionNotesEnabled, visualizationInFlyoutEnabled]);
|
||||
}, [eventKind, isPreview, securitySolutionNotesDisabled, visualizationInFlyoutEnabled]);
|
||||
|
||||
const selectedTabId = useMemo(() => {
|
||||
const defaultTab = tabsDisplayed[0].id;
|
||||
|
|
|
@ -78,7 +78,7 @@ describe('<AlertHeaderTitle />', () => {
|
|||
});
|
||||
|
||||
it('should render notes section if experimental flag is enabled', () => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
|
||||
const { getByTestId } = renderHeader(mockContextValue);
|
||||
expect(getByTestId(NOTES_TITLE_TEST_ID)).toBeInTheDocument();
|
||||
|
|
|
@ -40,8 +40,8 @@ export const AlertHeaderTitle = memo(() => {
|
|||
refetchFlyoutData,
|
||||
getFieldsData,
|
||||
} = useDocumentDetailsContext();
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const { isAlert, ruleName, timestamp, ruleId } = useBasicDataFromDetailsData(
|
||||
|
@ -98,7 +98,30 @@ export const AlertHeaderTitle = memo(() => {
|
|||
/>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
{securitySolutionNotesEnabled ? (
|
||||
{securitySolutionNotesDisabled ? (
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
wrap
|
||||
data-test-subj={ALERT_SUMMARY_PANEL_TEST_ID}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<DocumentStatus />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<RiskScore />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<Assignees
|
||||
eventId={eventId}
|
||||
assignedUserIds={alertAssignees}
|
||||
onAssigneesUpdated={onAssigneesUpdated}
|
||||
isPreview={isPreview}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="s"
|
||||
|
@ -132,29 +155,6 @@ export const AlertHeaderTitle = memo(() => {
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
wrap
|
||||
data-test-subj={ALERT_SUMMARY_PANEL_TEST_ID}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<DocumentStatus />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<RiskScore />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<Assignees
|
||||
eventId={eventId}
|
||||
assignedUserIds={alertAssignees}
|
||||
onAssigneesUpdated={onAssigneesUpdated}
|
||||
isPreview={isPreview}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -229,7 +229,7 @@ export const links: LinkItem = {
|
|||
path: NOTES_PATH,
|
||||
skipUrlState: true,
|
||||
hideTimeline: true,
|
||||
experimentalKey: 'securitySolutionNotesEnabled',
|
||||
hideWhenExperimentalKey: 'securitySolutionNotesDisabled',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -88,8 +88,8 @@ const NotesTelemetry = () => (
|
|||
);
|
||||
|
||||
export const ManagementContainer = memo(() => {
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -162,7 +162,7 @@ export const ManagementContainer = memo(() => {
|
|||
hasPrivilege={canReadActionsLogManagement}
|
||||
/>
|
||||
|
||||
{securitySolutionNotesEnabled && (
|
||||
{!securitySolutionNotesDisabled && (
|
||||
<Route path={MANAGEMENT_ROUTING_NOTES_PATH} component={NotesTelemetry} />
|
||||
)}
|
||||
|
||||
|
|
|
@ -45,8 +45,8 @@ describe('useFetchNotes', () => {
|
|||
expect(typeof result.current.onLoad).toBe('function');
|
||||
});
|
||||
|
||||
it('should not dispatch action when securitySolutionNotesEnabled is false', () => {
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(false);
|
||||
it('should not dispatch action when securitySolutionNotesDisabled is true', () => {
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(true);
|
||||
const { result } = renderHook(() => useFetchNotes());
|
||||
|
||||
result.current.onLoad([{ _id: '1' }]);
|
||||
|
@ -54,7 +54,7 @@ describe('useFetchNotes', () => {
|
|||
});
|
||||
|
||||
it('should not dispatch action when events array is empty', () => {
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(true);
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(false);
|
||||
const { result } = renderHook(() => useFetchNotes());
|
||||
|
||||
result.current.onLoad([]);
|
||||
|
@ -62,7 +62,7 @@ describe('useFetchNotes', () => {
|
|||
});
|
||||
|
||||
it('should dispatch fetchNotesByDocumentIds with correct ids when conditions are met', () => {
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(true);
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(false);
|
||||
const { result } = renderHook(() => useFetchNotes());
|
||||
|
||||
const events = [{ _id: '1' }, { _id: '2' }, { _id: '3' }];
|
||||
|
@ -74,7 +74,7 @@ describe('useFetchNotes', () => {
|
|||
});
|
||||
|
||||
it('should memoize onLoad function', () => {
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(true);
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(false);
|
||||
const { result, rerender } = renderHook(() => useFetchNotes());
|
||||
|
||||
const firstOnLoad = result.current.onLoad;
|
||||
|
@ -84,7 +84,7 @@ describe('useFetchNotes', () => {
|
|||
expect(firstOnLoad).toBe(secondOnLoad);
|
||||
});
|
||||
|
||||
it('should update onLoad when securitySolutionNotesEnabled changes', () => {
|
||||
it('should update onLoad when securitySolutionNotesDisabled changes', () => {
|
||||
mockedUseIsExperimentalFeatureEnabled.mockReturnValue(true);
|
||||
const { result, rerender } = renderHook(() => useFetchNotes());
|
||||
|
||||
|
|
|
@ -22,19 +22,19 @@ export interface UseFetchNotesResult {
|
|||
*/
|
||||
export const useFetchNotes = (): UseFetchNotesResult => {
|
||||
const dispatch = useDispatch();
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
const onLoad = useCallback(
|
||||
(events: Array<Partial<{ _id: string }>>) => {
|
||||
if (!securitySolutionNotesEnabled || events.length === 0) return;
|
||||
if (securitySolutionNotesDisabled || events.length === 0) return;
|
||||
|
||||
const eventIds: string[] = events
|
||||
.map((event) => event._id)
|
||||
.filter((id) => id != null) as string[];
|
||||
dispatch(fetchNotesByDocumentIds({ documentIds: eventIds }));
|
||||
},
|
||||
[dispatch, securitySolutionNotesEnabled]
|
||||
[dispatch, securitySolutionNotesDisabled]
|
||||
);
|
||||
|
||||
return { onLoad };
|
||||
|
|
|
@ -21,5 +21,5 @@ export const links: LinkItem = {
|
|||
}),
|
||||
],
|
||||
links: [],
|
||||
experimentalKey: 'securitySolutionNotesEnabled',
|
||||
hideWhenExperimentalKey: 'securitySolutionNotesDisabled',
|
||||
};
|
||||
|
|
|
@ -111,7 +111,7 @@ interface NotesTabContentProps {
|
|||
}
|
||||
|
||||
/**
|
||||
* Renders the "old" notes tab content. This should be removed when we remove the securitySolutionNotesEnabled feature flag
|
||||
* Renders the "old" notes tab content. This should be removed when we remove the securitySolutionNotesDisabled feature flag
|
||||
*/
|
||||
export const OldNotes: React.FC<NotesTabContentProps> = React.memo(({ timelineId }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
|
|
@ -117,8 +117,8 @@ export const EqlTabContentComponent: React.FC<Props> = ({
|
|||
});
|
||||
|
||||
const { openFlyout } = useExpandableFlyoutApi();
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -139,7 +139,7 @@ export const EqlTabContentComponent: React.FC<Props> = ({
|
|||
const onToggleShowNotes = useCallback(
|
||||
(eventId?: string) => {
|
||||
const indexName = selectedPatterns.join(',');
|
||||
if (eventId && securitySolutionNotesEnabled) {
|
||||
if (eventId && !securitySolutionNotesDisabled) {
|
||||
openFlyout({
|
||||
right: {
|
||||
id: DocumentDetailsRightPanelKey,
|
||||
|
@ -177,7 +177,7 @@ export const EqlTabContentComponent: React.FC<Props> = ({
|
|||
},
|
||||
[
|
||||
openFlyout,
|
||||
securitySolutionNotesEnabled,
|
||||
securitySolutionNotesDisabled,
|
||||
selectedPatterns,
|
||||
telemetry,
|
||||
timelineId,
|
||||
|
|
|
@ -253,8 +253,8 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
selectTimelineESQLSavedSearchId(state, timelineId)
|
||||
);
|
||||
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const [visualizationInFlyoutEnabled] = useUiSetting$<boolean>(
|
||||
|
@ -320,16 +320,20 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
}
|
||||
}, [fetchNotes, isTimelineSaved]);
|
||||
|
||||
const numberOfNotesNewSystem = useSelector((state: State) =>
|
||||
const notesNewSystem = useSelector((state: State) =>
|
||||
selectSortedNotesBySavedObjectId(state, {
|
||||
savedObjectId: timelineSavedObjectId,
|
||||
sort: { field: 'created', direction: 'asc' },
|
||||
})
|
||||
);
|
||||
const numberOfNotesNewSystem = useMemo(
|
||||
() => notesNewSystem.length + (isEmpty(timelineDescription) ? 0 : 1),
|
||||
[notesNewSystem, timelineDescription]
|
||||
);
|
||||
|
||||
const numberOfNotes = useMemo(
|
||||
() => (securitySolutionNotesEnabled ? numberOfNotesNewSystem.length : numberOfNotesOldSystem),
|
||||
[numberOfNotesNewSystem, numberOfNotesOldSystem, securitySolutionNotesEnabled]
|
||||
() => (securitySolutionNotesDisabled ? numberOfNotesOldSystem : numberOfNotesNewSystem),
|
||||
[numberOfNotesNewSystem, numberOfNotesOldSystem, securitySolutionNotesDisabled]
|
||||
);
|
||||
|
||||
const setActiveTab = useCallback(
|
||||
|
@ -446,9 +450,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
>
|
||||
<span>{i18n.NOTES_TAB}</span>
|
||||
{showTimeline && numberOfNotes > 0 && timelineType === TimelineTypeEnum.default && (
|
||||
<div>
|
||||
<CountBadge>{numberOfNotes}</CountBadge>
|
||||
</div>
|
||||
<CountBadge>{numberOfNotes}</CountBadge>
|
||||
)}
|
||||
</StyledEuiTab>
|
||||
<StyledEuiTab
|
||||
|
@ -462,9 +464,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
{showTimeline &&
|
||||
numberOfPinnedEvents > 0 &&
|
||||
timelineType === TimelineTypeEnum.default && (
|
||||
<div>
|
||||
<CountBadge>{numberOfPinnedEvents}</CountBadge>
|
||||
</div>
|
||||
<CountBadge>{numberOfPinnedEvents}</CountBadge>
|
||||
)}
|
||||
</StyledEuiTab>
|
||||
</StyledEuiTabs>
|
||||
|
|
|
@ -70,14 +70,14 @@ const mockGlobalStateWithUnSavedTimeline: State = {
|
|||
describe('NotesTabContentComponent', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the old note system', () => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
|
|
|
@ -71,7 +71,7 @@ interface NotesTabContentProps {
|
|||
|
||||
/**
|
||||
* Renders the notes tab content.
|
||||
* At this time the component support the old notes system and the new notes system (via the securitySolutionNotesEnabled feature flag).
|
||||
* At this time the component support the old notes system and the new notes system (via the securitySolutionNotesDisabled feature flag).
|
||||
* The old notes system is deprecated and will be removed in the future.
|
||||
* In both cases, the component fetches the notes for the timeline and renders:
|
||||
* - the timeline description
|
||||
|
@ -86,8 +86,8 @@ const NotesTabContentComponent: React.FC<NotesTabContentProps> = React.memo(({ t
|
|||
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
|
||||
const canCreateNotes = kibanaSecuritySolutionsPrivileges.crud;
|
||||
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const getScrollToTop = useMemo(() => getScrollToTopSelector(), []);
|
||||
|
@ -180,7 +180,9 @@ const NotesTabContentComponent: React.FC<NotesTabContentProps> = React.memo(({ t
|
|||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{securitySolutionNotesEnabled ? (
|
||||
{securitySolutionNotesDisabled ? (
|
||||
<OldNotes timelineId={timelineId} />
|
||||
) : (
|
||||
<EuiFlexGroup data-test-subj={'new-notes-screen'}>
|
||||
<EuiFlexItem>
|
||||
{timelineDescription}
|
||||
|
@ -213,8 +215,6 @@ const NotesTabContentComponent: React.FC<NotesTabContentProps> = React.memo(({ t
|
|||
<Participants notes={notes} timelineCreatedBy={timeline.createdBy} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<OldNotes timelineId={timelineId} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -146,8 +146,8 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
|
|||
});
|
||||
|
||||
const { openFlyout } = useExpandableFlyoutApi();
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -168,7 +168,7 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
|
|||
const onToggleShowNotes = useCallback(
|
||||
(eventId?: string) => {
|
||||
const indexName = selectedPatterns.join(',');
|
||||
if (eventId && securitySolutionNotesEnabled) {
|
||||
if (eventId && !securitySolutionNotesDisabled) {
|
||||
openFlyout({
|
||||
right: {
|
||||
id: DocumentDetailsRightPanelKey,
|
||||
|
@ -206,7 +206,7 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
|
|||
},
|
||||
[
|
||||
openFlyout,
|
||||
securitySolutionNotesEnabled,
|
||||
securitySolutionNotesDisabled,
|
||||
selectedPatterns,
|
||||
telemetry,
|
||||
timelineId,
|
||||
|
|
|
@ -882,12 +882,12 @@ describe('query tab with unified timeline', () => {
|
|||
});
|
||||
|
||||
describe('Leading actions - notes', () => {
|
||||
describe('securitySolutionNotesEnabled = true', () => {
|
||||
describe('securitySolutionNotesDisabled = false', () => {
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
|
||||
jest.fn((feature: keyof ExperimentalFeatures) => {
|
||||
if (feature === 'securitySolutionNotesEnabled') {
|
||||
return true;
|
||||
if (feature === 'securitySolutionNotesDisabled') {
|
||||
return false;
|
||||
}
|
||||
return allowedExperimentalValues[feature];
|
||||
})
|
||||
|
@ -937,12 +937,12 @@ describe('query tab with unified timeline', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('securitySolutionNotesEnabled = false', () => {
|
||||
describe('securitySolutionNotesDisabled = true', () => {
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
|
||||
jest.fn((feature: keyof ExperimentalFeatures) => {
|
||||
if (feature === 'securitySolutionNotesEnabled') {
|
||||
return false;
|
||||
if (feature === 'securitySolutionNotesDisabled') {
|
||||
return true;
|
||||
}
|
||||
return allowedExperimentalValues[feature];
|
||||
})
|
||||
|
@ -1071,12 +1071,12 @@ describe('query tab with unified timeline', () => {
|
|||
});
|
||||
|
||||
describe('Leading actions - pin', () => {
|
||||
describe('securitySolutionNotesEnabled = true', () => {
|
||||
describe('securitySolutionNotesDisabled = false', () => {
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
|
||||
jest.fn((feature: keyof ExperimentalFeatures) => {
|
||||
if (feature === 'securitySolutionNotesEnabled') {
|
||||
return true;
|
||||
if (feature === 'securitySolutionNotesDisabled') {
|
||||
return false;
|
||||
}
|
||||
return allowedExperimentalValues[feature];
|
||||
})
|
||||
|
@ -1155,12 +1155,12 @@ describe('query tab with unified timeline', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('securitySolutionNotesEnabled = false', () => {
|
||||
describe('securitySolutionNotesDisabled = true', () => {
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(
|
||||
jest.fn((feature: keyof ExperimentalFeatures) => {
|
||||
if (feature === 'securitySolutionNotesEnabled') {
|
||||
return false;
|
||||
if (feature === 'securitySolutionNotesDisabled') {
|
||||
return true;
|
||||
}
|
||||
return allowedExperimentalValues[feature];
|
||||
})
|
||||
|
|
|
@ -185,8 +185,8 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
});
|
||||
|
||||
const { openFlyout } = useExpandableFlyoutApi();
|
||||
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesEnabled'
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -207,7 +207,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
const onToggleShowNotes = useCallback(
|
||||
(eventId?: string) => {
|
||||
const indexName = selectedPatterns.join(',');
|
||||
if (eventId && securitySolutionNotesEnabled) {
|
||||
if (eventId && !securitySolutionNotesDisabled) {
|
||||
openFlyout({
|
||||
right: {
|
||||
id: DocumentDetailsRightPanelKey,
|
||||
|
@ -245,7 +245,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
},
|
||||
[
|
||||
openFlyout,
|
||||
securitySolutionNotesEnabled,
|
||||
securitySolutionNotesDisabled,
|
||||
selectedPatterns,
|
||||
telemetry,
|
||||
timelineId,
|
||||
|
|
|
@ -33,7 +33,8 @@ import {
|
|||
XY_CHART,
|
||||
} from '../../../screens/shared';
|
||||
|
||||
describe(`Event Rendered View`, { tags: ['@ess', '@serverless'] }, () => {
|
||||
// skipping as this test is also failing on main (see https://github.com/elastic/security-team/issues/10874)
|
||||
describe.skip(`Event Rendered View`, { tags: ['@ess', '@serverless'] }, () => {
|
||||
beforeEach(() => {
|
||||
login();
|
||||
createRule(getNewRule());
|
||||
|
|
|
@ -42,6 +42,8 @@ import {
|
|||
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB,
|
||||
DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES_TITLE,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_TITLE,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_VALUE,
|
||||
} from '../../../../screens/expandable_flyout/alert_details_right_panel';
|
||||
import {
|
||||
closeFlyout,
|
||||
|
@ -95,9 +97,8 @@ describe('Alert details expandable flyout right panel', { tags: ['@ess', '@serve
|
|||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES_TITLE).should('have.text', 'Assignees');
|
||||
|
||||
// TODO uncomment when the securitySolutionNotesEnabled feature flag is removed
|
||||
// cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_TITLE).should('have.text', 'Notes');
|
||||
// cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_VALUE).should('have.text', '0');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_TITLE).should('have.text', 'Notes');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_VALUE).should('exist');
|
||||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE)
|
||||
.should('be.visible')
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('Timeline notes tab', { tags: ['@ess', '@serverless'] }, () => {
|
|||
it('should be able to delete a note', () => {
|
||||
const deleteNoteContent = 'delete me';
|
||||
addNotesToTimeline(deleteNoteContent);
|
||||
cy.get(DELETE_NOTE).last().click();
|
||||
cy.get(DELETE_NOTE(0)).click();
|
||||
cy.get(MODAL_CONFIRMATION_BTN).click();
|
||||
cy.get(NOTE_DESCRIPTION).last().should('not.have.text', deleteNoteContent);
|
||||
});
|
||||
|
|
|
@ -55,10 +55,10 @@ export const DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES_VALUE = getDataTestSubject
|
|||
'securitySolutionFlyoutHeaderAssignees'
|
||||
);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_TITLE = getDataTestSubjectSelector(
|
||||
'securitySolutionFlyoutHeaderAssigneesTitle'
|
||||
'securitySolutionFlyoutHeaderNotesTitle'
|
||||
);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_HEADER_NOTES_VALUE = getDataTestSubjectSelector(
|
||||
'securitySolutionFlyoutHeaderAssigneesValue'
|
||||
'securitySolutionFlyoutHeaderNotesAddNoteButton'
|
||||
);
|
||||
|
||||
/* Footer */
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import type { TimelineFilter } from '../objects/timeline';
|
||||
import { getDataTestSubjectSelector } from '../helpers/common';
|
||||
|
||||
export const ADD_NOTE_BUTTON = '[data-test-subj="add-note"]';
|
||||
export const ADD_NOTE_BUTTON = '[data-test-subj="securitySolutionNotesAddNotesButton"]';
|
||||
|
||||
export const ADD_FILTER =
|
||||
'[data-test-subj="timeline-search-or-filter"] [data-test-subj="addFilter"]';
|
||||
|
@ -61,15 +61,13 @@ export const UNLOCKED_ICON = '[data-test-subj="timeline-date-picker-unlock-butto
|
|||
|
||||
export const ROW_ADD_NOTES_BUTTON = '[data-test-subj="timeline-notes-button-small"]';
|
||||
|
||||
export const ADD_NOTE_CONTAINER = '[data-test-subj="add-note-container"]';
|
||||
export const NOTE_CARD_CONTENT = (index: number) =>
|
||||
`[data-test-subj="securitySolutionNotesNotesComment-${index}"]`;
|
||||
|
||||
export const RESOLVER_GRAPH_CONTAINER = '[data-test-subj="resolver:graph"]';
|
||||
export const NOTE_DESCRIPTION =
|
||||
'[data-test-subj="securitySolutionNotesTimelineDescriptionComment"]';
|
||||
|
||||
export const NOTE_CARD_CONTENT = '[data-test-subj="notes"]';
|
||||
|
||||
export const NOTE_DESCRIPTION = '[data-test-subj="note-preview-description"]';
|
||||
|
||||
export const NOTES_TEXT_AREA = '[data-test-subj="add-a-note"] textarea';
|
||||
export const NOTES_TEXT_AREA = '[data-test-subj="euiMarkdownEditorTextArea"]';
|
||||
|
||||
export const NOTES_TAB_BUTTON = '[data-test-subj="timelineTabs-notes"]';
|
||||
|
||||
|
@ -81,7 +79,8 @@ export const NOTES_AUTHOR = '.euiCommentEvent__headerUsername';
|
|||
|
||||
export const NOTES_LINK = '[data-test-subj="markdown-link"]';
|
||||
|
||||
export const DELETE_NOTE = '[data-test-subj="delete-note"]';
|
||||
export const DELETE_NOTE = (index: number) =>
|
||||
`[data-test-subj="securitySolutionNotesDeleteNotesButton-${index}"]`;
|
||||
|
||||
export const MARKDOWN_INVESTIGATE_BUTTON =
|
||||
'[data-test-subj="insight-investigate-in-timeline-button"]';
|
||||
|
@ -215,8 +214,6 @@ export const TIMELINE_TITLE = '[data-test-subj="timeline-modal-header-title"]';
|
|||
|
||||
export const TIMELINE_TITLE_INPUT = '[data-test-subj="save-timeline-modal-title-input"]';
|
||||
|
||||
export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]';
|
||||
|
||||
export const TIMESTAMP_TOGGLE_FIELD =
|
||||
'[data-test-subj="actionItem-security-detailsFlyout-cellActions-toggleColumn"]';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue