Logs: refactor unescaped content (#108187)
This commit is contained in:
@@ -689,7 +689,6 @@ const UnthemedLogs: React.FunctionComponent<Props> = (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: Props) => {
|
||||
logsMeta={logsMeta}
|
||||
logOptionsStorageKey={SETTING_KEY_ROOT}
|
||||
onLogOptionsChange={onLogOptionsChange}
|
||||
hasUnescapedContent={hasUnescapedContent}
|
||||
filterLevels={filterLevels}
|
||||
/>
|
||||
</div>
|
||||
@@ -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);
|
||||
|
||||
@@ -26,7 +26,6 @@ import { LogListControls } from './panel/LogListControls';
|
||||
import { ScrollToLogsEvent } from './panel/virtualization';
|
||||
|
||||
export interface ControlledLogRowsProps extends Omit<Props, 'scrollElement'> {
|
||||
hasUnescapedContent?: boolean;
|
||||
loading: boolean;
|
||||
logsMeta?: LogsMetaItem[];
|
||||
loadMoreLogs?: (range: AbsoluteTimeRange) => void;
|
||||
@@ -63,7 +62,6 @@ export const ControlledLogRows = forwardRef<HTMLDivElement | null, ControlledLog
|
||||
deduplicatedRows,
|
||||
dedupStrategy,
|
||||
filterLevels,
|
||||
hasUnescapedContent,
|
||||
showLabels,
|
||||
showTime,
|
||||
logsMeta,
|
||||
@@ -84,7 +82,6 @@ export const ControlledLogRows = forwardRef<HTMLDivElement | null, ControlledLog
|
||||
enableLogDetails={false}
|
||||
filterLevels={filterLevels}
|
||||
fontSize="default"
|
||||
hasUnescapedContent={hasUnescapedContent}
|
||||
logOptionsStorageKey={logOptionsStorageKey}
|
||||
logs={deduplicatedRows ?? []}
|
||||
logsMeta={logsMeta}
|
||||
|
||||
@@ -148,7 +148,6 @@ export const LogList = ({
|
||||
timeZone,
|
||||
wrapLogMessage,
|
||||
}: Props) => {
|
||||
const hasUnescapedContent = useMemo(() => logs.some((log) => log.hasUnescapedContent), [logs]);
|
||||
return (
|
||||
<LogListContextProvider
|
||||
app={app}
|
||||
@@ -160,7 +159,6 @@ export const LogList = ({
|
||||
filterLevels={filterLevels}
|
||||
fontSize={fontSize}
|
||||
getRowContextQuery={getRowContextQuery}
|
||||
hasUnescapedContent={hasUnescapedContent}
|
||||
isLabelFilterActive={isLabelFilterActive}
|
||||
logs={logs}
|
||||
logsMeta={logsMeta}
|
||||
|
||||
@@ -42,9 +42,10 @@ export interface LogListContextData extends Omit<Props, 'containerElement' | 'lo
|
||||
downloadLogs: (format: DownloadFormat) => 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<LogListContextData>({
|
||||
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<boolean>;
|
||||
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 (
|
||||
<LogListContext.Provider
|
||||
@@ -513,7 +504,7 @@ export const LogListContextProvider = ({
|
||||
forceEscape: logListState.forceEscape,
|
||||
hasLogsWithErrors,
|
||||
hasSampledLogs,
|
||||
hasUnescapedContent: logListState.hasUnescapedContent,
|
||||
hasUnescapedContent,
|
||||
isLabelFilterActive,
|
||||
getRowContextQuery,
|
||||
logSupportsContext,
|
||||
|
||||
@@ -5,14 +5,17 @@ import { CoreApp, EventBusSrv, LogLevel, LogsDedupStrategy, LogsSortOrder } from
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { downloadLogs } from '../../utils';
|
||||
import { createLogRow } from '../mocks/logRow';
|
||||
import { createLogLine, createLogRow } from '../mocks/logRow';
|
||||
|
||||
import { LogListFontSize } from './LogList';
|
||||
import { LogListContextProvider } from './LogListContext';
|
||||
import { LogListControls } from './LogListControls';
|
||||
import { ScrollToLogsEvent } from './virtualization';
|
||||
|
||||
jest.mock('../../utils');
|
||||
jest.mock('../../utils', () => ({
|
||||
...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(
|
||||
<LogListContextProvider {...contextProps} hasUnescapedContent>
|
||||
<LogListContextProvider {...contextProps} logs={[log]}>
|
||||
<LogListControls eventBus={new EventBusSrv()} />
|
||||
</LogListContextProvider>
|
||||
);
|
||||
await userEvent.click(screen.getByLabelText('Fix incorrectly escaped newline and tab sequences in log lines'));
|
||||
rerender(
|
||||
<LogListContextProvider {...contextProps} hasUnescapedContent>
|
||||
<LogListContextProvider {...contextProps} logs={[log]}>
|
||||
<LogListControls eventBus={new EventBusSrv()} />
|
||||
</LogListContextProvider>
|
||||
);
|
||||
|
||||
@@ -417,6 +417,23 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props)
|
||||
size="lg"
|
||||
/>
|
||||
</Dropdown>
|
||||
{visualisationType === 'logs' && hasUnescapedContent && (
|
||||
<IconButton
|
||||
name="enter"
|
||||
aria-pressed={forceEscape}
|
||||
className={forceEscape ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onForceEscapeClick}
|
||||
tooltip={
|
||||
forceEscape
|
||||
? t('logs.logs-controls.remove-escaping', 'Remove escaping')
|
||||
: t(
|
||||
'logs.logs-controls.escape-newlines',
|
||||
'Fix incorrectly escaped newline and tab sequences in log lines'
|
||||
)
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{visualisationType === 'logs' && (
|
||||
|
||||
@@ -18,6 +18,7 @@ export const LogListContext = createContext<LogListContextData>({
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user