mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki/Logs: Make it possible to copy log values to clipboard (#50914)
* adds copy icon while hovering over log value, copys value to clipboard on click * Show copy icon while hovering over single log value * Add test to check for -> render of copy-icon while hovering a single log value * Show copy icon while hovering over single log value * refactor: remove commented out code Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
This commit is contained in:
parent
2372501368
commit
47fda6c18c
@ -1,4 +1,5 @@
|
|||||||
import { screen, render, fireEvent } from '@testing-library/react';
|
import { screen, render, fireEvent } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import React, { ComponentProps } from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
|
|
||||||
import { LogDetailsRow } from './LogDetailsRow';
|
import { LogDetailsRow } from './LogDetailsRow';
|
||||||
@ -105,4 +106,16 @@ describe('LogDetailsRow', () => {
|
|||||||
expect(screen.getByTestId('logLabelStats')).toBeInTheDocument();
|
expect(screen.getByTestId('logLabelStats')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('logLabelStats')).toHaveTextContent('another value');
|
expect(screen.getByTestId('logLabelStats')).toHaveTextContent('another value');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render clipboard button on hover of log row table value', async () => {
|
||||||
|
setup({ parsedKey: 'key', parsedValue: 'value' });
|
||||||
|
|
||||||
|
const valueCell = screen.getByRole('cell', { name: 'value' });
|
||||||
|
await userEvent.hover(valueCell);
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Copy value to clipboard' })).toBeInTheDocument();
|
||||||
|
await userEvent.unhover(valueCell);
|
||||||
|
|
||||||
|
expect(screen.queryByRole('button', { name: 'Copy value to clipboard' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import { Field, LinkModel, LogLabelStatsModel, GrafanaTheme2 } from '@grafana/da
|
|||||||
|
|
||||||
import { withTheme2 } from '../../themes/index';
|
import { withTheme2 } from '../../themes/index';
|
||||||
import { Themeable2 } from '../../types/theme';
|
import { Themeable2 } from '../../types/theme';
|
||||||
|
import { ClipboardButton } from '../ClipboardButton/ClipboardButton';
|
||||||
import { DataLinkButton } from '../DataLinks/DataLinkButton';
|
import { DataLinkButton } from '../DataLinks/DataLinkButton';
|
||||||
import { IconButton } from '../IconButton/IconButton';
|
import { IconButton } from '../IconButton/IconButton';
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ interface State {
|
|||||||
showFieldsStats: boolean;
|
showFieldsStats: boolean;
|
||||||
fieldCount: number;
|
fieldCount: number;
|
||||||
fieldStats: LogLabelStatsModel[] | null;
|
fieldStats: LogLabelStatsModel[] | null;
|
||||||
|
mouseOver: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
@ -52,18 +54,27 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
showingField: css`
|
showingField: css`
|
||||||
color: ${theme.colors.primary.text};
|
color: ${theme.colors.primary.text};
|
||||||
`,
|
`,
|
||||||
|
hoverValueCopy: css`
|
||||||
|
margin: ${theme.spacing(0, 0, 0, 1.2)};
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 20px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
`,
|
||||||
wrapLine: css`
|
wrapLine: css`
|
||||||
label: wrapLine;
|
label: wrapLine;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
showFieldsStats: false,
|
showFieldsStats: false,
|
||||||
fieldCount: 0,
|
fieldCount: 0,
|
||||||
fieldStats: null,
|
fieldStats: null,
|
||||||
|
mouseOver: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
showField = () => {
|
showField = () => {
|
||||||
@ -112,6 +123,11 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hoverValueCopy() {
|
||||||
|
const mouseOver = !this.state.mouseOver;
|
||||||
|
this.setState({ mouseOver });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
theme,
|
theme,
|
||||||
@ -126,7 +142,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
onClickFilterLabel,
|
onClickFilterLabel,
|
||||||
onClickFilterOutLabel,
|
onClickFilterOutLabel,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showFieldsStats, fieldStats, fieldCount } = this.state;
|
const { showFieldsStats, fieldStats, fieldCount, mouseOver } = this.state;
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
const style = getLogRowStyles(theme);
|
const style = getLogRowStyles(theme);
|
||||||
|
|
||||||
@ -166,8 +182,23 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
{/* Key - value columns */}
|
{/* Key - value columns */}
|
||||||
<td className={style.logDetailsLabel}>{parsedKey}</td>
|
<td className={style.logDetailsLabel}>{parsedKey}</td>
|
||||||
<td className={cx(styles.wordBreakAll, wrapLogMessage && styles.wrapLine)}>
|
<td
|
||||||
|
className={cx(styles.wordBreakAll, wrapLogMessage && styles.wrapLine)}
|
||||||
|
onMouseEnter={this.hoverValueCopy.bind(this)}
|
||||||
|
onMouseLeave={this.hoverValueCopy.bind(this)}
|
||||||
|
>
|
||||||
{parsedValue}
|
{parsedValue}
|
||||||
|
{mouseOver && (
|
||||||
|
<ClipboardButton
|
||||||
|
getText={() => parsedValue}
|
||||||
|
title="Copy value to clipboard"
|
||||||
|
fill="text"
|
||||||
|
variant="secondary"
|
||||||
|
icon="copy"
|
||||||
|
size="sm"
|
||||||
|
className={styles.hoverValueCopy}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{links?.map((link) => (
|
{links?.map((link) => (
|
||||||
<span key={link.title}>
|
<span key={link.title}>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user