Log Line Context: Add support for default displayed fields (#117162)

* LogLineContext: Integrate default displayed fields with reset fields button

* Remove unused import

* Lint
This commit is contained in:
Matias Chomicki 2026-02-03 11:12:07 +01:00 committed by GitHub
parent a6f4c5da5d
commit 3ba197e0b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 135 additions and 2 deletions

View file

@ -7,8 +7,11 @@ import {
LogsSortOrder,
SplitOpenOptions,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { dataFrameToLogsModel } from '../../logsModel';
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
import { getDisplayedFieldsForLogs, identifyOTelLanguages, OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME } from '../otel/formats';
import { DEFAULT_TIME_WINDOW, LogLineContext, PAGE_SIZE } from './LogLineContext';
@ -19,6 +22,7 @@ jest.mock('@grafana/assistant', () => ({
openAssistant: jest.fn(),
}),
}));
jest.mock('../otel/formats');
const dfBefore = createDataFrame({
fields: [
@ -79,6 +83,9 @@ jest.mock('app/features/explore/state/main', () => ({
},
}));
jest.mocked(getDisplayedFieldsForLogs).mockReturnValue([]);
jest.mocked(identifyOTelLanguages).mockReturnValue([]);
const logs = dataFrameToLogsModel([dfNow]);
const row = logs.rows[0];
@ -606,4 +613,120 @@ describe('LogLineContext', () => {
).not.toBeInTheDocument();
expect(screen.getAllByText('foo123')).toHaveLength(3);
});
test('Should hide "Show original logs" button when there are no displayed fields', async () => {
render(
<LogLineContext
log={row}
open={true}
onClose={() => {}}
getRowContext={getRowContext}
timeZone={timeZone}
sortOrder={LogsSortOrder.Descending}
displayedFields={[]}
/>
);
await waitFor(() => {
expect(screen.getByText('Log context')).toBeInTheDocument();
});
expect(
screen.queryByRole('button', {
name: /show original logs/i,
})
).not.toBeInTheDocument();
});
test('Should show "Show original logs" button when displayed fields are provided', async () => {
const displayedFields = ['level', 'label'];
render(
<LogLineContext
log={row}
open={true}
onClose={() => {}}
getRowContext={getRowContext}
timeZone={timeZone}
sortOrder={LogsSortOrder.Descending}
displayedFields={displayedFields}
/>
);
await waitFor(() => {
expect(screen.getByText('Log context')).toBeInTheDocument();
});
// Button should be visible when displayedFields are provided and differ from defaultDisplayedFields
expect(
screen.getByRole('button', {
name: /show original logs/i,
})
).toBeInTheDocument();
});
describe('Default displayed fields', () => {
const originalFlagValue = config.featureToggles.otelLogsFormatting;
beforeEach(() => {
config.featureToggles.otelLogsFormatting = true;
jest
.mocked(getDisplayedFieldsForLogs)
.mockReturnValue([LOG_LINE_BODY_FIELD_NAME, OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME]);
});
afterAll(() => {
config.featureToggles.otelLogsFormatting = originalFlagValue;
});
test('Should show "Show original logs" button when displayed fields are different than the default fields', async () => {
const displayedFields = ['level', 'label'];
render(
<LogLineContext
log={row}
open={true}
onClose={() => {}}
getRowContext={getRowContext}
timeZone={timeZone}
sortOrder={LogsSortOrder.Descending}
displayedFields={displayedFields}
/>
);
await waitFor(() => {
expect(screen.getByText('Log context')).toBeInTheDocument();
});
expect(
screen.getByRole('button', {
name: /show original logs/i,
})
).toBeInTheDocument();
});
test('Should not show "Show original logs" button when displayed fields match the default fields', async () => {
const displayedFields = [LOG_LINE_BODY_FIELD_NAME, OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME];
render(
<LogLineContext
log={row}
open={true}
onClose={() => {}}
getRowContext={getRowContext}
timeZone={timeZone}
sortOrder={LogsSortOrder.Descending}
displayedFields={displayedFields}
/>
);
await waitFor(() => {
expect(screen.getByText('Log context')).toBeInTheDocument();
});
expect(
screen.queryByRole('button', {
name: /show original logs/i,
})
).not.toBeInTheDocument();
});
});
});

View file

@ -20,6 +20,7 @@ import {
LogRowModel,
LogsDedupStrategy,
LogsSortOrder,
shallowCompare,
store,
TimeRange,
} from '@grafana/data';
@ -37,7 +38,7 @@ import { LoadingIndicator } from '../LoadingIndicator';
import { LogLineDetailsLog } from './LogLineDetailsLog';
import { LogLineMenuCustomItem } from './LogLineMenu';
import { LogList } from './LogList';
import { LogList, LogListOptions } from './LogList';
import { LogListModel } from './processing';
import { ScrollToLogsEvent } from './virtualization';
@ -100,6 +101,7 @@ export const LogLineContext = memo(
: DEFAULT_TIME_WINDOW.toString();
const [timeWindow, setTimeWindow] = useState(parseInt(defaultTimeWindow, 10));
const [displayedFields, setDisplayedFields] = useState<string[]>(displayedFieldsProp);
const [defaultDisplayedFields, setDefaultDisplayedFields] = useState<string[]>([]);
const eventBusRef = useRef(new EventBusSrv());
@ -343,6 +345,12 @@ export const LogLineContext = memo(
}
}, [log.datasourceUid]);
const onLogOptionsChange = useCallback((option: LogListOptions, value: string | string[] | boolean) => {
if (option === 'defaultDisplayedFields' && Array.isArray(value)) {
setDefaultDisplayedFields(value);
}
}, []);
return (
<Modal
isOpen={open}
@ -386,7 +394,8 @@ export const LogLineContext = memo(
/>
</Stack>
)}
{displayedFields.length > 0 && (
{/** Show button to reset if there are displayed fields and they are different than the defaults */}
{displayedFields.length > 0 && shallowCompare(displayedFields, defaultDisplayedFields) === false && (
<Button
variant="secondary"
onClick={resetFields}
@ -444,6 +453,7 @@ export const LogLineContext = memo(
loading={aboveState === LoadingState.Loading || belowState === LoadingState.Loading}
permalinkedLogId={log.uid}
onPermalinkClick={onPermalinkClick}
onLogOptionsChange={onLogOptionsChange}
onClickHideField={hideField}
onClickShowField={showField}
setDisplayedFields={setDisplayedFields}