314 lines
9.6 KiB
TypeScript

import './LogsExplorer.styles.scss';
import * as Sentry from '@sentry/react';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import cx from 'classnames';
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
import { LOCALSTORAGE } from 'constants/localStorage';
import { PANEL_TYPES } from 'constants/queryBuilder';
import LogExplorerQuerySection from 'container/LogExplorerQuerySection';
import LogsExplorerViews from 'container/LogsExplorerViews';
import {
defaultLogsSelectedColumns,
defaultOptionsQuery,
URL_OPTIONS,
} from 'container/OptionsMenu/constants';
import { OptionsQuery } from 'container/OptionsMenu/types';
import LeftToolbarActions from 'container/QueryBuilder/components/ToolbarActions/LeftToolbarActions';
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import Toolbar from 'container/Toolbar/Toolbar';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { isEqual, isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { usePreferenceContext } from 'providers/preferences/context/PreferenceContextProvider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';
import { WrapperStyled } from './styles';
import { SELECTED_VIEWS } from './utils';
function LogsExplorer(): JSX.Element {
const [showFrequencyChart, setShowFrequencyChart] = useState(true);
const showFrequencyChartRef = useRef<boolean>(true);
const prevPanelTypeRef = useRef<string | null>(null);
const [selectedView, setSelectedView] = useState<SELECTED_VIEWS>(
SELECTED_VIEWS.SEARCH,
);
const { preferences, loading: preferencesLoading } = usePreferenceContext();
const [showFilters, setShowFilters] = useState<boolean>(() => {
const localStorageValue = getLocalStorageKey(
LOCALSTORAGE.SHOW_LOGS_QUICK_FILTERS,
);
if (!isNull(localStorageValue)) {
return localStorageValue === 'true';
}
return true;
});
const { handleRunQuery, currentQuery, panelType } = useQueryBuilder();
const listQueryKeyRef = useRef<any>();
const chartQueryKeyRef = useRef<any>();
const [isLoadingQueries, setIsLoadingQueries] = useState<boolean>(false);
const handleToggleShowFrequencyChart = (): void => {
setShowFrequencyChart(!showFrequencyChart);
};
const handleChangeSelectedView = (view: SELECTED_VIEWS): void => {
setSelectedView(view);
};
const handleFilterVisibilityChange = (): void => {
setLocalStorageApi(
LOCALSTORAGE.SHOW_LOGS_QUICK_FILTERS,
String(!showFilters),
);
setShowFilters((prev) => !prev);
};
// Switch to query builder view if there are more than 1 queries
useEffect(() => {
if (currentQuery.builder.queryData.length > 1) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER);
}
if (
currentQuery.builder.queryData.length === 1 &&
currentQuery.builder.queryData?.[0]?.groupBy?.length > 0
) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER);
}
}, [currentQuery.builder.queryData, currentQuery.builder.queryData.length]);
const {
redirectWithQuery: redirectWithOptionsData,
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery);
// Get and parse stored columns from localStorage
const logListOptionsFromLocalStorage = useMemo(() => {
const data = getLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS);
if (!data) return null;
try {
return JSON.parse(data);
} catch {
return null;
}
}, []);
// Check if the columns have the required columns (timestamp, body)
const hasRequiredColumns = useCallback(
(columns?: Array<{ key: string }> | null): boolean => {
if (!columns?.length) return false;
const hasTimestamp = columns.some((col) => col.key === 'timestamp');
const hasBody = columns.some((col) => col.key === 'body');
return hasTimestamp && hasBody;
},
[],
);
// Merge the columns with the required columns (timestamp, body) if missing
const mergeWithRequiredColumns = useCallback(
(columns: BaseAutocompleteData[]): BaseAutocompleteData[] => [
// Add required columns (timestamp, body) if missing
...(!hasRequiredColumns(columns) ? defaultLogsSelectedColumns : []),
...columns,
],
[hasRequiredColumns],
);
// Migrate the options query to the new format
const migrateOptionsQuery = useCallback(
(query: OptionsQuery): OptionsQuery => {
// Skip if already migrated
if (query.version) return query;
if (logListOptionsFromLocalStorage?.version) {
return logListOptionsFromLocalStorage;
}
// Case 1: we have localStorage columns
if (logListOptionsFromLocalStorage?.selectColumns?.length > 0) {
return {
...query,
version: 1,
selectColumns: mergeWithRequiredColumns(
logListOptionsFromLocalStorage.selectColumns,
),
};
}
// Case 2: No query columns in localStorage in but query has columns
if (query.selectColumns.length > 0) {
return {
...query,
version: 1,
selectColumns: mergeWithRequiredColumns(query.selectColumns),
};
}
// Case 3: No columns anywhere, use defaults
return {
...query,
version: 1,
selectColumns: defaultLogsSelectedColumns,
};
},
[mergeWithRequiredColumns, logListOptionsFromLocalStorage],
);
useEffect(() => {
if (prevPanelTypeRef.current !== panelType) {
if (panelType === PANEL_TYPES.TABLE) {
showFrequencyChartRef.current = showFrequencyChart;
setShowFrequencyChart(false);
} else if (prevPanelTypeRef.current === PANEL_TYPES.TABLE) {
setShowFrequencyChart(showFrequencyChartRef.current);
}
prevPanelTypeRef.current = panelType;
}
}, [panelType, showFrequencyChart]);
useEffect(() => {
if (!preferences || preferencesLoading) {
return;
}
const migratedQuery = migrateOptionsQuery({
selectColumns: preferences.columns || defaultLogsSelectedColumns,
maxLines: preferences.formatting?.maxLines || defaultOptionsQuery.maxLines,
format: preferences.formatting?.format || defaultOptionsQuery.format,
fontSize: preferences.formatting?.fontSize || defaultOptionsQuery.fontSize,
version: preferences.formatting?.version,
});
// Only redirect if the query was actually modified
if (
!isEqual(migratedQuery, {
selectColumns: preferences?.columns,
maxLines: preferences?.formatting?.maxLines,
format: preferences?.formatting?.format,
fontSize: preferences?.formatting?.fontSize,
version: preferences?.formatting?.version,
})
) {
redirectWithOptionsData(migratedQuery);
}
}, [
migrateOptionsQuery,
preferences,
redirectWithOptionsData,
preferencesLoading,
]);
const isMultipleQueries = useMemo(
() =>
currentQuery.builder.queryData?.length > 1 ||
currentQuery.builder.queryFormulas?.length > 0,
[currentQuery],
);
const isGroupByPresent = useMemo(
() =>
currentQuery.builder.queryData?.length === 1 &&
currentQuery.builder.queryData?.[0]?.groupBy?.length > 0,
[currentQuery.builder.queryData],
);
const toolbarViews = useMemo(
() => ({
search: {
name: 'search',
label: 'Search',
disabled: isMultipleQueries || isGroupByPresent,
show: true,
},
queryBuilder: {
name: 'query-builder',
label: 'Query Builder',
disabled: false,
show: true,
},
clickhouse: {
name: 'clickhouse',
label: 'Clickhouse',
disabled: false,
show: false,
},
}),
[isGroupByPresent, isMultipleQueries],
);
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className={cx('logs-module-page', showFilters ? 'filter-visible' : '')}>
{showFilters && (
<section className={cx('log-quick-filter-left-section')}>
<QuickFilters
className="qf-logs-explorer"
signal={SignalType.LOGS}
source={QuickFiltersSource.LOGS_EXPLORER}
handleFilterVisibilityChange={handleFilterVisibilityChange}
/>
</section>
)}
<section className={cx('log-module-right-section')}>
<Toolbar
showAutoRefresh={false}
leftActions={
<LeftToolbarActions
showFilter={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange}
items={toolbarViews}
selectedView={selectedView}
onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart={showFrequencyChart}
/>
}
rightActions={
<RightToolbarActions
onStageRunQuery={handleRunQuery}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
isLoadingQueries={isLoadingQueries}
/>
}
showOldCTA
/>
<WrapperStyled>
<div className="log-explorer-query-container">
<div>
<ExplorerCard sourcepage={DataSource.LOGS}>
<LogExplorerQuerySection selectedView={selectedView} />
</ExplorerCard>
</div>
<div className="logs-explorer-views">
<LogsExplorerViews
selectedView={selectedView}
showFrequencyChart={showFrequencyChart}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
setIsLoadingQueries={setIsLoadingQueries}
/>
</div>
</div>
</WrapperStyled>
</section>
</div>
</Sentry.ErrorBoundary>
);
}
export default LogsExplorer;