mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 15:45:43 -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 userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
import { LogDetailsRow } from './LogDetailsRow';
|
||||
@ -105,4 +106,16 @@ describe('LogDetailsRow', () => {
|
||||
expect(screen.getByTestId('logLabelStats')).toBeInTheDocument();
|
||||
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 { Themeable2 } from '../../types/theme';
|
||||
import { ClipboardButton } from '../ClipboardButton/ClipboardButton';
|
||||
import { DataLinkButton } from '../DataLinks/DataLinkButton';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
|
||||
@ -31,6 +32,7 @@ interface State {
|
||||
showFieldsStats: boolean;
|
||||
fieldCount: number;
|
||||
fieldStats: LogLabelStatsModel[] | null;
|
||||
mouseOver: boolean;
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
@ -52,18 +54,27 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
showingField: css`
|
||||
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`
|
||||
label: wrapLine;
|
||||
white-space: pre-wrap;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
showFieldsStats: false,
|
||||
fieldCount: 0,
|
||||
fieldStats: null,
|
||||
mouseOver: false,
|
||||
};
|
||||
|
||||
showField = () => {
|
||||
@ -112,6 +123,11 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
hoverValueCopy() {
|
||||
const mouseOver = !this.state.mouseOver;
|
||||
this.setState({ mouseOver });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
@ -126,7 +142,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
onClickFilterLabel,
|
||||
onClickFilterOutLabel,
|
||||
} = this.props;
|
||||
const { showFieldsStats, fieldStats, fieldCount } = this.state;
|
||||
const { showFieldsStats, fieldStats, fieldCount, mouseOver } = this.state;
|
||||
const styles = getStyles(theme);
|
||||
const style = getLogRowStyles(theme);
|
||||
|
||||
@ -166,8 +182,23 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
|
||||
{/* Key - value columns */}
|
||||
<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}
|
||||
{mouseOver && (
|
||||
<ClipboardButton
|
||||
getText={() => parsedValue}
|
||||
title="Copy value to clipboard"
|
||||
fill="text"
|
||||
variant="secondary"
|
||||
icon="copy"
|
||||
size="sm"
|
||||
className={styles.hoverValueCopy}
|
||||
/>
|
||||
)}
|
||||
{links?.map((link) => (
|
||||
<span key={link.title}>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user