/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-disable react-hooks/exhaustive-deps */
import {
EuiIcon,
EuiTabbedContent,
EuiTabbedContentTab,
EuiText,
htmlIdGenerator,
} from '@elastic/eui';
import $ from 'jquery';
import { isEmpty, map } from 'lodash';
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { LogExplorerRouterContext } from '..';
import {
APP_ANALYTICS_TAB_ID_REGEX,
CREATE_TAB_PARAM_KEY,
NEW_TAB,
REDIRECT_TAB,
SAVED_OBJECT_ID,
TAB_CHART_ID,
TAB_EVENT_ID,
TAB_ID_TXT_PFX,
TAB_TITLE,
} from '../../../../common/constants/explorer';
import { ILogExplorerProps } from '../../../../common/types/explorer';
import { initializeTabData, removeTabData } from '../../application_analytics/helpers/utils';
import { selectQueryResult } from '../redux/slices/query_result_slice';
import { selectQueries } from '../redux/slices/query_slice';
import { selectQueryTabs, setSelectedQueryTab } from '../redux/slices/query_tab_slice';
import { Explorer } from './explorer';
const searchBarConfigs = {
[TAB_EVENT_ID]: {
showSaveButton: true,
showSavePanelOptionsList: false,
},
[TAB_CHART_ID]: {
showSaveButton: true,
showSavePanelOptionsList: true,
},
};
export const LogExplorer = ({
pplService,
dslService,
savedObjects,
timestampUtils,
setToast,
savedObjectId,
getExistingEmptyTab,
notifications,
http,
queryManager,
}: ILogExplorerProps) => {
const history = useHistory();
const routerContext = useContext(LogExplorerRouterContext);
const dispatch = useDispatch();
const tabIds = useSelector(selectQueryTabs).queryTabIds.filter(
(tabid: string) => !tabid.match(APP_ANALYTICS_TAB_ID_REGEX)
);
const tabNames = useSelector(selectQueryTabs).tabNames;
const queries = useSelector(selectQueries);
const curSelectedTabId = useSelector(selectQueryTabs).selectedQueryTab;
const explorerData = useSelector(selectQueryResult);
const queryRef = useRef();
const tabIdsRef = useRef();
const explorerDataRef = useRef();
const curSelectedTabIdRef = useRef();
queryRef.current = queries;
tabIdsRef.current = tabIds;
explorerDataRef.current = explorerData;
curSelectedTabIdRef.current = curSelectedTabId;
const [tabCreatedTypes, setTabCreatedTypes] = useState({});
// Append add-new-tab link to the end of the tab list, and remove it once tabs state changes
useEffect(() => {
const newLink = $(
'+ Add new'
).on('click', () => {
addNewTab(NEW_TAB);
});
$('.queryTabs > .euiTabs').append(newLink);
return () => {
$('.queryTabs > .euiTabs .linkNewTag').remove();
};
}, [tabIds]);
const handleTabClick = (selectedTab: EuiTabbedContentTab) => {
history.replace(`/explorer/${queryRef.current![selectedTab.id][SAVED_OBJECT_ID] || ''}`);
dispatch(setSelectedQueryTab({ tabId: selectedTab.id }));
};
const handleTabClose = (TabIdToBeClosed: string) => {
if (tabIds.length === 1) {
setToast('Cannot close last tab.', 'danger');
return;
}
const index: number = tabIds.indexOf(TabIdToBeClosed);
const curSelectedTab = curSelectedTabIdRef.current;
let newIdToFocus = '';
if (TabIdToBeClosed === curSelectedTab) {
if (index === 0) {
newIdToFocus = tabIds[index + 1];
} else if (index > 0) {
newIdToFocus = tabIds[index - 1];
}
}
removeTabData(dispatch, TabIdToBeClosed, newIdToFocus);
};
const addNewTab = async (where: string) => {
// get a new tabId
const tabId = htmlIdGenerator(TAB_ID_TXT_PFX)();
// create a new tab
await initializeTabData(dispatch, tabId, where);
setTabCreatedTypes((staleState) => {
return {
...staleState,
[tabId]: where,
};
});
return tabId;
};
const dispatchSavedObjectId = async () => {
const emptyTabId = getExistingEmptyTab({
tabIds: tabIdsRef.current,
queries: queryRef.current,
explorerData: explorerDataRef.current,
});
const newTabId = emptyTabId ? emptyTabId : await addNewTab(REDIRECT_TAB);
return newTabId;
};
useEffect(() => {
if (!isEmpty(savedObjectId)) {
dispatchSavedObjectId();
}
if (routerContext && routerContext.searchParams.has(CREATE_TAB_PARAM_KEY)) {
// need to wait for current redux event loop to finish
setImmediate(() => {
addNewTab(NEW_TAB);
routerContext.searchParams.delete(CREATE_TAB_PARAM_KEY);
routerContext.routerProps.history.replace({
search: routerContext.searchParams.toString(),
});
});
}
}, []);
function getQueryTab({
tabTitle,
tabId,
handlesTabClose,
}: {
tabTitle: string;
tabId: string;
handlesTabClose: (TabIdToBeClosed: string) => void;
}) {
return {
id: tabId,
name: (
<>
{tabTitle}
{
e.stopPropagation();
handlesTabClose(tabId);
}}
data-test-subj="eventExplorer__tabClose"
/>
>
),
content: (
<>
>
),
};
}
const memorizedTabs = useMemo(() => {
const res = map(tabIds, (tabId) => {
return getQueryTab({
tabTitle: tabNames[tabId] || TAB_TITLE,
tabId,
handlesTabClose: handleTabClose,
});
});
return res;
}, [tabIds, tabNames, tabCreatedTypes]);
return (
<>
tab.id === curSelectedTabId)}
onTabClick={(selectedTab: EuiTabbedContentTab) => handleTabClick(selectedTab)}
data-test-subj="eventExplorer__topLevelTabbing"
size="s"
/>
>
);
};