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)