From 433a5fd464d2e257d5e18220fa5fe1a635ed3420 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Thu, 17 Jul 2025 10:59:03 +0200 Subject: [PATCH] Logs: refactor unescaped content (#108187) --- public/app/features/explore/Logs/Logs.tsx | 6 ------ .../logs/components/ControlledLogRows.tsx | 3 --- .../logs/components/panel/LogList.tsx | 2 -- .../logs/components/panel/LogListContext.tsx | 21 ++++++------------- .../components/panel/LogListControls.test.tsx | 12 +++++++---- .../logs/components/panel/LogListControls.tsx | 17 +++++++++++++++ .../panel/__mocks__/LogListContext.tsx | 3 +++ 7 files changed, 34 insertions(+), 30 deletions(-) diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 4507fa9e028..a6d4040a446 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -689,7 +689,6 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { [getPinnedLogsCount, onOpenContext, onPinLineCallback, outlineItems, pinnedLogs, register, unregister, updateItem] ); - const hasUnescapedContent = useMemo(() => checkUnescapedContent(logRows), [logRows]); const { dedupedRows, dedupCount } = useMemo(() => dedupRows(logRows, dedupStrategy), [dedupStrategy, logRows]); const navigationRange = useMemo(() => createNavigationRange(logRows), [logRows]); const infiniteScrollAvailable = useMemo( @@ -1035,7 +1034,6 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { logsMeta={logsMeta} logOptionsStorageKey={SETTING_KEY_ROOT} onLogOptionsChange={onLogOptionsChange} - hasUnescapedContent={hasUnescapedContent} filterLevels={filterLevels} /> @@ -1260,10 +1258,6 @@ const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean, tableHeight: n }; }; -const checkUnescapedContent = (logRows: LogRowModel[]) => { - return logRows.some((r) => r.hasUnescapedContent); -}; - const dedupRows = (logRows: LogRowModel[], dedupStrategy: LogsDedupStrategy) => { const dedupedRows = dedupLogRows(logRows, dedupStrategy); const dedupCount = dedupedRows.reduce((sum, row) => (row.duplicates ? sum + row.duplicates : sum), 0); diff --git a/public/app/features/logs/components/ControlledLogRows.tsx b/public/app/features/logs/components/ControlledLogRows.tsx index 189ce66dda9..a77f8ff4326 100644 --- a/public/app/features/logs/components/ControlledLogRows.tsx +++ b/public/app/features/logs/components/ControlledLogRows.tsx @@ -26,7 +26,6 @@ import { LogListControls } from './panel/LogListControls'; import { ScrollToLogsEvent } from './panel/virtualization'; export interface ControlledLogRowsProps extends Omit { - hasUnescapedContent?: boolean; loading: boolean; logsMeta?: LogsMetaItem[]; loadMoreLogs?: (range: AbsoluteTimeRange) => void; @@ -63,7 +62,6 @@ export const ControlledLogRows = forwardRef { - const hasUnescapedContent = useMemo(() => logs.some((log) => log.hasUnescapedContent), [logs]); return ( void; enableLogDetails: boolean; filterLevels: LogLevel[]; + forceEscape: boolean; hasLogsWithErrors?: boolean; hasSampledLogs?: boolean; - hasUnescapedContent?: boolean; + hasUnescapedContent: boolean; logLineMenuCustomItems?: LogLineMenuCustomItem[]; setDedupStrategy: (dedupStrategy: LogsDedupStrategy) => void; setDetailsMode: (mode: LogLineDetailsMode) => void; @@ -75,6 +76,7 @@ export const LogListContext = createContext({ downloadLogs: () => {}, enableLogDetails: false, filterLevels: [], + forceEscape: false, fontSize: 'default', hasUnescapedContent: false, setDedupStrategy: () => {}, @@ -124,7 +126,6 @@ export type LogListState = Pick< | 'fontSize' | 'forceEscape' | 'filterLevels' - | 'hasUnescapedContent' | 'pinnedLogs' | 'prettifyJSON' | 'showUniqueLabels' @@ -145,8 +146,6 @@ export interface Props { enableLogDetails: boolean; filterLevels?: LogLevel[]; fontSize: LogListFontSize; - forceEscape?: boolean; - hasUnescapedContent?: boolean; getRowContextQuery?: GetRowContextQueryFn; isLabelFilterActive?: (key: string, value: string, refId?: string) => Promise; logs: LogRowModel[]; @@ -189,8 +188,6 @@ export const LogListContextProvider = ({ displayedFields, filterLevels, fontSize, - forceEscape = false, - hasUnescapedContent, isLabelFilterActive, getRowContextQuery, logs, @@ -227,8 +224,7 @@ export const LogListContextProvider = ({ filterLevels: filterLevels ?? (logOptionsStorageKey ? store.getObject(`${logOptionsStorageKey}.filterLevels`, []) : []), fontSize, - forceEscape, - hasUnescapedContent, + forceEscape: logOptionsStorageKey ? store.getBool(`${logOptionsStorageKey}.forceEscape`, false) : false, pinnedLogs, prettifyJSON, showTime, @@ -295,12 +291,6 @@ export const LogListContextProvider = ({ setLogListState((logListState) => ({ ...logListState, fontSize })); }, [fontSize]); - useEffect(() => { - if (logListState.hasUnescapedContent !== hasUnescapedContent) { - setLogListState({ ...logListState, hasUnescapedContent }); - } - }, [hasUnescapedContent, logListState]); - useEffect(() => { if (!shallowCompare(logListState.pinnedLogs ?? [], pinnedLogs ?? [])) { setLogListState({ ...logListState, pinnedLogs }); @@ -495,6 +485,7 @@ export const LogListContextProvider = ({ const hasLogsWithErrors = useMemo(() => logs.some((log) => !!checkLogsError(log)), [logs]); const hasSampledLogs = useMemo(() => logs.some((log) => !!checkLogsSampled(log)), [logs]); + const hasUnescapedContent = useMemo(() => logs.some((r) => r.hasUnescapedContent), [logs]); return ( ({ + ...jest.requireActual('../../utils'), + downloadLogs: jest.fn(), +})); const fontSize: LogListFontSize = 'default'; const contextProps = { @@ -294,14 +297,15 @@ describe('LogListControls', () => { }); test('Controls new lines', async () => { + const log = createLogLine({ entry: 'the\\r\\nentry', hasUnescapedContent: true }); const { rerender } = render( - + ); await userEvent.click(screen.getByLabelText('Fix incorrectly escaped newline and tab sequences in log lines')); rerender( - + ); diff --git a/public/app/features/logs/components/panel/LogListControls.tsx b/public/app/features/logs/components/panel/LogListControls.tsx index 5c455a935d3..670a2767fea 100644 --- a/public/app/features/logs/components/panel/LogListControls.tsx +++ b/public/app/features/logs/components/panel/LogListControls.tsx @@ -417,6 +417,23 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props) size="lg" /> + {visualisationType === 'logs' && hasUnescapedContent && ( + + )} )} {visualisationType === 'logs' && ( diff --git a/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx b/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx index 949f61fc861..131afaa32cb 100644 --- a/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx +++ b/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx @@ -18,6 +18,7 @@ export const LogListContext = createContext({ enableLogDetails: false, filterLevels: [], fontSize: 'default', + forceEscape: false, hasUnescapedContent: false, setDedupStrategy: () => {}, setDetailsWidth: () => {}, @@ -85,6 +86,8 @@ export const defaultValue: LogListContextData = { enableLogDetails: false, filterLevels: [], fontSize: 'default', + forceEscape: false, + hasUnescapedContent: false, setDetailsWidth: jest.fn(), showDetails: [], toggleDetails: jest.fn(),