diff --git a/public/app/features/logs/components/panel/LogList.test.tsx b/public/app/features/logs/components/panel/LogList.test.tsx
index 4032aa2b9e4..1416b7cf7d0 100644
--- a/public/app/features/logs/components/panel/LogList.test.tsx
+++ b/public/app/features/logs/components/panel/LogList.test.tsx
@@ -359,17 +359,16 @@ describe('LogList', () => {
render();
expect(screen.getByText('2025-08-06 03:35:19.504')).toBeInTheDocument();
- expect(screen.getByLabelText('Show nanosecond timestamps')).toBeInTheDocument();
- await userEvent.click(screen.getByLabelText('Show nanosecond timestamps'));
+ await userEvent.click(screen.getByLabelText('Log timestamps'));
+ await userEvent.click(screen.getByText('Show nanosecond timestamps'));
expect(screen.getByText('2025-08-06 03:35:19.504133766')).toBeInTheDocument();
- expect(screen.getByLabelText('Hide timestamps')).toBeInTheDocument();
- await userEvent.click(screen.getByLabelText('Hide timestamps'));
+ await userEvent.click(screen.getByLabelText('Log timestamps'));
+ await userEvent.click(screen.getByText('Hide timestamps'));
expect(screen.queryByText(/2025-08-06 03:35:19/)).not.toBeInTheDocument();
- expect(screen.getByLabelText('Show millisecond timestamps')).toBeInTheDocument();
});
});
describe('Interactions', () => {
diff --git a/public/app/features/logs/components/panel/LogListControls.test.tsx b/public/app/features/logs/components/panel/LogListControls.test.tsx
index 40782778f79..d61bfbde5bb 100644
--- a/public/app/features/logs/components/panel/LogListControls.test.tsx
+++ b/public/app/features/logs/components/panel/LogListControls.test.tsx
@@ -227,22 +227,57 @@ describe('LogListControls', () => {
);
- await userEvent.click(screen.getByLabelText('Wrap lines'));
+ await userEvent.click(screen.getByLabelText('Log line wrapping'));
+ await userEvent.click(screen.getByText('Enable line wrapping'));
expect(onLogOptionsChange).toHaveBeenCalledTimes(2);
expect(onLogOptionsChange).toHaveBeenCalledWith('wrapLogMessage', true);
expect(onLogOptionsChange).toHaveBeenCalledWith('prettifyJSON', false);
- await userEvent.click(screen.getByLabelText('Wrap lines and expand JSON'));
- expect(onLogOptionsChange).toHaveBeenCalledTimes(3);
+ await userEvent.click(screen.getByLabelText('Log line wrapping'));
+ await userEvent.click(screen.getByText('Enable line wrapping and prettify JSON'));
+
+ expect(onLogOptionsChange).toHaveBeenCalledTimes(4);
expect(onLogOptionsChange).toHaveBeenCalledWith('prettifyJSON', true);
- await userEvent.click(screen.getByLabelText('Unwrap lines'));
+ await userEvent.click(screen.getByLabelText('Log line wrapping'));
+ await userEvent.click(screen.getByText('Disable line wrapping'));
expect(onLogOptionsChange).toHaveBeenCalledWith('wrapLogMessage', false);
expect(onLogOptionsChange).toHaveBeenCalledWith('prettifyJSON', false);
- expect(onLogOptionsChange).toHaveBeenCalledTimes(5);
+ expect(onLogOptionsChange).toHaveBeenCalledTimes(6);
+
+ config.featureToggles.newLogsPanel = originalFlagState;
+ });
+
+ test('Controls line wrapping and prettify JSON', async () => {
+ const originalFlagState = config.featureToggles.newLogsPanel;
+ config.featureToggles.newLogsPanel = true;
+
+ const onLogOptionsChange = jest.fn();
+ render(
+
+
+
+ );
+
+ await userEvent.click(screen.getByLabelText('Log timestamps'));
+ await userEvent.click(screen.getByText('Show millisecond timestamps'));
+
+ expect(onLogOptionsChange).toHaveBeenCalledTimes(1);
+ expect(onLogOptionsChange).toHaveBeenCalledWith('showTime', true);
+
+ await userEvent.click(screen.getByLabelText('Log timestamps'));
+ await userEvent.click(screen.getByText('Show nanosecond timestamps'));
+
+ expect(onLogOptionsChange).toHaveBeenCalledTimes(2);
+
+ await userEvent.click(screen.getByLabelText('Log timestamps'));
+ await userEvent.click(screen.getByText('Hide timestamps'));
+
+ expect(onLogOptionsChange).toHaveBeenCalledTimes(3);
+ expect(onLogOptionsChange).toHaveBeenCalledWith('showTime', false);
config.featureToggles.newLogsPanel = originalFlagState;
});
diff --git a/public/app/features/logs/components/panel/LogListControls.tsx b/public/app/features/logs/components/panel/LogListControls.tsx
index 372b135e48f..bb8ebd398f8 100644
--- a/public/app/features/logs/components/panel/LogListControls.tsx
+++ b/public/app/features/logs/components/panel/LogListControls.tsx
@@ -11,7 +11,6 @@ import { Dropdown, Icon, IconButton, Menu, Tooltip, useStyles2 } from '@grafana/
import { LogsVisualisationType } from '../../../explore/Logs/Logs';
import { DownloadFormat } from '../../utils';
-import { LogLineTimestampResolution } from './LogLine';
import { useLogListContext } from './LogListContext';
import { useLogListSearchContext } from './LogListSearchContext';
import { ScrollToLogsEvent } from './virtualization';
@@ -92,7 +91,9 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
const onFilterLevelClick = useCallback(
(level?: LogLevel) => {
- reportInteraction('logs_log_list_controls_level_clicked');
+ reportInteraction('logs_log_list_controls_level_clicked', {
+ level,
+ });
if (level === undefined) {
setFilterLevels([]);
} else if (!filterLevels.includes(level)) {
@@ -167,7 +168,12 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
className={dedupStrategy === option ? styles.menuItemActive : undefined}
description={LogsDedupDescription[option]}
label={capitalize(option)}
- onClick={() => setDedupStrategy(option)}
+ onClick={() => {
+ setDedupStrategy(option);
+ reportInteraction('logs_log_list_controls_deduplication_clicked', {
+ option,
+ });
+ }}
/>
))}
@@ -202,15 +208,30 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
),
@@ -464,40 +485,76 @@ const TimestampResolutionButton = () => {
const styles = useStyles2(getStyles);
const { setTimestampResolution, setShowTime, showTime, timestampResolution } = useLogListContext();
- const onShowTimestampsClick = useCallback(() => {
- if (!config.featureToggles.newLogsPanel) {
- reportInteraction('logs_log_list_controls_show_time_clicked', {
- show_time: !showTime,
- });
- setShowTime(!showTime);
- return;
- }
- if (!showTime || timestampResolution === 'ns') {
- setShowTime(!showTime);
- } else if (timestampResolution === 'ms') {
- setTimestampResolution('ns');
- }
- }, [setShowTime, setTimestampResolution, showTime, timestampResolution]);
+ const hide = useCallback(() => {
+ setShowTime(false);
+ reportInteraction('logs_log_list_controls_show_time_clicked', {
+ show_time: false,
+ });
+ }, [setShowTime]);
+
+ const showMs = useCallback(() => {
+ setShowTime(true);
+ setTimestampResolution('ms');
+ reportInteraction('logs_log_list_controls_show_time_clicked', {
+ show_time: false,
+ resolution: 'ms',
+ });
+ }, [setShowTime, setTimestampResolution]);
+
+ const showNs = useCallback(() => {
+ setShowTime(true);
+ setTimestampResolution('ns');
+ reportInteraction('logs_log_list_controls_show_time_clicked', {
+ show_time: false,
+ resolution: 'ns',
+ });
+ }, [setShowTime, setTimestampResolution]);
+
+ const timestampMenu = useMemo(
+ () => (
+
+ ),
+ [hide, showMs, showNs, showTime, styles.menuItemActive, timestampResolution]
+ );
return (
-
-
-
+
+
+
+
+
+
+
);
};
@@ -515,34 +572,72 @@ const WrapLogMessageButton = () => {
* Line wrapping also controls JSON formatting, because with line wrapping disabled,
* JSON formatting has no effect, so one is related with the other.
*/
- const onWrapLogMessageClick = useCallback(() => {
- if (!wrapLogMessage) {
- setWrapLogMessage(true);
- setPrettifyJSON(false);
- } else if (!prettifyJSON) {
- setPrettifyJSON(true);
- } else {
- setWrapLogMessage(false);
- setPrettifyJSON(false);
- }
+ const disable = useCallback(() => {
+ setWrapLogMessage(false);
+ setPrettifyJSON(false);
reportInteraction('logs_log_list_controls_wrap_clicked', {
- state: !wrapLogMessage,
+ state: false,
+ prettify: false,
});
- }, [prettifyJSON, setPrettifyJSON, setWrapLogMessage, wrapLogMessage]);
+ }, [setPrettifyJSON, setWrapLogMessage]);
+
+ const wrap = useCallback(() => {
+ setWrapLogMessage(true);
+ setPrettifyJSON(false);
+ reportInteraction('logs_log_list_controls_wrap_clicked', {
+ state: true,
+ prettify: false,
+ });
+ }, [setPrettifyJSON, setWrapLogMessage]);
+
+ const wrapAndPrettify = useCallback(() => {
+ setWrapLogMessage(true);
+ setPrettifyJSON(true);
+ reportInteraction('logs_log_list_controls_wrap_clicked', {
+ state: true,
+ prettify: true,
+ });
+ }, [setPrettifyJSON, setWrapLogMessage]);
+
+ const wrappingMenu = useMemo(
+ () => (
+
+ ),
+ [disable, prettifyJSON, styles.menuItemActive, wrap, wrapAndPrettify, wrapLogMessage]
+ );
return (
-
-
-
+
+
+
+
+
+
+
);
};
@@ -611,12 +706,13 @@ const getStyles = (theme: GrafanaTheme2) => {
margin: 0,
boxShadow: 'none',
border: 'none',
- display: 'inline-flex',
+ display: 'flex',
background: 'transparent',
justifyContent: 'center',
alignItems: 'center',
padding: 0,
overflow: 'visible',
+ width: '100%',
}),
customControlIcon: css({
verticalAlign: 'baseline',
@@ -633,22 +729,3 @@ const getStyles = (theme: GrafanaTheme2) => {
}),
};
};
-
-function getTimestampTooltip(showTime: boolean, timestampResolution: LogLineTimestampResolution) {
- if (!showTime) {
- return t('logs.logs-controls.show-ms-timestamps', 'Show millisecond timestamps');
- }
- if (timestampResolution === 'ms') {
- return t('logs.logs-controls.show-ns-timestamps', 'Show nanosecond timestamps');
- }
- return t('logs.logs-controls.hide-timestamps', 'Hide timestamps');
-}
-
-function getWrapLogMessageTooltip(wrapLogMessage: boolean, prettifyJSON: boolean | undefined) {
- if (!wrapLogMessage) {
- return t('logs.logs-controls.wrap-lines', 'Wrap lines');
- }
- return prettifyJSON
- ? t('logs.logs-controls.unwrap-lines', 'Unwrap lines')
- : t('logs.logs-controls.wrap-json-lines', 'Wrap lines and expand JSON');
-}
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 013c0255105..46459e68f27 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -9644,6 +9644,12 @@
"hide-search": "Close search",
"hide-timestamps": "Hide timestamps",
"hide-unique-labels": "Hide unique labels",
+ "line-wrapping": {
+ "enable": "Enable line wrapping",
+ "enable-prettify": "Enable line wrapping and prettify JSON",
+ "hide": "Disable line wrapping",
+ "label": "Log line wrapping"
+ },
"newest-first": "Sorted by newest logs first - Click to show oldest first",
"oldest-first": "Sorted by oldest logs first - Click to show newest first",
"prettify-json": "Expand JSON logs",
@@ -9652,13 +9658,16 @@
"resolution-ns": "ns",
"scroll-bottom": "Scroll to bottom",
"scroll-top": "Scroll to top",
- "show-ms-timestamps": "Show millisecond timestamps",
- "show-ns-timestamps": "Show nanosecond timestamps",
"show-search": "Search in logs result",
"show-timestamps": "Show timestamps",
"show-unique-labels": "Show unique labels",
+ "timestamp": {
+ "hide": "Hide timestamps",
+ "label": "Log timestamps",
+ "milliseconds": "Show millisecond timestamps",
+ "nanoseconds": "Show nanosecond timestamps"
+ },
"unwrap-lines": "Unwrap lines",
- "wrap-json-lines": "Wrap lines and expand JSON",
"wrap-lines": "Wrap lines"
},
"logs-navigation": {