mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
d418299780
commit
d69888df2d
@ -5,7 +5,7 @@ import { ComponentProps } from 'react';
|
|||||||
import { CoreApp, createTheme, LogLevel, LogRowModel } from '@grafana/data';
|
import { CoreApp, createTheme, LogLevel, LogRowModel } from '@grafana/data';
|
||||||
import { IconButton } from '@grafana/ui';
|
import { IconButton } from '@grafana/ui';
|
||||||
|
|
||||||
import { LogRowMessage } from './LogRowMessage';
|
import { LogRowMessage, MAX_CHARACTERS } from './LogRowMessage';
|
||||||
import { createLogRow } from './__mocks__/logRow';
|
import { createLogRow } from './__mocks__/logRow';
|
||||||
import { getLogRowStyles } from './getLogRowStyles';
|
import { getLogRowStyles } from './getLogRowStyles';
|
||||||
|
|
||||||
@ -219,4 +219,19 @@ line3`;
|
|||||||
expect(onAfter).toHaveBeenCalledWith(expect.anything(), row);
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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 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 { 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 { LogMessageAnsi } from './LogMessageAnsi';
|
||||||
import { LogRowMenuCell } from './LogRowMenuCell';
|
import { LogRowMenuCell } from './LogRowMenuCell';
|
||||||
@ -44,25 +46,73 @@ interface LogMessageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LogMessage = ({ hasAnsi, entry, highlights, styles }: LogMessageProps) => {
|
const LogMessage = ({ hasAnsi, entry, highlights, styles }: LogMessageProps) => {
|
||||||
|
const excessCharacters = useMemo(() => entry.length - MAX_CHARACTERS, [entry]);
|
||||||
const needsHighlighter =
|
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 searchWords = highlights ?? [];
|
||||||
|
const [showFull, setShowFull] = useState(excessCharacters < 0);
|
||||||
|
const truncatedEntry = useMemo(() => (showFull ? entry : entry.substring(0, MAX_CHARACTERS)), [entry, showFull]);
|
||||||
|
|
||||||
if (hasAnsi) {
|
if (hasAnsi) {
|
||||||
const highlight = needsHighlighter ? { searchWords, highlightClassName: styles.logsRowMatchHighLight } : undefined;
|
const highlight = needsHighlighter ? { searchWords, highlightClassName: styles.logsRowMatchHighLight } : undefined;
|
||||||
return <LogMessageAnsi value={entry} highlight={highlight} />;
|
return <LogMessageAnsi value={truncatedEntry} highlight={highlight} />;
|
||||||
} else if (needsHighlighter) {
|
} else if (needsHighlighter) {
|
||||||
return (
|
return (
|
||||||
<Highlighter
|
<Highlighter
|
||||||
textToHighlight={entry}
|
textToHighlight={truncatedEntry}
|
||||||
searchWords={searchWords}
|
searchWords={searchWords}
|
||||||
findChunks={findHighlightChunksInText}
|
findChunks={findHighlightChunksInText}
|
||||||
highlightClassName={styles.logsRowMatchHighLight}
|
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 = (
|
const restructureLog = (
|
||||||
line: string,
|
line: string,
|
||||||
prettifyLogMessage: boolean,
|
prettifyLogMessage: boolean,
|
||||||
|
@ -1742,6 +1742,10 @@
|
|||||||
"logs": {
|
"logs": {
|
||||||
"infinite-scroll": {
|
"infinite-scroll": {
|
||||||
"older-logs": "Older logs"
|
"older-logs": "Older logs"
|
||||||
|
},
|
||||||
|
"log-row-message": {
|
||||||
|
"ellipsis": "… ",
|
||||||
|
"more": "more"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate-to-cloud": {
|
"migrate-to-cloud": {
|
||||||
|
@ -1742,6 +1742,10 @@
|
|||||||
"logs": {
|
"logs": {
|
||||||
"infinite-scroll": {
|
"infinite-scroll": {
|
||||||
"older-logs": "Øľđęř ľőģş"
|
"older-logs": "Øľđęř ľőģş"
|
||||||
|
},
|
||||||
|
"log-row-message": {
|
||||||
|
"ellipsis": "… ",
|
||||||
|
"more": "mőřę"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate-to-cloud": {
|
"migrate-to-cloud": {
|
||||||
|
Loading…
Reference in New Issue
Block a user