Logs Panel: Limit displayed characters to MAX_CHARACTERS (#96997)

* LogRowMessage: limit displayed characters to MAX_CHARACTERS

* LogRowMessage: update ellipsis text

* Formatting

* Revert test change

* LogRowMessage: fix conditional

* Extract translations

* LogRowMessage: use button for ellipsis

* Revert test change

* Change fill to outline

* Revert test change
This commit is contained in:
Matias Chomicki 2024-11-26 15:45:44 +00:00 committed by GitHub
parent d418299780
commit d69888df2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 8 deletions

View File

@ -5,7 +5,7 @@ import { ComponentProps } from 'react';
import { CoreApp, createTheme, LogLevel, LogRowModel } from '@grafana/data';
import { IconButton } from '@grafana/ui';
import { LogRowMessage } from './LogRowMessage';
import { LogRowMessage, MAX_CHARACTERS } from './LogRowMessage';
import { createLogRow } from './__mocks__/logRow';
import { getLogRowStyles } from './getLogRowStyles';
@ -219,4 +219,19 @@ line3`;
expect(onAfter).toHaveBeenCalledWith(expect.anything(), row);
});
});
describe('Extremely long log lines', () => {
let entry = '';
beforeEach(() => {
entry = new Array(MAX_CHARACTERS).fill('a').join('') + 'b';
});
it('Displays an ellipsis for log lines above the character limit', async () => {
setup({
row: createLogRow({ entry, logLevel: LogLevel.error, timeEpochMs: 1546297200000 }),
});
expect(screen.getByText(/1 more/)).toBeInTheDocument();
await userEvent.click(screen.getByText(/1 more/));
expect(screen.queryByText(/1 more/)).not.toBeInTheDocument();
});
});
});

View File

@ -1,9 +1,11 @@
import { memo, ReactNode, useMemo } from 'react';
import { css } from '@emotion/css';
import { memo, ReactNode, SyntheticEvent, useMemo, useState } from 'react';
import Highlighter from 'react-highlight-words';
import { CoreApp, findHighlightChunksInText, LogRowContextOptions, LogRowModel } from '@grafana/data';
import { CoreApp, findHighlightChunksInText, GrafanaTheme2, LogRowContextOptions, LogRowModel } from '@grafana/data';
import { DataQuery } from '@grafana/schema';
import { PopoverContent } from '@grafana/ui';
import { PopoverContent, useTheme2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { LogMessageAnsi } from './LogMessageAnsi';
import { LogRowMenuCell } from './LogRowMenuCell';
@ -44,25 +46,73 @@ interface LogMessageProps {
}
const LogMessage = ({ hasAnsi, entry, highlights, styles }: LogMessageProps) => {
const excessCharacters = useMemo(() => entry.length - MAX_CHARACTERS, [entry]);
const needsHighlighter =
highlights && highlights.length > 0 && highlights[0] && highlights[0].length > 0 && entry.length < MAX_CHARACTERS;
highlights && highlights.length > 0 && highlights[0] && highlights[0].length > 0 && excessCharacters <= 0;
const searchWords = highlights ?? [];
const [showFull, setShowFull] = useState(excessCharacters < 0);
const truncatedEntry = useMemo(() => (showFull ? entry : entry.substring(0, MAX_CHARACTERS)), [entry, showFull]);
if (hasAnsi) {
const highlight = needsHighlighter ? { searchWords, highlightClassName: styles.logsRowMatchHighLight } : undefined;
return <LogMessageAnsi value={entry} highlight={highlight} />;
return <LogMessageAnsi value={truncatedEntry} highlight={highlight} />;
} else if (needsHighlighter) {
return (
<Highlighter
textToHighlight={entry}
textToHighlight={truncatedEntry}
searchWords={searchWords}
findChunks={findHighlightChunksInText}
highlightClassName={styles.logsRowMatchHighLight}
/>
);
}
return <>{entry}</>;
return (
<>
{truncatedEntry}
{!showFull && <Ellipsis showFull={showFull} toggle={setShowFull} diff={excessCharacters} />}
</>
);
};
interface EllipsisProps {
showFull: boolean;
toggle(state: boolean): void;
diff: number;
}
const Ellipsis = ({ toggle, diff }: EllipsisProps) => {
const styles = getEllipsisStyles(useTheme2());
const handleClick = (e: SyntheticEvent) => {
e.stopPropagation();
toggle(true);
};
return (
<>
<Trans i18nKey="logs.log-row-message.ellipsis"> </Trans>
<span className={styles.showMore} onClick={handleClick}>
{diff} <Trans i18nKey="logs.log-row-message.more">more</Trans>
</span>
</>
);
};
const getEllipsisStyles = (theme: GrafanaTheme2) => ({
showMore: css({
display: 'inline-flex',
fontWeight: theme.typography.fontWeightMedium,
fontSize: theme.typography.size.sm,
fontFamily: theme.typography.fontFamily,
height: theme.spacing(3),
padding: theme.spacing(0.25, 1),
color: theme.colors.secondary.text,
border: `1px solid ${theme.colors.border.strong}`,
'&:hover': {
background: theme.colors.secondary.transparent,
borderColor: theme.colors.emphasize(theme.colors.border.strong, 0.25),
color: theme.colors.secondary.text,
},
}),
});
const restructureLog = (
line: string,
prettifyLogMessage: boolean,

View File

@ -1742,6 +1742,10 @@
"logs": {
"infinite-scroll": {
"older-logs": "Older logs"
},
"log-row-message": {
"ellipsis": "… ",
"more": "more"
}
},
"migrate-to-cloud": {

View File

@ -1742,6 +1742,10 @@
"logs": {
"infinite-scroll": {
"older-logs": "Øľđęř ľőģş"
},
"log-row-message": {
"ellipsis": "… ",
"more": "mőřę"
}
},
"migrate-to-cloud": {