Outline: Delegate log level filtering to the Logs panel (#106973)
* Content Outline: Depreacte log level filtering * LogsVolumePanelList: refactor series visibility callback * Logs Volume: sync filters with panel * ExploreGraph: add toggleLegend support to remove overrides * Explore: implement two way level filtering from Panel or Logs Volume * Remove log * ExploreGraph: remove buggy ref * Update test
This commit is contained in:
@@ -58,7 +58,7 @@ interface Props {
|
||||
thresholdsStyle?: GraphThresholdsStyleConfig;
|
||||
eventBus: EventBus;
|
||||
vizLegendOverrides?: Partial<VizLegendOptions>;
|
||||
toggleLegendRef?: React.MutableRefObject<(name: string, mode: SeriesVisibilityChangeMode) => void>;
|
||||
toggleLegendRef?: React.MutableRefObject<(name: string | undefined, mode: SeriesVisibilityChangeMode) => void>;
|
||||
}
|
||||
|
||||
export function ExploreGraph({
|
||||
@@ -141,27 +141,27 @@ export function ExploreGraph({
|
||||
|
||||
const structureRev = useStructureRev(dataWithConfig);
|
||||
|
||||
const onHiddenSeriesChangedRef = useRef(onHiddenSeriesChanged);
|
||||
const previousHiddenFrames = useRef<string[] | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (onHiddenSeriesChangedRef.current) {
|
||||
const hiddenFrames: string[] = [];
|
||||
dataWithConfig.forEach((frame) => {
|
||||
const allFieldsHidden = frame.fields.map((field) => field.config?.custom?.hideFrom?.viz).every(identity);
|
||||
if (allFieldsHidden) {
|
||||
hiddenFrames.push(getFrameDisplayName(frame));
|
||||
}
|
||||
});
|
||||
if (
|
||||
previousHiddenFrames.current === undefined ||
|
||||
!isEqual(sortBy(hiddenFrames), sortBy(previousHiddenFrames.current))
|
||||
) {
|
||||
previousHiddenFrames.current = hiddenFrames;
|
||||
onHiddenSeriesChangedRef.current(hiddenFrames);
|
||||
}
|
||||
if (!onHiddenSeriesChanged) {
|
||||
return;
|
||||
}
|
||||
}, [dataWithConfig]);
|
||||
const hiddenFrames: string[] = [];
|
||||
dataWithConfig.forEach((frame) => {
|
||||
const allFieldsHidden = frame.fields.map((field) => field.config?.custom?.hideFrom?.viz).every(identity);
|
||||
if (allFieldsHidden) {
|
||||
hiddenFrames.push(getFrameDisplayName(frame));
|
||||
}
|
||||
});
|
||||
if (
|
||||
previousHiddenFrames.current === undefined ||
|
||||
!isEqual(sortBy(hiddenFrames), sortBy(previousHiddenFrames.current))
|
||||
) {
|
||||
previousHiddenFrames.current = hiddenFrames;
|
||||
onHiddenSeriesChanged(hiddenFrames);
|
||||
}
|
||||
}, [dataWithConfig, onHiddenSeriesChanged]);
|
||||
|
||||
const panelContext: PanelContext = {
|
||||
eventsScope: 'explore',
|
||||
@@ -174,7 +174,14 @@ export function ExploreGraph({
|
||||
dataLinkPostProcessor,
|
||||
};
|
||||
|
||||
function toggleLegend(name: string, mode: SeriesVisibilityChangeMode) {
|
||||
function toggleLegend(name: string | undefined, mode: SeriesVisibilityChangeMode) {
|
||||
if (!name) {
|
||||
setFieldConfig({
|
||||
...fieldConfig,
|
||||
overrides: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
setFieldConfig(seriesVisibilityConfigFactory(name, mode, fieldConfig, data));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ import {
|
||||
TimeRange,
|
||||
LogsDedupStrategy,
|
||||
LogsSortOrder,
|
||||
LogLevel,
|
||||
DataTopic,
|
||||
CoreApp,
|
||||
LogsDedupDescription,
|
||||
rangeUtil,
|
||||
@@ -31,10 +29,11 @@ import {
|
||||
DataHoverEvent,
|
||||
serializeStateToUrlParam,
|
||||
urlUtil,
|
||||
LogLevel,
|
||||
} from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { DataQuery, DataTopic } from '@grafana/schema';
|
||||
import {
|
||||
Button,
|
||||
InlineField,
|
||||
@@ -47,7 +46,6 @@ import {
|
||||
Themeable2,
|
||||
withTheme2,
|
||||
} from '@grafana/ui';
|
||||
import { mapMouseEventToMode } from '@grafana/ui/internal';
|
||||
import store from 'app/core/store';
|
||||
import { createAndCopyShortLink, getLogsPermalinkRange } from 'app/core/utils/shortLinks';
|
||||
import { ControlledLogRows } from 'app/features/logs/components/ControlledLogRows';
|
||||
@@ -56,7 +54,7 @@ import { LogRows } from 'app/features/logs/components/LogRows';
|
||||
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
||||
import { LogList, LogListControlOptions } from 'app/features/logs/components/panel/LogList';
|
||||
import { isDedupStrategy, isLogsSortOrder } from 'app/features/logs/components/panel/LogListContext';
|
||||
import { LogLevelColor, dedupLogRows, filterLogLevels } from 'app/features/logs/logsModel';
|
||||
import { LogLevelColor, dedupLogRows } from 'app/features/logs/logsModel';
|
||||
import { getLogLevelFromKey, getLogLevelInfo } from 'app/features/logs/utils';
|
||||
import { LokiQueryDirection } from 'app/plugins/datasource/loki/dataquery.gen';
|
||||
import { isLokiQuery } from 'app/plugins/datasource/loki/queryUtils';
|
||||
@@ -65,7 +63,6 @@ import { getState } from 'app/store/store';
|
||||
import { ExploreItemState, useDispatch } from 'app/types';
|
||||
|
||||
import {
|
||||
contentOutlineTrackLevelFilter,
|
||||
contentOutlineTrackPinAdded,
|
||||
contentOutlineTrackPinClicked,
|
||||
contentOutlineTrackPinLimitReached,
|
||||
@@ -202,7 +199,6 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
store.getBool(SETTINGS_KEYS.prettifyLogMessage, false)
|
||||
);
|
||||
const [dedupStrategy, setDedupStrategy] = useState<LogsDedupStrategy>(LogsDedupStrategy.none);
|
||||
const [hiddenLogLevels, setHiddenLogLevels] = useState<LogLevel[]>([]);
|
||||
const [logsSortOrder, setLogsSortOrder] = useState<LogsSortOrder>(
|
||||
store.get(SETTINGS_KEYS.logsSortOrder) || LogsSortOrder.Descending
|
||||
);
|
||||
@@ -219,12 +215,12 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const previousLoading = usePrevious(loading);
|
||||
|
||||
const logsVolumeEventBus = eventBus.newScopedBus('logsvolume', { onlyLocal: false });
|
||||
const { register, unregister, outlineItems, updateItem, unregisterAllChildren } = useContentOutlineContext() ?? {};
|
||||
const { register, unregister, outlineItems, updateItem } = useContentOutlineContext() ?? {};
|
||||
const flipOrderTimer = useRef<number | undefined>(undefined);
|
||||
const cancelFlippingTimer = useRef<number | undefined>(undefined);
|
||||
const toggleLegendRef = useRef<(name: string, mode: SeriesVisibilityChangeMode) => void>(() => {});
|
||||
const toggleLegendRef = useRef<(name: string | undefined, mode: SeriesVisibilityChangeMode) => void>(() => {});
|
||||
const topLogsRef = useRef<HTMLDivElement>(null);
|
||||
const logLevelsRef = useRef<LogLevel[] | null>(null);
|
||||
const [filterLevels, setFilterLevels] = useState<LogLevel[] | undefined>(undefined);
|
||||
|
||||
const tableHeight = getLogsTableHeight();
|
||||
const setWrapperLineWrapStyles = wrapLogMessage || visualisationType === 'table';
|
||||
@@ -247,62 +243,6 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
return logsParent?.children?.filter((child) => child.title === PINNED_LOGS_TITLE).length ?? 0;
|
||||
}, [outlineItems]);
|
||||
|
||||
const registerLogLevelsWithContentOutline = useCallback(() => {
|
||||
const levelsArr = Object.keys(LogLevelColor);
|
||||
const logVolumeDataFrames = new Set(logsVolumeData?.data);
|
||||
// TODO remove this once filtering multiple log volumes is supported
|
||||
const logVolData = logsVolumeData?.data.filter(
|
||||
(frame: DataFrame) => frame.meta?.dataTopic !== DataTopic.Annotations
|
||||
);
|
||||
const grouped = groupBy(logVolData, 'meta.custom.datasourceName');
|
||||
const numberOfLogVolumes = Object.keys(grouped).length;
|
||||
|
||||
// clean up all current log levels
|
||||
if (unregisterAllChildren) {
|
||||
unregisterAllChildren((items) => {
|
||||
const logsParent = items?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
|
||||
return logsParent?.id;
|
||||
}, 'filter');
|
||||
}
|
||||
|
||||
// check if we have dataFrames that return the same level
|
||||
const logLevelsArray: LogLevel[] = [];
|
||||
logVolumeDataFrames.forEach((dataFrame) => {
|
||||
const { level } = getLogLevelInfo(dataFrame, logsVolumeData?.data ?? []);
|
||||
logLevelsArray.push(getLogLevelFromKey(level));
|
||||
});
|
||||
|
||||
const sortedLLArray = logLevelsArray.sort((a: string, b: string) =>
|
||||
levelsArr.indexOf(a) > levelsArr.indexOf(b) ? 1 : -1
|
||||
);
|
||||
|
||||
const logLevels = new Set(sortedLLArray);
|
||||
logLevelsRef.current = Array.from(logLevels);
|
||||
|
||||
if (logLevels.size > 1 && logsVolumeEnabled && numberOfLogVolumes === 1) {
|
||||
logLevels.forEach((level) => {
|
||||
const allLevelsSelected = hiddenLogLevels.length === 0;
|
||||
const currentLevelSelected = !hiddenLogLevels.find((hiddenLevel) => hiddenLevel === level);
|
||||
if (register) {
|
||||
register({
|
||||
title: level,
|
||||
icon: 'gf-logs',
|
||||
panelId: PINNED_LOGS_PANELID,
|
||||
level: 'child',
|
||||
type: 'filter',
|
||||
highlight: currentLevelSelected && !allLevelsSelected,
|
||||
onClick: (e: React.MouseEvent) => {
|
||||
toggleLegendRef.current?.(level, mapMouseEventToMode(e));
|
||||
contentOutlineTrackLevelFilter(level);
|
||||
},
|
||||
ref: null,
|
||||
color: LogLevelColor[level],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [logsVolumeData?.data, unregisterAllChildren, logsVolumeEnabled, hiddenLogLevels, register, toggleLegendRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getPinnedLogsCount() === PINNED_LOGS_LIMIT) {
|
||||
setPinLineButtonTooltipTitle(
|
||||
@@ -348,10 +288,6 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
setDisplayedFields(displayedFields);
|
||||
}, [panelState?.logs?.displayedFields]);
|
||||
|
||||
useEffect(() => {
|
||||
registerLogLevelsWithContentOutline();
|
||||
}, [logsVolumeData?.data, hiddenLogLevels, registerLogLevelsWithContentOutline]);
|
||||
|
||||
useUnmount(() => {
|
||||
if (flipOrderTimer) {
|
||||
window.clearTimeout(flipOrderTimer.current);
|
||||
@@ -564,11 +500,6 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onToggleLogLevel = useCallback((hiddenRawLevels: string[]) => {
|
||||
const hiddenLogLevels = hiddenRawLevels.map(getLogLevelFromKey);
|
||||
setHiddenLogLevels(hiddenLogLevels);
|
||||
}, []);
|
||||
|
||||
const onToggleLogsVolumeCollapse = useCallback(
|
||||
(collapsed: boolean) => {
|
||||
props.onSetLogsVolumeEnabled(!collapsed);
|
||||
@@ -758,60 +689,81 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
);
|
||||
|
||||
const hasUnescapedContent = useMemo(() => checkUnescapedContent(logRows), [logRows]);
|
||||
const filteredLogs = useMemo(() => filterRows(logRows, hiddenLogLevels), [hiddenLogLevels, logRows]);
|
||||
const { dedupedRows, dedupCount } = useMemo(
|
||||
() => dedupRows(filteredLogs, dedupStrategy),
|
||||
[dedupStrategy, filteredLogs]
|
||||
);
|
||||
const { dedupedRows, dedupCount } = useMemo(() => dedupRows(logRows, dedupStrategy), [dedupStrategy, logRows]);
|
||||
const navigationRange = useMemo(() => createNavigationRange(logRows), [logRows]);
|
||||
const infiniteScrollAvailable = useMemo(
|
||||
() => !logsQueries?.some((query) => 'direction' in query && query.direction === LokiQueryDirection.Scan),
|
||||
[logsQueries]
|
||||
);
|
||||
|
||||
const visibilityChangedRef = useRef(true);
|
||||
const onLogOptionsChange = useCallback(
|
||||
(option: keyof LogListControlOptions, value: string | string[] | boolean) => {
|
||||
if (option === 'sortOrder' && isLogsSortOrder(value)) {
|
||||
sortOrderChanged(value);
|
||||
} else if (option === 'filterLevels' && Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
setHiddenLogLevels([]);
|
||||
return;
|
||||
}
|
||||
const allLevels = logLevelsRef.current ?? Object.keys(LogLevelColor).map(getLogLevelFromKey);
|
||||
if (!allLevels.length) {
|
||||
// Logs panel is disabled
|
||||
return;
|
||||
}
|
||||
if (hiddenLogLevels.length === 0) {
|
||||
toggleLegendRef.current?.(value[0], SeriesVisibilityChangeMode.ToggleSelection);
|
||||
setHiddenLogLevels(allLevels.filter((level) => level !== value[0]));
|
||||
return;
|
||||
}
|
||||
const appendsLevel = value.find((level) => hiddenLogLevels.includes(getLogLevelFromKey(level)));
|
||||
const removesLevel = allLevels.find((level) => !value.includes(level) && !hiddenLogLevels.includes(level));
|
||||
if (appendsLevel) {
|
||||
toggleLegendRef.current?.(appendsLevel, SeriesVisibilityChangeMode.AppendToSelection);
|
||||
setHiddenLogLevels(hiddenLogLevels.filter((hiddenLevel) => hiddenLevel === appendsLevel));
|
||||
return;
|
||||
} else if (removesLevel) {
|
||||
toggleLegendRef.current?.(removesLevel, SeriesVisibilityChangeMode.AppendToSelection);
|
||||
setHiddenLogLevels([...hiddenLogLevels, removesLevel]);
|
||||
}
|
||||
} else if (option === 'dedupStrategy' && isDedupStrategy(value)) {
|
||||
setDedupStrategy(value);
|
||||
} else if (option === 'filterLevels' && Array.isArray(value) && logsVolumeEnabled) {
|
||||
const logVolData =
|
||||
logsVolumeData?.data.filter((frame: DataFrame) => frame.meta?.dataTopic !== DataTopic.Annotations) ?? [];
|
||||
const grouped = groupBy(logVolData, 'meta.custom.datasourceName');
|
||||
const numberOfLogVolumes = Object.keys(grouped).length;
|
||||
|
||||
// Not supported
|
||||
if (numberOfLogVolumes > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logsVolumeLevels = [
|
||||
...new Set(
|
||||
logVolData.map((dataFrame) => {
|
||||
const { level } = getLogLevelInfo(dataFrame, logVolData);
|
||||
return getLogLevelFromKey(level);
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
setFilterLevels((currentFilterLevels: LogLevel[] | undefined) => {
|
||||
const newLevels = value.map((level) => getLogLevelFromKey(level));
|
||||
const newLogsVolumeLevels = newLevels.filter((level) => logsVolumeLevels.includes(level));
|
||||
const prevLogsVolumeLevels = currentFilterLevels?.filter((level) => logsVolumeLevels.includes(level)) ?? [];
|
||||
|
||||
if (!newLogsVolumeLevels.length) {
|
||||
toggleLegendRef.current?.(undefined, SeriesVisibilityChangeMode.ToggleSelection);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const addedLevels = newLogsVolumeLevels.filter((newLevel) => !prevLogsVolumeLevels.includes(newLevel));
|
||||
const removedLevels = prevLogsVolumeLevels.filter((prevLevel) => !newLogsVolumeLevels.includes(prevLevel));
|
||||
|
||||
addedLevels.forEach((level) => {
|
||||
visibilityChangedRef.current = true;
|
||||
toggleLegendRef.current?.(
|
||||
level,
|
||||
!prevLogsVolumeLevels.length
|
||||
? SeriesVisibilityChangeMode.ToggleSelection
|
||||
: SeriesVisibilityChangeMode.AppendToSelection
|
||||
);
|
||||
});
|
||||
removedLevels.forEach((level) => {
|
||||
visibilityChangedRef.current = true;
|
||||
toggleLegendRef.current?.(level, SeriesVisibilityChangeMode.AppendToSelection);
|
||||
});
|
||||
|
||||
return newLevels;
|
||||
});
|
||||
}
|
||||
},
|
||||
[hiddenLogLevels, sortOrderChanged]
|
||||
[logsVolumeData?.data, logsVolumeEnabled, sortOrderChanged]
|
||||
);
|
||||
|
||||
const filterLevels: LogLevel[] | undefined = useMemo(
|
||||
() =>
|
||||
!logLevelsRef.current || logLevelsRef.current.length === 0
|
||||
? undefined
|
||||
: logLevelsRef.current.filter((level) => hiddenLogLevels.length > 0 && !hiddenLogLevels.includes(level)),
|
||||
[hiddenLogLevels]
|
||||
);
|
||||
const onDisplayedSeriesChanged = useCallback((levels: string[]) => {
|
||||
if (visibilityChangedRef.current) {
|
||||
visibilityChangedRef.current = false;
|
||||
return;
|
||||
}
|
||||
setFilterLevels(levels.map((level) => getLogLevelFromKey(level)));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -843,7 +795,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
timeZone={timeZone}
|
||||
splitOpen={splitOpen}
|
||||
onLoadLogsVolume={loadLogsVolumeData}
|
||||
onHiddenSeriesChanged={onToggleLogLevel}
|
||||
onDisplayedSeriesChanged={onDisplayedSeriesChanged}
|
||||
eventBus={logsVolumeEventBus}
|
||||
onClose={() => onToggleLogsVolumeCollapse(true)}
|
||||
/>
|
||||
@@ -1315,10 +1267,6 @@ const dedupRows = (logRows: LogRowModel[], dedupStrategy: LogsDedupStrategy) =>
|
||||
return { dedupedRows, dedupCount };
|
||||
};
|
||||
|
||||
const filterRows = (logRows: LogRowModel[], hiddenLogLevels: string[]) => {
|
||||
return filterLogLevels(logRows, new Set(hiddenLogLevels));
|
||||
};
|
||||
|
||||
const createNavigationRange = (logRows: LogRowModel[]): { from: number; to: number } | undefined => {
|
||||
if (!logRows || logRows.length === 0) {
|
||||
return undefined;
|
||||
|
||||
@@ -31,7 +31,9 @@ type Props = {
|
||||
onHiddenSeriesChanged: (hiddenSeries: string[]) => void;
|
||||
eventBus: EventBus;
|
||||
annotations: DataFrame[];
|
||||
toggleLegendRef?: React.MutableRefObject<(name: string, mode: SeriesVisibilityChangeMode) => void> | undefined;
|
||||
toggleLegendRef?:
|
||||
| React.MutableRefObject<(name: string | undefined, mode: SeriesVisibilityChangeMode) => void>
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export function LogsVolumePanel(props: Props) {
|
||||
|
||||
@@ -22,7 +22,7 @@ function renderPanel(logsVolumeData?: DataQueryResponse, onLoadLogsVolume = () =
|
||||
onUpdateTimeRange={() => {}}
|
||||
logsVolumeData={logsVolumeData}
|
||||
onLoadLogsVolume={onLoadLogsVolume}
|
||||
onHiddenSeriesChanged={() => null}
|
||||
onDisplayedSeriesChanged={() => null}
|
||||
eventBus={new EventBusSrv()}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { flatten, groupBy, mapValues, sortBy } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
@@ -10,8 +10,10 @@ import {
|
||||
DataTopic,
|
||||
dateTime,
|
||||
EventBus,
|
||||
getFrameDisplayName,
|
||||
GrafanaTheme2,
|
||||
LoadingState,
|
||||
shallowCompare,
|
||||
SplitOpen,
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
@@ -34,10 +36,10 @@ type Props = {
|
||||
width: number;
|
||||
onUpdateTimeRange: (timeRange: AbsoluteTimeRange) => void;
|
||||
onLoadLogsVolume: () => void;
|
||||
onHiddenSeriesChanged: (hiddenSeries: string[]) => void;
|
||||
onDisplayedSeriesChanged: (series: string[]) => void;
|
||||
eventBus: EventBus;
|
||||
onClose?(): void;
|
||||
toggleLegendRef?: React.MutableRefObject<(name: string, mode: SeriesVisibilityChangeMode) => void>;
|
||||
toggleLegendRef?: React.MutableRefObject<(name: string | undefined, mode: SeriesVisibilityChangeMode) => void>;
|
||||
};
|
||||
|
||||
export const LogsVolumePanelList = ({
|
||||
@@ -46,7 +48,7 @@ export const LogsVolumePanelList = ({
|
||||
onUpdateTimeRange,
|
||||
width,
|
||||
onLoadLogsVolume,
|
||||
onHiddenSeriesChanged,
|
||||
onDisplayedSeriesChanged,
|
||||
eventBus,
|
||||
splitOpen,
|
||||
timeZone,
|
||||
@@ -96,6 +98,25 @@ export const LogsVolumePanelList = ({
|
||||
const to = dateTime(Math.min(absoluteRange.to, allLogsVolumeMaximumRange.to));
|
||||
const visibleRange: TimeRange = { from, to, raw: { from, to } };
|
||||
|
||||
const handleHiddenSeriesChanged = useCallback(
|
||||
(hiddenSeries: string[]) => {
|
||||
// Not supported
|
||||
if (numberOfLogVolumes > 1) {
|
||||
return;
|
||||
}
|
||||
const allLevels = [
|
||||
...new Set(
|
||||
Object.values(logVolumes)
|
||||
.map((series) => series.map((dataFrame) => getFrameDisplayName(dataFrame)))
|
||||
.flat()
|
||||
),
|
||||
];
|
||||
const displayedLevels = allLevels.filter((level) => !hiddenSeries.includes(level));
|
||||
onDisplayedSeriesChanged(shallowCompare(allLevels, displayedLevels) ? [] : displayedLevels);
|
||||
},
|
||||
[logVolumes, numberOfLogVolumes, onDisplayedSeriesChanged]
|
||||
);
|
||||
|
||||
if (logsVolumeData?.state === LoadingState.Loading) {
|
||||
return (
|
||||
<span>
|
||||
@@ -185,7 +206,7 @@ export const LogsVolumePanelList = ({
|
||||
splitOpen={splitOpen}
|
||||
onLoadLogsVolume={onLoadLogsVolume}
|
||||
// TODO: Support filtering level from multiple log levels
|
||||
onHiddenSeriesChanged={numberOfLogVolumes > 1 ? () => {} : onHiddenSeriesChanged}
|
||||
onHiddenSeriesChanged={numberOfLogVolumes > 1 ? () => {} : handleHiddenSeriesChanged}
|
||||
eventBus={eventBus}
|
||||
annotations={annotations}
|
||||
/>
|
||||
|
||||
@@ -261,10 +261,13 @@ export const LogListContextProvider = ({
|
||||
if (filterLevels === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!shallowCompare(logListState.filterLevels, filterLevels)) {
|
||||
setLogListState({ ...logListState, filterLevels });
|
||||
}
|
||||
}, [filterLevels, logListState]);
|
||||
setLogListState((logListState) => {
|
||||
if (!shallowCompare(logListState.filterLevels, filterLevels)) {
|
||||
return { ...logListState, filterLevels };
|
||||
}
|
||||
return logListState;
|
||||
});
|
||||
}, [filterLevels]);
|
||||
|
||||
useEffect(() => {
|
||||
setLogListState((logListState) => ({ ...logListState, fontSize }));
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
COMMON_LABELS,
|
||||
dataFrameToLogsModel,
|
||||
dedupLogRows,
|
||||
filterLogLevels,
|
||||
getSeriesProperties,
|
||||
LIMIT_LABEL,
|
||||
TOTAL_LABEL,
|
||||
@@ -169,63 +168,6 @@ describe('dedupLogRows()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterLogLevels()', () => {
|
||||
test('should correctly filter out log levels', () => {
|
||||
const rows = [
|
||||
{
|
||||
entry: 'DEBUG 1',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'ERROR 1',
|
||||
logLevel: LogLevel.error,
|
||||
},
|
||||
{
|
||||
entry: 'TRACE 1',
|
||||
logLevel: LogLevel.trace,
|
||||
},
|
||||
] as LogRowModel[];
|
||||
const filteredLogs = filterLogLevels(rows, new Set([LogLevel.debug]));
|
||||
expect(filteredLogs.length).toBe(2);
|
||||
expect(filteredLogs).toEqual([
|
||||
{ entry: 'ERROR 1', logLevel: 'error' },
|
||||
{ entry: 'TRACE 1', logLevel: 'trace' },
|
||||
]);
|
||||
});
|
||||
test('should correctly filter out log levels and then deduplicate', () => {
|
||||
const rows = [
|
||||
{
|
||||
entry: 'DEBUG 1',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'DEBUG 2',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'DEBUG 2',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'ERROR 1',
|
||||
logLevel: LogLevel.error,
|
||||
},
|
||||
{
|
||||
entry: 'TRACE 1',
|
||||
logLevel: LogLevel.trace,
|
||||
},
|
||||
] as LogRowModel[];
|
||||
const filteredLogs = filterLogLevels(rows, new Set([LogLevel.error]));
|
||||
const deduplicatedLogs = dedupLogRows(filteredLogs, LogsDedupStrategy.exact);
|
||||
expect(deduplicatedLogs.length).toBe(3);
|
||||
expect(deduplicatedLogs).toEqual([
|
||||
{ duplicates: 0, entry: 'DEBUG 1', logLevel: 'debug' },
|
||||
{ duplicates: 1, entry: 'DEBUG 2', logLevel: 'debug' },
|
||||
{ duplicates: 0, entry: 'TRACE 1', logLevel: 'trace' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
const emptyLogsModel = {
|
||||
hasUniqueLabels: false,
|
||||
rows: [],
|
||||
|
||||
@@ -104,16 +104,6 @@ export function dedupLogRows(rows: LogRowModel[], strategy?: LogsDedupStrategy):
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function filterLogLevels(logRows: LogRowModel[], hiddenLogLevels: Set<string>): LogRowModel[] {
|
||||
if (hiddenLogLevels.size === 0) {
|
||||
return logRows;
|
||||
}
|
||||
|
||||
return logRows.filter((row: LogRowModel) => {
|
||||
return !hiddenLogLevels.has(row.logLevel);
|
||||
});
|
||||
}
|
||||
|
||||
interface Series {
|
||||
lastTs: number | null;
|
||||
datapoints: Array<[number, number]>;
|
||||
|
||||
Reference in New Issue
Block a user