/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import dateMath from '@elastic/datemath';
import {
EuiButtonIcon,
EuiContextMenuItem,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiLink,
EuiLoadingSpinner,
EuiSpacer,
EuiTabbedContent,
EuiTabbedContentTab,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@osd/i18n/react';
import classNames from 'classnames';
import { isEmpty, isEqual, reduce } from 'lodash';
import React, {
ReactElement,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { batch, useDispatch, useSelector } from 'react-redux';
import { LogExplorerRouterContext } from '..';
import {
CREATE_TAB_PARAM,
CREATE_TAB_PARAM_KEY,
DATE_PICKER_FORMAT,
DEFAULT_AVAILABILITY_QUERY,
EVENT_ANALYTICS_DOCUMENTATION_URL,
NEW_TAB,
PATTERNS_EXTRACTOR_REGEX,
PATTERNS_REGEX,
RAW_QUERY,
SAVED_OBJECT_ID,
SAVED_OBJECT_TYPE,
SAVED_QUERY,
SAVED_VISUALIZATION,
SELECTED_DATE_RANGE,
SELECTED_FIELDS,
SELECTED_PATTERN_FIELD,
SELECTED_TIMESTAMP,
TAB_CHART_ID,
TAB_CHART_TITLE,
TAB_CREATED_TYPE,
TAB_EVENT_ID,
TAB_EVENT_TITLE,
TIME_INTERVAL_OPTIONS,
} from '../../../../common/constants/explorer';
import {
LIVE_END_TIME,
LIVE_OPTIONS,
PPL_NEWLINE_REGEX,
PPL_STATS_REGEX,
} from '../../../../common/constants/shared';
import { QueryManager } from '../../../../common/query_manager';
import {
IExplorerFields,
IExplorerProps,
IField,
IQuery,
IQueryTab,
IVisualizationContainerProps,
} from '../../../../common/types/explorer';
import {
buildQuery,
buildRawQuery,
getIndexPatternFromRawQuery,
uiSettingsService,
} from '../../../../common/utils';
import { PPLDataFetcher } from '../../../services/data_fetchers/ppl/ppl_data_fetcher';
import { getSavedObjectsClient } from '../../../services/saved_objects/saved_object_client/client_factory';
import { OSDSavedVisualizationClient } from '../../../services/saved_objects/saved_object_client/osd_saved_objects/saved_visualization';
import {
PanelSavedObjectClient,
PPLSavedQueryClient,
} from '../../../services/saved_objects/saved_object_client/ppl';
import { PPLSavedObjectLoader } from '../../../services/saved_objects/saved_object_loaders/ppl/ppl_loader';
import {
SaveAsCurrentQuery,
SaveAsCurrentVisualization,
SaveAsNewVisualization,
} from '../../../services/saved_objects/saved_object_savers';
import { SaveAsNewQuery } from '../../../services/saved_objects/saved_object_savers/ppl/save_as_new_query';
import { sleep } from '../../common/live_tail/live_tail_button';
import { onItemSelect, parseGetSuggestions } from '../../common/search/autocomplete_logic';
import { Search } from '../../common/search/search';
import { getVizContainerProps } from '../../visualizations/charts/helpers';
import { TabContext, useFetchEvents, useFetchPatterns, useFetchVisualizations } from '../hooks';
import { selectCountDistribution } from '../redux/slices/count_distribution_slice';
import { selectFields, updateFields } from '../redux/slices/field_slice';
import { selectQueryResult } from '../redux/slices/query_result_slice';
import { changeDateRange, changeQuery, selectQueries } from '../redux/slices/query_slice';
import { updateTabName } from '../redux/slices/query_tab_slice';
import { selectExplorerVisualization } from '../redux/slices/visualization_slice';
import {
change as changeVisualizationConfig,
change as changeVizConfig,
change as updateVizConfig,
selectVisualizationConfig,
} from '../redux/slices/viualization_config_slice';
import { formatError, getDefaultVisConfig } from '../utils';
import { getContentTabTitle, getDateRange } from '../utils/utils';
import { DataGrid } from './events_views/data_grid';
import { HitsCounter } from './hits_counter/hits_counter';
import { LogPatterns } from './log_patterns/log_patterns';
import { NoResults } from './no_results';
import { Sidebar } from './sidebar';
import { TimechartHeader } from './timechart_header';
import { ExplorerVisualizations } from './visualizations';
import { CountDistribution } from './visualizations/count_distribution';
export const Explorer = ({
pplService,
dslService,
tabId,
savedObjects,
timestampUtils,
setToast,
http,
history,
notifications,
savedObjectId,
curSelectedTabId,
searchBarConfigs,
appId = '',
appBaseQuery = '',
addVisualizationToPanel,
startTime,
endTime,
setStartTime,
setEndTime,
callback,
callbackInApp,
queryManager = new QueryManager(),
}: IExplorerProps) => {
const routerContext = useContext(LogExplorerRouterContext);
const dispatch = useDispatch();
const requestParams = { tabId };
const { getLiveTail, getEvents, getAvailableFields } = useFetchEvents({
pplService,
requestParams,
});
const { getCountVisualizations } = useFetchVisualizations({
pplService,
requestParams,
});
const {
isEventsLoading: isPatternLoading,
getPatterns,
setDefaultPatternsField,
} = useFetchPatterns({
pplService,
requestParams,
});
const appLogEvents = tabId.startsWith('application-analytics-tab');
const query = useSelector(selectQueries)[tabId];
const explorerData = useSelector(selectQueryResult)[tabId];
const explorerFields = useSelector(selectFields)[tabId];
const countDistribution = useSelector(selectCountDistribution)[tabId];
const explorerVisualizations = useSelector(selectExplorerVisualization)[tabId];
const userVizConfigs = useSelector(selectVisualizationConfig)[tabId] || {};
const [selectedContentTabId, setSelectedContentTab] = useState(TAB_EVENT_ID);
const [selectedCustomPanelOptions, setSelectedCustomPanelOptions] = useState([]);
const [selectedPanelName, setSelectedPanelName] = useState('');
const [curVisId, setCurVisId] = useState('bar');
const [isPanelTextFieldInvalid, setIsPanelTextFieldInvalid] = useState(false);
const [isSidebarClosed, setIsSidebarClosed] = useState(false);
const [timeIntervalOptions, setTimeIntervalOptions] = useState(TIME_INTERVAL_OPTIONS);
const [isOverridingTimestamp, setIsOverridingTimestamp] = useState(false);
const [isOverridingPattern, setIsOverridingPattern] = useState(false);
const [tempQuery, setTempQuery] = useState(query[RAW_QUERY]);
const [isLiveTailPopoverOpen, setIsLiveTailPopoverOpen] = useState(false);
const [isLiveTailOn, setIsLiveTailOn] = useState(false);
const [liveTailTabId, setLiveTailTabId] = useState(TAB_EVENT_ID);
const [liveTailName, setLiveTailName] = useState('Live');
const [liveHits, setLiveHits] = useState(0);
const [browserTabFocus, setBrowserTabFocus] = useState(true);
const [liveTimestamp, setLiveTimestamp] = useState(DATE_PICKER_FORMAT);
const [triggerAvailability, setTriggerAvailability] = useState(false);
const selectedIntervalRef = useRef<{
text: string;
value: string;
}>();
const [subType, setSubType] = useState('visualization');
const [metricMeasure, setMetricMeasure] = useState('');
const [metricChecked, setMetricChecked] = useState(false);
const queryRef = useRef();
const appBasedRef = useRef('');
appBasedRef.current = appBaseQuery;
const selectedPanelNameRef = useRef('');
const explorerFieldsRef = useRef();
const isLiveTailOnRef = useRef(false);
const liveTailTabIdRef = useRef('');
const liveTailNameRef = useRef('Live');
queryRef.current = query;
selectedPanelNameRef.current = selectedPanelName;
explorerFieldsRef.current = explorerFields;
isLiveTailOnRef.current = isLiveTailOn;
liveTailTabIdRef.current = liveTailTabId;
liveTailNameRef.current = liveTailName;
const findAutoInterval = (start: string = '', end: string = '') => {
const momentStart = dateMath.parse(start)!;
const momentEnd = dateMath.parse(end, { roundUp: true })!;
const diffSeconds = momentEnd.unix() - momentStart.unix();
let minInterval = 'y';
// less than 1 second
if (diffSeconds <= 1) minInterval = 'ms';
// less than 2 minutes
else if (diffSeconds <= 60 * 2) minInterval = 's';
// less than 2 hours
else if (diffSeconds <= 3600 * 2) minInterval = 'm';
// less than 2 days
else if (diffSeconds <= 86400 * 2) minInterval = 'h';
// less than 1 month
else if (diffSeconds <= 86400 * 31) minInterval = 'd';
// less than 3 months
else if (diffSeconds <= 86400 * 93) minInterval = 'w';
// less than 1 year
else if (diffSeconds <= 86400 * 366) minInterval = 'M';
setTimeIntervalOptions([
{ text: 'Auto', value: 'auto_' + minInterval },
...TIME_INTERVAL_OPTIONS,
]);
selectedIntervalRef.current = { text: 'Auto', value: 'auto_' + minInterval };
};
useEffect(() => {
const handleSetBrowserTabFocus = () => {
if (document.hidden) setBrowserTabFocus(false);
else setBrowserTabFocus(true);
};
document.addEventListener('visibilitychange', handleSetBrowserTabFocus);
return () => {
document.removeEventListener('visibilitychange', handleSetBrowserTabFocus);
};
}, []);
const getErrorHandler = (title: string) => {
return (error: any) => {
const formattedError = formatError(error.name, error.message, error.body.message);
notifications.toasts.addError(formattedError, {
title,
});
};
};
const fetchData = async (startingTime?: string, endingTime?: string) => {
const curQuery: IQuery = queryRef.current!;
new PPLDataFetcher(
{ ...curQuery },
{ batch, dispatch, changeQuery, changeVizConfig },
{
tabId,
findAutoInterval,
getCountVisualizations,
getLiveTail,
getEvents,
getErrorHandler,
getPatterns,
setDefaultPatternsField,
timestampUtils,
curVisId,
selectedContentTabId,
queryManager,
getDefaultVisConfig,
getAvailableFields,
},
{
appBaseQuery,
query: curQuery,
startingTime,
endingTime,
isLiveTailOn: isLiveTailOnRef.current,
selectedInterval: selectedIntervalRef,
},
notifications
).search();
};
const isIndexPatternChanged = (currentQuery: string, prevTabQuery: string) =>
!isEqual(getIndexPatternFromRawQuery(currentQuery), getIndexPatternFromRawQuery(prevTabQuery));
const updateTabData = async (objectId: string) => {
await new PPLSavedObjectLoader(
getSavedObjectsClient({ objectId, objectType: 'savedQuery' }),
notifications,
{
batch,
dispatch,
changeQuery,
updateFields,
updateTabName,
updateVizConfig,
},
{ objectId },
{
tabId,
appLogEvents,
setStartTime,
setEndTime,
queryManager,
getDefaultVisConfig,
setSelectedPanelName,
setCurVisId,
setTempQuery,
setMetricChecked,
setMetricMeasure,
setSubType,
setSelectedContentTab,
fetchData,
}
).load();
};
const prepareAvailability = async () => {
setSelectedContentTab(TAB_CHART_ID);
setTriggerAvailability(true);
await setTempQuery(buildQuery(appBaseQuery, DEFAULT_AVAILABILITY_QUERY));
await updateQueryInStore(buildQuery(appBaseQuery, DEFAULT_AVAILABILITY_QUERY));
await handleTimeRangePickerRefresh(true);
};
useEffect(() => {
if (!isEmpty(appBasedRef.current)) {
if (callback) {
callback(() => prepareAvailability());
}
if (callbackInApp) {
callbackInApp(() => prepareAvailability());
}
}
}, [appBasedRef.current]);
useEffect(() => {
let objectId;
if (queryRef.current![TAB_CREATED_TYPE] === NEW_TAB || appLogEvents) {
objectId = queryRef.current!.savedObjectId || '';
} else {
objectId = queryRef.current!.savedObjectId || savedObjectId;
}
if (objectId) {
updateTabData(objectId);
} else {
fetchData(startTime, endTime);
}
if (
routerContext &&
routerContext.searchParams.get(CREATE_TAB_PARAM_KEY) === CREATE_TAB_PARAM[TAB_CHART_ID]
) {
setSelectedContentTab(TAB_CHART_ID);
}
}, []);
useEffect(() => {
if (appLogEvents) {
if (savedObjectId) {
updateTabData(savedObjectId);
}
}
}, [savedObjectId]);
const handleTimePickerChange = async (timeRange: string[]) => {
if (appLogEvents) {
setStartTime(timeRange[0]);
setEndTime(timeRange[1]);
}
await dispatch(
changeDateRange({
tabId: requestParams.tabId,
data: {
[RAW_QUERY]: queryRef.current![RAW_QUERY],
[SELECTED_DATE_RANGE]: timeRange,
},
})
);
};
const showPermissionErrorToast = () => {
setToast(
'Please ask your administrator to enable Event Analytics for you.',
'danger',