/* eslint-disable sonarjs/cognitive-complexity */ import './LogsExplorerViews.styles.scss'; import { Button } from 'antd'; import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { LOCALSTORAGE } from 'constants/localStorage'; import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes'; import { QueryParams } from 'constants/query'; import { initialFilters, initialQueriesMap, initialQueryBuilderFormValues, PANEL_TYPES, } from 'constants/queryBuilder'; import { DEFAULT_PER_PAGE_VALUE } from 'container/Controls/config'; import Download from 'container/DownloadV2/DownloadV2'; import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper'; import GoToTop from 'container/GoToTop'; import LogsExplorerChart from 'container/LogsExplorerChart'; import LogsExplorerList from 'container/LogsExplorerList'; import LogsExplorerTable from 'container/LogsExplorerTable'; import { useOptionsMenu } from 'container/OptionsMenu'; import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; import dayjs from 'dayjs'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; import { LogTimeRange } from 'hooks/logs/types'; import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import useAxiosError from 'hooks/useAxiosError'; import useClickOutside from 'hooks/useClickOutside'; import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange'; import { useNotifications } from 'hooks/useNotifications'; import useUrlQueryData from 'hooks/useUrlQueryData'; import { FlatLogData } from 'lib/logs/flatLogData'; import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData'; import { cloneDeep, defaultTo, isEmpty, omit, set } from 'lodash-es'; import { Sliders } from 'lucide-react'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { Dashboard } from 'types/api/dashboard/getAll'; import { ILog } from 'types/api/logs/log'; import { IBuilderQuery, OrderByPayload, Query, TagFilter, } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource, LogsAggregatorOperator, StringOperators, } from 'types/common/queryBuilder'; import { GlobalReducer } from 'types/reducer/globalTime'; import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink'; import { v4 } from 'uuid'; function LogsExplorerViews({ selectedView, showFrequencyChart, }: { selectedView: SELECTED_VIEWS; showFrequencyChart: boolean; }): JSX.Element { const { notifications } = useNotifications(); const history = useHistory(); // this is to respect the panel type present in the URL rather than defaulting it to list always. const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); const { activeLogId, timeRange, onTimeRangeChange } = useCopyLogLink(); const { queryData: pageSize } = useUrlQueryData( QueryParams.pageSize, DEFAULT_PER_PAGE_VALUE, ); const { minTime } = useSelector( (state) => state.globalTime, ); const currentMinTimeRef = useRef(minTime); // Context const { initialDataSource, currentQuery, stagedQuery, panelType, updateAllQueriesOperators, handleSetConfig, } = useQueryBuilder(); const [selectedPanelType, setSelectedPanelType] = useState( panelType || PANEL_TYPES.LIST, ); const { handleExplorerTabChange } = useHandleExplorerTabChange(); // State const [page, setPage] = useState(1); const [logs, setLogs] = useState([]); const [requestData, setRequestData] = useState(null); const [showFormatMenuItems, setShowFormatMenuItems] = useState(false); const handleAxisError = useAxiosError(); const listQuery = useMemo(() => { if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null; return stagedQuery.builder.queryData.find((item) => !item.disabled) || null; }, [stagedQuery]); const { options, config } = useOptionsMenu({ storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS, dataSource: initialDataSource || DataSource.LOGS, aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP, }); const orderByTimestamp: OrderByPayload | null = useMemo(() => { const timestampOrderBy = listQuery?.orderBy.find( (item) => item.columnName === 'timestamp', ); return timestampOrderBy || null; }, [listQuery]); const isMultipleQueries = useMemo( () => currentQuery.builder.queryData.length > 1 || currentQuery.builder.queryFormulas.length > 0, [currentQuery], ); const isGroupByExist = useMemo(() => { const groupByCount: number = currentQuery.builder.queryData.reduce( (acc, query) => acc + query.groupBy.length, 0, ); return groupByCount > 0; }, [currentQuery]); const isLimit: boolean = useMemo(() => { if (!listQuery) return false; if (!listQuery.limit) return false; return logs.length >= listQuery.limit; }, [logs.length, listQuery]); const listChartQuery = useMemo(() => { if (!stagedQuery || !listQuery) return null; const modifiedQueryData: IBuilderQuery = { ...listQuery, aggregateOperator: LogsAggregatorOperator.COUNT, }; const modifiedQuery: Query = { ...stagedQuery, builder: { ...stagedQuery.builder, queryData: stagedQuery.builder.queryData.map((item) => ({ ...item, ...modifiedQueryData, })), }, }; return modifiedQuery; }, [stagedQuery, listQuery]); const exportDefaultQuery = useMemo( () => updateAllQueriesOperators( currentQuery || initialQueriesMap.logs, selectedPanelType, DataSource.LOGS, ), [currentQuery, selectedPanelType, updateAllQueriesOperators], ); const handleModeChange = (panelType: PANEL_TYPES): void => { if (selectedView === SELECTED_VIEWS.SEARCH) { handleSetConfig(panelType, DataSource.LOGS); } setShowFormatMenuItems(false); handleExplorerTabChange(panelType); }; const { data: listChartData, isFetching: isFetchingListChartData, isLoading: isLoadingListChartData, } = useGetExplorerQueryRange( listChartQuery, PANEL_TYPES.TIME_SERIES, DEFAULT_ENTITY_VERSION, { enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST, }, ); const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange( requestData, panelType, DEFAULT_ENTITY_VERSION, { keepPreviousData: true, enabled: !isLimit && !!requestData, }, { ...(timeRange && activeLogId && !logs.length && { start: timeRange.start, end: timeRange.end, }), }, ); const getRequestData = useCallback( ( query: Query | null, params: { page: number; log: ILog | null; pageSize: number; filters: TagFilter; }, ): Query | null => { if (!query) return null; const paginateData = getPaginationQueryData({ filters: params.filters, listItemId: params.log ? params.log.id : null, orderByTimestamp, page: params.page, pageSize: params.pageSize, }); const queryData: IBuilderQuery[] = query.builder.queryData.length > 1 ? query.builder.queryData : [ { ...(listQuery || initialQueryBuilderFormValues), ...paginateData, }, ]; const data: Query = { ...query, builder: { ...query.builder, queryData, }, }; return data; }, [orderByTimestamp, listQuery], ); const handleEndReached = useCallback( (index: number) => { if (!listQuery) return; if (isLimit) return; if (logs.length < pageSize) return; const { limit, filters } = listQuery; const lastLog = logs[index]; const nextLogsLength = logs.length + pageSize; const nextPageSize = limit && nextLogsLength >= limit ? limit - logs.length : pageSize; if (!stagedQuery) return; const newRequestData = getRequestData(stagedQuery, { filters, page: page + 1, log: orderByTimestamp ? lastLog : null, pageSize: nextPageSize, }); setPage((prevPage) => prevPage + 1); setRequestData(newRequestData); }, [ isLimit, logs, listQuery, pageSize, stagedQuery, getRequestData, page, orderByTimestamp, ], ); const { mutate: updateDashboard, isLoading: isUpdateDashboardLoading, } = useUpdateDashboard(); const getUpdatedQueryForExport = useCallback((): Query => { const updatedQuery = cloneDeep(currentQuery); set(updatedQuery, 'builder.queryData[0].pageSize', 10); return updatedQuery; }, [currentQuery]); const handleExport = useCallback( (dashboard: Dashboard | null): void => { if (!dashboard || !panelType) return; const panelTypeParam = AVAILABLE_EXPORT_PANEL_TYPES.includes(panelType) ? panelType : PANEL_TYPES.TIME_SERIES; const widgetId = v4(); const query = panelType === PANEL_TYPES.LIST ? getUpdatedQueryForExport() : exportDefaultQuery; const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery( dashboard, query, widgetId, panelTypeParam, options.selectColumns, ); updateDashboard(updatedDashboard, { onSuccess: (data) => { if (data.error) { const message = data.error === 'feature usage exceeded' ? ( Panel limit exceeded for {DataSource.LOGS} in community edition. Please checkout our paid plans{' '} here ) : ( data.error ); notifications.error({ message, }); return; } const dashboardEditView = generateExportToDashboardLink({ query, panelType: panelTypeParam, dashboardId: data.payload?.uuid || '', widgetId, }); history.push(dashboardEditView); }, onError: handleAxisError, }); }, [ getUpdatedQueryForExport, exportDefaultQuery, options.selectColumns, history, notifications, panelType, updateDashboard, handleAxisError, ], ); useEffect(() => { const shouldChangeView = (isMultipleQueries || isGroupByExist) && selectedView !== SELECTED_VIEWS.SEARCH; if (selectedPanelType === PANEL_TYPES.LIST && shouldChangeView) { handleExplorerTabChange(PANEL_TYPES.TIME_SERIES); setSelectedPanelType(PANEL_TYPES.TIME_SERIES); } if (panelType) { setSelectedPanelType(panelType); } }, [ isMultipleQueries, isGroupByExist, selectedPanelType, selectedView, handleExplorerTabChange, panelType, ]); useEffect(() => { if ( selectedView && selectedView === SELECTED_VIEWS.SEARCH && handleSetConfig ) { handleSetConfig(defaultTo(panelTypes, PANEL_TYPES.LIST), DataSource.LOGS); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [handleSetConfig, panelTypes]); useEffect(() => { const currentParams = data?.params as Omit; const currentData = data?.payload?.data?.newResult?.data?.result || []; if (currentData.length > 0 && currentData[0].list) { const currentLogs: ILog[] = currentData[0].list.map((item) => ({ ...item.data, timestamp: item.timestamp, })); const newLogs = [...logs, ...currentLogs]; setLogs(newLogs); onTimeRangeChange({ start: currentParams?.start, end: timeRange?.end || currentParams?.end, pageSize: newLogs.length, }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); useEffect(() => { if ( requestData?.id !== stagedQuery?.id || currentMinTimeRef.current !== minTime ) { const newRequestData = getRequestData(stagedQuery, { filters: listQuery?.filters || initialFilters, page: 1, log: null, pageSize: timeRange?.pageSize && activeLogId ? timeRange?.pageSize : pageSize, }); setLogs([]); setPage(1); setRequestData(newRequestData); currentMinTimeRef.current = minTime; } }, [ stagedQuery, requestData, getRequestData, listQuery, pageSize, minTime, timeRange, activeLogId, onTimeRangeChange, panelType, selectedView, ]); const chartData = useMemo(() => { if (!stagedQuery) return []; if (panelType === PANEL_TYPES.LIST) { if (listChartData && listChartData.payload.data.result.length > 0) { return listChartData.payload.data.result; } return []; } if (!data || data.payload.data.result.length === 0) return []; const isGroupByExist = stagedQuery.builder.queryData.some( (queryData) => queryData.groupBy.length > 0, ); const firstPayloadQuery = data.payload.data.result.find( (item) => item.queryName === listQuery?.queryName, ); const firstPayloadQueryArray = firstPayloadQuery ? [firstPayloadQuery] : []; return isGroupByExist ? data.payload.data.result : firstPayloadQueryArray; }, [stagedQuery, panelType, data, listChartData, listQuery]); const formatItems = [ { key: 'raw', label: 'Raw', data: { title: 'max lines per row', }, }, { key: 'list', label: 'Default', }, { key: 'table', label: 'Column', data: { title: 'columns', }, }, ]; const handleToggleShowFormatOptions = (): void => setShowFormatMenuItems(!showFormatMenuItems); const menuRef = useRef(null); useClickOutside({ ref: menuRef, onClickOutside: () => { if (showFormatMenuItems) { setShowFormatMenuItems(false); } }, }); const flattenLogData = useMemo( () => logs.map((log) => { const timestamp = typeof log.timestamp === 'string' ? dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS') : dayjs(log.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'); return FlatLogData({ timestamp, body: log.body, ...omit(log, 'timestamp', 'body'), }); }), [logs], ); return (
{showFrequencyChart && ( )}
{selectedPanelType === PANEL_TYPES.LIST && (
)}
{selectedPanelType === PANEL_TYPES.LIST && ( )} {selectedPanelType === PANEL_TYPES.TIME_SERIES && ( )} {selectedPanelType === PANEL_TYPES.TABLE && ( )}
); } export default memo(LogsExplorerViews);