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
new file mode 100644
index 00000000000000..2d4404fd750c05
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx
@@ -0,0 +1,83 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { createMockStore, mockGlobalState } from '../../../../common/mock';
+import '../../../../common/mock/match_media';
+import { TestProviders } from '../../../../common/mock/test_providers';
+
+import { TabsContent } from '.';
+import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
+import { TimelineType } from '../../../../../common/api/timeline';
+import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_availability';
+import { render, screen, waitFor } from '@testing-library/react';
+
+jest.mock('../../../../common/hooks/esql/use_esql_availability', () => ({
+ useEsqlAvailability: jest.fn().mockReturnValue({
+ isESQLTabInTimelineEnabled: true,
+ }),
+}));
+
+const useEsqlAvailabilityMock = useEsqlAvailability as jest.Mock;
+
+describe('Timeline', () => {
+ describe('esql tab', () => {
+ const esqlTabSubj = `timelineTabs-${TimelineTabs.esql}`;
+ const defaultProps = {
+ renderCellValue: () => {},
+ rowRenderers: [],
+ timelineId: TimelineId.test,
+ timelineType: TimelineType.default,
+ timelineDescription: '',
+ };
+
+ it('should show the esql tab', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByTestId(esqlTabSubj)).toBeVisible();
+ });
+
+ it('should not show the esql tab when the advanced setting is disabled', async () => {
+ useEsqlAvailabilityMock.mockReturnValue({
+ isESQLTabInTimelineEnabled: false,
+ });
+ render(
+
+
+
+ );
+
+ 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,
+ });
+
+ const stateWithSavedSearchId = structuredClone(mockGlobalState);
+ stateWithSavedSearchId.timeline.timelineById[TimelineId.test].savedSearchId = 'test-id';
+ const mockStore = createMockStore(stateWithSavedSearchId);
+
+ render(
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(screen.queryByTestId(esqlTabSubj)).toBeVisible();
+ });
+ });
+ });
+});
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 2795624acb683c..2e164677735dd4 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
@@ -43,6 +43,7 @@ import * as i18n from './translations';
import { useLicense } from '../../../../common/hooks/use_license';
import { TIMELINE_CONVERSATION_TITLE } from '../../../../assistant/content/conversations/translations';
import { initializeTimelineSettings } from '../../../store/actions';
+import { selectTimelineESQLSavedSearchId } from '../../../store/selectors';
const HideShowContainer = styled.div.attrs<{ $isVisible: boolean; isOverflowYScroll: boolean }>(
({ $isVisible = false, isOverflowYScroll = false }) => ({
@@ -110,6 +111,10 @@ const ActiveTimelineTab = memo(
}) => {
const { hasAssistantPrivilege } = useAssistantAvailability();
const { isESQLTabInTimelineEnabled } = useEsqlAvailability();
+ const timelineESQLSavedSearch = useShallowEqualSelector((state) =>
+ selectTimelineESQLSavedSearchId(state, timelineId)
+ );
+ const shouldShowESQLTab = isESQLTabInTimelineEnabled || timelineESQLSavedSearch != null;
const aiAssistantFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode');
const getTab = useCallback(
(tab: TimelineTabs) => {
@@ -177,7 +182,7 @@ const ActiveTimelineTab = memo(
timelineId={timelineId}
/>
- {showTimeline && isESQLTabInTimelineEnabled && activeTimelineTab === TimelineTabs.esql && (
+ {showTimeline && shouldShowESQLTab && activeTimelineTab === TimelineTabs.esql && (
= ({
timelineDescription,
}) => {
const aiAssistantFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode');
- const { isESQLTabInTimelineEnabled } = useEsqlAvailability();
const { hasAssistantPrivilege } = useAssistantAvailability();
const dispatch = useDispatch();
const getActiveTab = useMemo(() => getActiveTabSelector(), []);
@@ -267,9 +271,14 @@ const TabsContentComponent: React.FC = ({
const getAppNotes = useMemo(() => getNotesSelector(), []);
const getTimelineNoteIds = useMemo(() => getNoteIdsSelector(), []);
const getTimelinePinnedEventNotes = useMemo(() => getEventIdToNoteIdsSelector(), []);
+ const { isESQLTabInTimelineEnabled } = 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 numberOfPinnedEvents = useShallowEqualSelector((state) =>
getNumberOfPinnedEvents(state, timelineId)
@@ -372,7 +381,7 @@ const TabsContentComponent: React.FC = ({
{i18n.QUERY_TAB}
{showTimeline && }
- {isESQLTabInTimelineEnabled && (
+ {shouldShowESQLTab && (
time
*/
const selectTimelineKqlQuery = createSelector(selectTimelineById, (timeline) => timeline?.kqlQuery);
+/**
+ * Selector that returns the timeline esql saved search id.
+ */
+export const selectTimelineESQLSavedSearchId = createSelector(
+ selectTimelineById,
+ (timeline) => timeline?.savedSearchId
+);
+
/**
* Selector that returns the kqlQuery.filterQuery.kuery.expression of a timeline.
*/