[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:
Kibana Machine 2024-10-31 02:34:17 +11:00 committed by GitHub
parent 8b362b9d12
commit 80ade8b15f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 158 additions and 130 deletions

View file

@ -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

View file

@ -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}
/>
)}
</>

View file

@ -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);
});
});

View file

@ -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(

View file

@ -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

View file

@ -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--;
}

View file

@ -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;

View file

@ -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();

View file

@ -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>
)}
</>
);

View file

@ -229,7 +229,7 @@ export const links: LinkItem = {
path: NOTES_PATH,
skipUrlState: true,
hideTimeline: true,
experimentalKey: 'securitySolutionNotesEnabled',
hideWhenExperimentalKey: 'securitySolutionNotesDisabled',
},
],
};

View file

@ -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} />
)}

View file

@ -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());

View file

@ -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 };

View file

@ -21,5 +21,5 @@ export const links: LinkItem = {
}),
],
links: [],
experimentalKey: 'securitySolutionNotesEnabled',
hideWhenExperimentalKey: 'securitySolutionNotesDisabled',
};

View file

@ -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();

View file

@ -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,

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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,

View file

@ -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];
})

View file

@ -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,

View file

@ -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());

View file

@ -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')

View file

@ -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);
});

View file

@ -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 */

View file

@ -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"]';