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:
Seyaji 2022-06-29 10:13:30 +01:00 committed by GitHub
parent 2372501368
commit 47fda6c18c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 3 deletions

View File

@ -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();
});
});

View File

@ -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}>
&nbsp;