Logs: update tests, check for detected_level

This commit is contained in:
L2D2Grafana
2026-01-07 07:43:01 -08:00
parent 4de55500d8
commit 099c75b632
7 changed files with 92 additions and 50 deletions
@@ -48,7 +48,7 @@ describe('LogsMetaRow', () => {
});
it('renders the show original line button', () => {
setup({ displayedFields: ['test'] });
setup({ displayedFields: ['test'], defaultDisplayedFields: ['Time', 'detected_level', '___LOG_LINE_BODY___'] });
expect(
screen.getByRole('button', {
name: 'Show original line',
@@ -66,13 +66,20 @@ describe('LogsMetaRow', () => {
});
it('renders the displayed fields', async () => {
setup({ displayedFields: ['testField1234'] });
setup({
displayedFields: ['testField1234'],
defaultDisplayedFields: ['Time', 'detected_level', '___LOG_LINE_BODY___'],
});
expect(await screen.findByText('testField1234')).toBeInTheDocument();
});
it('renders a button to clear displayedfields', () => {
const clearSpy = jest.fn();
setup({ displayedFields: ['testField1234'], clearDisplayedFields: clearSpy });
setup({
displayedFields: ['testField1234'],
defaultDisplayedFields: ['Time', 'detected_level', '___LOG_LINE_BODY___'],
clearDisplayedFields: clearSpy,
});
fireEvent(
screen.getByRole('button', {
name: 'Show original line',
@@ -84,7 +84,7 @@ describe('LogsTableWrap', () => {
await waitFor(() => {
expect(updatePanelState).toBeCalledWith({
visualisationType: 'table',
columns: { 0: 'app', 1: 'Line', 2: 'Time' },
displayedFields: ['app', '___LOG_LINE_BODY___', 'Time'],
labelFieldName: 'labels',
});
});
@@ -97,7 +97,7 @@ describe('LogsTableWrap', () => {
await waitFor(() => {
expect(updatePanelState).toBeCalledWith({
visualisationType: 'table',
columns: { 0: 'Line', 1: 'Time' },
displayedFields: ['___LOG_LINE_BODY___', 'Time'],
labelFieldName: 'labels',
});
});
@@ -35,6 +35,7 @@ export const LOG_LINE_BODY_FIELD_NAME = '___LOG_LINE_BODY___';
export const TABLE_TIME_FIELD_NAME = 'Time';
export const TABLE_LINE_FIELD_NAME = 'Line';
export const TABLE_DETECTED_LEVEL_FIELD_NAME = 'detected_level';
export const TABLE_LEVEL_FIELD_NAME = 'level';
export const LogDetailsBody = (props: Props) => {
const showField = () => {
@@ -42,10 +42,7 @@ function getDisplayedFieldsForLanguages(logs: LogListModel[] | LogRowModel[], la
});
return displayedFields.filter(
(field) =>
field === LOG_LINE_BODY_FIELD_NAME ||
field === OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME ||
logs.some((log) => log.labels[field] !== undefined)
(field) => field === OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME || logs.some((log) => log.labels[field] !== undefined)
);
}
@@ -15,7 +15,7 @@ import {
import { config, reportInteraction } from '@grafana/runtime';
import { disablePopoverMenu, enablePopoverMenu, isPopoverMenuDisabled } from '../../utils';
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
import { LOG_LINE_BODY_FIELD_NAME, TABLE_TIME_FIELD_NAME, TABLE_DETECTED_LEVEL_FIELD_NAME } from '../LogDetailsBody';
import { createLogLine, createLogRow } from '../mocks/logRow';
import { OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME, OTEL_PROBE_FIELD } from '../otel/formats';
@@ -129,8 +129,18 @@ describe('LogList', () => {
<LogList {...defaultProps} onLogOptionsChange={onLogOptionsChange} setDisplayedFields={setDisplayedFields} />
);
expect(screen.getByText('log message 1')).toBeInTheDocument();
expect(onLogOptionsChange).not.toHaveBeenCalled();
expect(setDisplayedFields).not.toHaveBeenCalled();
// Even when OTel is disabled, we still report table defaults
expect(onLogOptionsChange).toHaveBeenCalledWith('defaultDisplayedFields', [
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
LOG_LINE_BODY_FIELD_NAME,
]);
// setDisplayedFields is called with the default fields
expect(setDisplayedFields).toHaveBeenCalledWith([
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
LOG_LINE_BODY_FIELD_NAME,
]);
config.featureToggles.otelLogsFormatting = originalState;
});
@@ -144,10 +154,19 @@ describe('LogList', () => {
<LogList {...defaultProps} onLogOptionsChange={onLogOptionsChange} setDisplayedFields={setDisplayedFields} />
);
expect(screen.getByText('log message 1')).toBeInTheDocument();
expect(onLogOptionsChange).toHaveBeenCalledWith('defaultDisplayedFields', []);
// For non-OTel logs, we report table defaults only (no OTel attributes field)
expect(onLogOptionsChange).toHaveBeenCalledWith('defaultDisplayedFields', [
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
LOG_LINE_BODY_FIELD_NAME,
]);
// No fields to display, no call
expect(setDisplayedFields).not.toHaveBeenCalled();
// setDisplayedFields is called with the default fields
expect(setDisplayedFields).toHaveBeenCalledWith([
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
LOG_LINE_BODY_FIELD_NAME,
]);
config.featureToggles.otelLogsFormatting = originalState;
});
@@ -168,11 +187,19 @@ describe('LogList', () => {
/>
);
expect(screen.getByText('log message 1')).toBeInTheDocument();
// For OTel logs, we report table defaults + OTel fields
expect(onLogOptionsChange).toHaveBeenCalledWith('defaultDisplayedFields', [
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
LOG_LINE_BODY_FIELD_NAME,
OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME,
]);
expect(setDisplayedFields).toHaveBeenCalledWith([
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
LOG_LINE_BODY_FIELD_NAME,
OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME,
]);
expect(setDisplayedFields).toHaveBeenCalledWith([LOG_LINE_BODY_FIELD_NAME, OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME]);
config.featureToggles.otelLogsFormatting = originalState;
});
@@ -27,9 +27,14 @@ import { config, getDataSourceSrv } from '@grafana/runtime';
import { PopoverContent } from '@grafana/ui';
import { checkLogsError, checkLogsSampled, downloadLogs as download, DownloadFormat } from '../../utils';
import { LOG_LINE_BODY_FIELD_NAME, TABLE_TIME_FIELD_NAME, TABLE_DETECTED_LEVEL_FIELD_NAME } from '../LogDetailsBody';
import {
LOG_LINE_BODY_FIELD_NAME,
TABLE_TIME_FIELD_NAME,
TABLE_DETECTED_LEVEL_FIELD_NAME,
TABLE_LEVEL_FIELD_NAME,
} from '../LogDetailsBody';
import { getFieldSelectorState } from '../fieldSelector/FieldSelector';
import { OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME, getDisplayedFieldsForLogs } from '../otel/formats';
import { getDisplayedFieldsForLogs } from '../otel/formats';
import { getDefaultDetailsMode, getDetailsWidth } from './LogDetailsContext';
import { LogLineTimestampResolution } from './LogLine';
@@ -121,11 +126,23 @@ export const useLogIsPermalinked = (log: LogListModel) => {
/**
* Get default table fields.
* Always returns the table field constants: Time and detected_level (excluding Line).
* Always returns Time, and detected_level if it exists in the logs (excluding Line).
*/
function getTableDefaultFields(logs: LogRowModel[]): string[] {
// Always return the table field constants (Time and detected_level, excluding Line)
return [TABLE_TIME_FIELD_NAME, TABLE_DETECTED_LEVEL_FIELD_NAME];
const fields: string[] = [TABLE_TIME_FIELD_NAME];
// Check if detected_level exists in any log's labels, fall back to level if not found
const hasDetectedLevel = logs.some((log) => log.labels?.[TABLE_DETECTED_LEVEL_FIELD_NAME] !== undefined);
const hasLevel = !hasDetectedLevel && logs.some((log) => log.labels?.[TABLE_LEVEL_FIELD_NAME] !== undefined);
if (hasDetectedLevel) {
fields.push(TABLE_DETECTED_LEVEL_FIELD_NAME);
} else if (hasLevel) {
// Fall back to level if detected_level is not present
fields.push(TABLE_LEVEL_FIELD_NAME);
}
return fields;
}
export type LogListState = Pick<
@@ -273,11 +290,14 @@ export const LogListContextProvider = ({
}, []);
const otelDisplayedFields = useMemo(() => {
if (!config.featureToggles.otelLogsFormatting || !setDisplayedFields || showLogAttributes === false) {
if (!config.featureToggles.otelLogsFormatting) {
return [];
}
if (showLogAttributes === false) {
return [];
}
return getDisplayedFieldsForLogs(logs);
}, [logs, setDisplayedFields, showLogAttributes]);
}, [logs, showLogAttributes]);
// Get table default fields
const tableDefaultFields = useMemo(() => {
@@ -287,34 +307,10 @@ export const LogListContextProvider = ({
// Combine table defaults with OTel defaults in specific order:
// ['Time', 'detected_level', '___LOG_LINE_BODY___', '___OTEL_LOG_ATTRIBUTES___']
const defaultDisplayedFields = useMemo(() => {
const orderedFields: string[] = [];
// 1. Add Time from table defaults
if (tableDefaultFields.includes(TABLE_TIME_FIELD_NAME)) {
orderedFields.push(TABLE_TIME_FIELD_NAME);
}
// 2. Add detected_level from table defaults
if (tableDefaultFields.includes(TABLE_DETECTED_LEVEL_FIELD_NAME)) {
orderedFields.push(TABLE_DETECTED_LEVEL_FIELD_NAME);
}
// 3. Always add LOG_LINE_BODY
const orderedFields: string[] = tableDefaultFields;
// Always add LOG_LINE_BODY
orderedFields.push(LOG_LINE_BODY_FIELD_NAME);
// 4. Always add OTEL_LOG_ATTRIBUTES
orderedFields.push(OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME);
// 5. Add any other OTel fields that aren't already included
otelDisplayedFields.forEach((field) => {
if (
field !== LOG_LINE_BODY_FIELD_NAME &&
field !== OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME &&
!orderedFields.includes(field)
) {
orderedFields.push(field);
}
});
orderedFields.push(...otelDisplayedFields);
return orderedFields;
}, [tableDefaultFields, otelDisplayedFields]);
@@ -223,6 +223,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText(OLDEST_LOGS_LABEL_REGEX));
expect(onLogOptionsChange).toHaveBeenCalledTimes(1);
expect(onLogOptionsChange).toHaveBeenCalledWith('sortOrder', LogsSortOrder.Descending);
@@ -235,6 +237,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText(DEDUPE_LABEL_COPY));
await userEvent.click(screen.getByText('Numbers'));
expect(onLogOptionsChange).toHaveBeenCalledTimes(1);
@@ -286,6 +290,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText(SHOW_TIMESTAMP_LABEL_COPY));
expect(onLogOptionsChange).toHaveBeenCalledTimes(1);
expect(onLogOptionsChange).toHaveBeenCalledWith('showTime', true);
@@ -298,6 +304,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText(WRAP_LINES_LABEL_COPY));
expect(onLogOptionsChange).toHaveBeenCalledTimes(1);
expect(onLogOptionsChange).toHaveBeenCalledWith('wrapLogMessage', true);
@@ -318,6 +326,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText('Wrap disabled'));
await userEvent.click(screen.getByText('Enable line wrapping'));
@@ -353,6 +363,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText(TIMESTAMP_LABEL_COPY));
await userEvent.click(screen.getByText('Show millisecond timestamps'));
@@ -381,6 +393,8 @@ describe('LogListControls', () => {
<LogListControls eventBus={new EventBusSrv()} />
</LogListContextProvider>
);
// Clear any initial calls (e.g., from defaultDisplayedFields)
onLogOptionsChange.mockClear();
await userEvent.click(screen.getByLabelText(ENABLE_HIGHLIGHTING_LABEL_COPY));
expect(onLogOptionsChange).toHaveBeenCalledTimes(1);
expect(onLogOptionsChange).toHaveBeenCalledWith('syntaxHighlighting', true);