Log Controls: Show level filter based on levels from the logs results (#111264)
* LogListControls: add logLevels prop
* LogListControls: show levels from displayed logs
* LogListSearch: fix tooltips
* Revert "LogListSearch: fix tooltips"
This reverts commit fda27101ef.
* Update tests
* Prettier
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { CoreApp, getDefaultTimeRange, LogRowModel, LogsDedupStrategy, LogsSortOrder, store } from '@grafana/data';
|
||||
import {
|
||||
CoreApp,
|
||||
getDefaultTimeRange,
|
||||
LogLevel,
|
||||
LogRowModel,
|
||||
LogsDedupStrategy,
|
||||
LogsSortOrder,
|
||||
store,
|
||||
} from '@grafana/data';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
|
||||
import { disablePopoverMenu, enablePopoverMenu, isPopoverMenuDisabled } from '../../utils';
|
||||
@@ -195,6 +203,26 @@ describe('LogList', () => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
test('Shows controls with level filters based on the displayed logs', async () => {
|
||||
logs = [createLogRow({ uid: '1', logLevel: LogLevel.info }), createLogRow({ uid: '2', logLevel: LogLevel.debug })];
|
||||
|
||||
render(<LogList {...defaultProps} showControls logs={logs} />);
|
||||
|
||||
expect(screen.getByText('info')).toBeInTheDocument();
|
||||
expect(screen.getByText('debug')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByLabelText('Filter levels'));
|
||||
|
||||
expect(await screen.findByText('All levels')).toBeVisible();
|
||||
expect(screen.getByText('Info')).toBeVisible();
|
||||
expect(screen.getByText('Debug')).toBeVisible();
|
||||
|
||||
await userEvent.click(screen.getByText('Debug'));
|
||||
|
||||
expect(screen.queryByText('info')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('debug')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Popover menu', () => {
|
||||
function setup(overrides: Partial<Props> = {}) {
|
||||
return render(
|
||||
|
||||
@@ -31,7 +31,7 @@ import { LogListContextProvider, LogListState, useLogListContext } from './LogLi
|
||||
import { LogListControls } from './LogListControls';
|
||||
import { LOG_LIST_SEARCH_HEIGHT, LogListSearch } from './LogListSearch';
|
||||
import { LogListSearchContextProvider, useLogListSearchContext } from './LogListSearchContext';
|
||||
import { preProcessLogs, LogListModel } from './processing';
|
||||
import { preProcessLogs, LogListModel, getLevelsFromLogs } from './processing';
|
||||
import { useKeyBindings } from './useKeyBindings';
|
||||
import { usePopoverMenu } from './usePopoverMenu';
|
||||
import { LogLineVirtualization, getLogLineSize, LogFieldDimension, ScrollToLogsEvent } from './virtualization';
|
||||
@@ -417,6 +417,8 @@ const LogListComponent = ({
|
||||
[debouncedScrollToItem, filteredLogs]
|
||||
);
|
||||
|
||||
const logLevels = useMemo(() => getLevelsFromLogs(processedLogs), [processedLogs]);
|
||||
|
||||
if (!containerElement || listHeight == null) {
|
||||
// Wait for container to be rendered
|
||||
return null;
|
||||
@@ -424,7 +426,7 @@ const LogListComponent = ({
|
||||
|
||||
return (
|
||||
<div className={styles.logListContainer}>
|
||||
{showControls && <LogListControls eventBus={eventBus} />}
|
||||
{showControls && <LogListControls logLevels={logLevels} eventBus={eventBus} />}
|
||||
{detailsMode === 'sidebar' && showDetails.length > 0 && (
|
||||
<LogLineDetails
|
||||
containerElement={containerElement}
|
||||
|
||||
@@ -239,7 +239,7 @@ describe('LogListControls', () => {
|
||||
expect(onLogOptionsChange).toHaveBeenCalledWith('dedupStrategy', LogsDedupStrategy.numbers);
|
||||
});
|
||||
|
||||
test('Sets level filters', async () => {
|
||||
test('Sets all level filters if not provided', async () => {
|
||||
const onLogOptionsChange = jest.fn();
|
||||
render(
|
||||
<LogListContextProvider {...contextProps} onLogOptionsChange={onLogOptionsChange}>
|
||||
@@ -258,6 +258,25 @@ describe('LogListControls', () => {
|
||||
expect(onLogOptionsChange).toHaveBeenCalledWith('filterLevels', ['error']);
|
||||
});
|
||||
|
||||
test('Sets all level filters from provided levels', async () => {
|
||||
const onLogOptionsChange = jest.fn();
|
||||
render(
|
||||
<LogListContextProvider {...contextProps} onLogOptionsChange={onLogOptionsChange}>
|
||||
<LogListControls eventBus={new EventBusSrv()} logLevels={[LogLevel.critical, LogLevel.information]} />
|
||||
</LogListContextProvider>
|
||||
);
|
||||
await userEvent.click(screen.getByLabelText(FILTER_LEVELS_LABEL_COPY));
|
||||
expect(await screen.findByText('All levels')).toBeVisible();
|
||||
expect(screen.getByText('Info')).toBeVisible();
|
||||
expect(screen.queryByText('Debug')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Trace')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Warning')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Error')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Critical')).toBeVisible();
|
||||
await userEvent.click(screen.getByText('Critical'));
|
||||
expect(onLogOptionsChange).toHaveBeenCalledWith('filterLevels', ['critical']);
|
||||
});
|
||||
|
||||
test('Controls timestamp visibility', async () => {
|
||||
const onLogOptionsChange = jest.fn();
|
||||
render(
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ScrollToLogsEvent } from './virtualization';
|
||||
type Props = {
|
||||
eventBus: EventBus;
|
||||
visualisationType?: LogsVisualisationType;
|
||||
logLevels?: LogLevel[];
|
||||
};
|
||||
|
||||
const DEDUP_OPTIONS = [
|
||||
@@ -46,7 +47,7 @@ const FILTER_LEVELS: LogLevel[] = [
|
||||
LogLevel.unknown,
|
||||
];
|
||||
|
||||
export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props) => {
|
||||
export const LogListControls = ({ eventBus, logLevels = FILTER_LEVELS, visualisationType = 'logs' }: Props) => {
|
||||
const {
|
||||
app,
|
||||
controlsExpanded,
|
||||
@@ -56,6 +57,7 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
|
||||
fontSize,
|
||||
forceEscape,
|
||||
hasUnescapedContent,
|
||||
logOptionsStorageKey,
|
||||
prettifyJSON,
|
||||
setControlsExpanded,
|
||||
setDedupStrategy,
|
||||
@@ -73,7 +75,6 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
|
||||
sortOrder,
|
||||
syntaxHighlighting,
|
||||
wrapLogMessage,
|
||||
logOptionsStorageKey,
|
||||
} = useLogListContext();
|
||||
const { hideSearch, searchVisible, showSearch } = useLogListSearchContext();
|
||||
|
||||
@@ -209,7 +210,7 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
|
||||
label={t('logs.logs-controls.display-level-all', 'All levels')}
|
||||
onClick={() => onFilterLevelClick()}
|
||||
/>
|
||||
{FILTER_LEVELS.map((level) => (
|
||||
{logLevels.map((level) => (
|
||||
<Menu.Item
|
||||
key={level}
|
||||
className={filterLevels.includes(level) ? styles.menuItemActive : undefined}
|
||||
@@ -219,7 +220,7 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
|
||||
))}
|
||||
</Menu>
|
||||
),
|
||||
[filterLevels, onFilterLevelClick, styles.menuItemActive]
|
||||
[filterLevels, logLevels, onFilterLevelClick, styles.menuItemActive]
|
||||
);
|
||||
|
||||
const downloadMenu = useMemo(
|
||||
|
||||
@@ -327,3 +327,11 @@ function countNewLines(log: string, limit = Infinity) {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
export function getLevelsFromLogs(logs: LogListModel[]) {
|
||||
const levels = new Set<LogLevel>();
|
||||
for (const log of logs) {
|
||||
levels.add(log.logLevel);
|
||||
}
|
||||
return Array.from(levels).filter((level) => level != null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user