From f718f2c2f21f5127fbd557c34f53ab88b01e863c Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 8 May 2024 08:21:30 +0200 Subject: [PATCH] [Security Solution] Timeline : Disabling Timeline ESQL feature flag should disable ESQL Tab. (#182816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary handles https://github.com/elastic/kibana/issues/182798 Recently there was PR : https://github.com/elastic/kibana/pull/181616 which does not disable ES|QL tab in timeline even if feature flag is disabled when : - User has already created a ESQL Query in timeline and saved the timeline. This PR makes sure when below feature flag exists, then `ES|QL` tab will be definitely disabled even when user has a saved timeline with ES|QL Query in it. ```yaml xpack.securitySolution.enableExperimental: - timelineEsqlTabDisabled ``` ## Desk Testing Guidelines 1. Remove above Feature Flag 2. Go to Timeline and Create a Timeline with ESQL Query 3. Save the timeline. 4. Go To advanced Settings and search for `esql` and disable the `enableESQL` setting. 5. Go back to the timeline saved in step 3. 6. ✅ Assert that the `ES|QL` tab is still there. 7. ✅ Assert that the `ES|QL` tab is NOT there in a new timeline. 8. Add above feature flag - which disables the esql Tab. 9. Go back to the timeline saved in step 3. 10. ✅ Assert that the `ES|QL` tab is no longer there. 11. ✅ Assert that the `ES|QL` tab is NOT there in a new timeline. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios (cherry picked from commit c43da3e4349a6bb13cdab507530efd926147c30b) --- .../hooks/esql/use_esql_availability.ts | 10 ++- .../components/timeline/tabs/index.test.tsx | 90 ++++++++++++++----- .../components/timeline/tabs/index.tsx | 27 +++++- 3 files changed, 95 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/hooks/esql/use_esql_availability.ts b/x-pack/plugins/security_solution/public/common/hooks/esql/use_esql_availability.ts index 41fc7084b32bf..3e9bdb658c3e1 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/esql/use_esql_availability.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/esql/use_esql_availability.ts @@ -18,17 +18,19 @@ import { useIsExperimentalFeatureEnabled } from '../use_experimental_features'; export const useEsqlAvailability = () => { const { uiSettings } = useKibana().services; const isEsqlAdvancedSettingEnabled = uiSettings?.get(ENABLE_ESQL); + + const isTimelineEsqlFeatureFlagDisabled = + useIsExperimentalFeatureEnabled('timelineEsqlTabDisabled'); + const isEsqlRuleTypeEnabled = !useIsExperimentalFeatureEnabled('esqlRulesDisabled') && isEsqlAdvancedSettingEnabled; - const isESQLTabInTimelineEnabled = - !useIsExperimentalFeatureEnabled('timelineEsqlTabDisabled') && isEsqlAdvancedSettingEnabled; return useMemo( () => ({ isEsqlAdvancedSettingEnabled, isEsqlRuleTypeEnabled, - isESQLTabInTimelineEnabled, + isTimelineEsqlEnabledByFeatureFlag: !isTimelineEsqlFeatureFlagDisabled, }), - [isESQLTabInTimelineEnabled, isEsqlAdvancedSettingEnabled, isEsqlRuleTypeEnabled] + [isEsqlAdvancedSettingEnabled, isTimelineEsqlFeatureFlagDisabled, isEsqlRuleTypeEnabled] ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx index 7f399aa095a8a..4d0a8f5cfd363 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx @@ -18,7 +18,8 @@ import { render, screen, waitFor } from '@testing-library/react'; jest.mock('../../../../common/hooks/esql/use_esql_availability', () => ({ useEsqlAvailability: jest.fn().mockReturnValue({ - isESQLTabInTimelineEnabled: true, + isEsqlAdvancedSettingEnabled: true, + isTimelineEsqlEnabledByFeatureFlag: true, }), })); @@ -44,38 +45,79 @@ describe('Timeline', () => { expect(screen.getByTestId(esqlTabSubj)).toBeVisible(); }); - it('should not show the esql tab when the advanced setting is disabled', async () => { - useEsqlAvailabilityMock.mockReturnValue({ - isESQLTabInTimelineEnabled: false, + describe('no existing esql query is present', () => { + it('should not show the esql tab when the advanced setting is disabled', async () => { + useEsqlAvailabilityMock.mockReturnValue({ + isEsqlAdvancedSettingEnabled: false, + isTimelineEsqlEnabledByFeatureFlag: true, + }); + render( + + + + ); + + await waitFor(() => { + expect(screen.queryByTestId(esqlTabSubj)).toBeNull(); + }); }); - render( - - - - ); + it('should not show the esql tab when the esql is disabled by feature flag', async () => { + useEsqlAvailabilityMock.mockReturnValue({ + isEsqlAdvancedSettingEnabled: false, + isTimelineEsqlEnabledByFeatureFlag: false, + }); + render( + + + + ); - await waitFor(() => { - expect(screen.queryByTestId(esqlTabSubj)).toBeNull(); + await waitFor(() => { + expect(screen.queryByTestId(esqlTabSubj)).toBeNull(); + }); }); }); - it('should show the esql tab when the advanced setting is disabled, but an esql query is present', async () => { - useEsqlAvailabilityMock.mockReturnValue({ - isESQLTabInTimelineEnabled: false, + describe('existing esql query is present', () => { + let mockStore: ReturnType; + beforeEach(() => { + const stateWithSavedSearchId = structuredClone(mockGlobalState); + stateWithSavedSearchId.timeline.timelineById[TimelineId.test].savedSearchId = 'test-id'; + mockStore = createMockStore(stateWithSavedSearchId); }); - const stateWithSavedSearchId = structuredClone(mockGlobalState); - stateWithSavedSearchId.timeline.timelineById[TimelineId.test].savedSearchId = 'test-id'; - const mockStore = createMockStore(stateWithSavedSearchId); + it('should show the esql tab when the advanced setting is disabled', async () => { + useEsqlAvailabilityMock.mockReturnValue({ + isESQLTabInTimelineEnabled: false, + isTimelineEsqlEnabledByFeatureFlag: true, + }); - render( - - - - ); + render( + + + + ); + + await waitFor(() => { + expect(screen.queryByTestId(esqlTabSubj)).toBeVisible(); + }); + }); + + it('should not show the esql tab when the esql is disabled by the feature flag', async () => { + useEsqlAvailabilityMock.mockReturnValue({ + isESQLTabInTimelineEnabled: true, + isTimelineEsqlEnabledByFeatureFlag: false, + }); + + render( + + + + ); - await waitFor(() => { - expect(screen.queryByTestId(esqlTabSubj)).toBeVisible(); + await waitFor(() => { + expect(screen.queryByTestId(esqlTabSubj)).toBeNull(); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx index 2e164677735dd..643a5b54be415 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx @@ -110,11 +110,20 @@ const ActiveTimelineTab = memo( showTimeline, }) => { const { hasAssistantPrivilege } = useAssistantAvailability(); - const { isESQLTabInTimelineEnabled } = useEsqlAvailability(); + const { isTimelineEsqlEnabledByFeatureFlag, isEsqlAdvancedSettingEnabled } = + useEsqlAvailability(); const timelineESQLSavedSearch = useShallowEqualSelector((state) => selectTimelineESQLSavedSearchId(state, timelineId) ); - const shouldShowESQLTab = isESQLTabInTimelineEnabled || timelineESQLSavedSearch != null; + const shouldShowESQLTab = useMemo(() => { + // disabling esql feature from feature flag should unequivocally hide the tab + // irrespective of the fact that the advanced setting is enabled or + // not or existing esql query is present or not + if (!isTimelineEsqlEnabledByFeatureFlag) { + return false; + } + return isEsqlAdvancedSettingEnabled || timelineESQLSavedSearch != null; + }, [isEsqlAdvancedSettingEnabled, isTimelineEsqlEnabledByFeatureFlag, timelineESQLSavedSearch]); const aiAssistantFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode'); const getTab = useCallback( (tab: TimelineTabs) => { @@ -271,14 +280,24 @@ const TabsContentComponent: React.FC = ({ const getAppNotes = useMemo(() => getNotesSelector(), []); const getTimelineNoteIds = useMemo(() => getNoteIdsSelector(), []); const getTimelinePinnedEventNotes = useMemo(() => getEventIdToNoteIdsSelector(), []); - const { isESQLTabInTimelineEnabled } = useEsqlAvailability(); + const { isEsqlAdvancedSettingEnabled, isTimelineEsqlEnabledByFeatureFlag } = + useEsqlAvailability(); + const timelineESQLSavedSearch = useShallowEqualSelector((state) => selectTimelineESQLSavedSearchId(state, timelineId) ); const activeTab = useShallowEqualSelector((state) => getActiveTab(state, timelineId)); const showTimeline = useShallowEqualSelector((state) => getShowTimeline(state, timelineId)); - const shouldShowESQLTab = isESQLTabInTimelineEnabled || timelineESQLSavedSearch != null; + const shouldShowESQLTab = useMemo(() => { + // disabling esql feature from feature flag should unequivocally hide the tab + // irrespective of the fact that the advanced setting is enabled or + // not or existing esql query is present or not + if (!isTimelineEsqlEnabledByFeatureFlag) { + return false; + } + return isEsqlAdvancedSettingEnabled || timelineESQLSavedSearch != null; + }, [isEsqlAdvancedSettingEnabled, isTimelineEsqlEnabledByFeatureFlag, timelineESQLSavedSearch]); const numberOfPinnedEvents = useShallowEqualSelector((state) => getNumberOfPinnedEvents(state, timelineId)