Logs: Show copy button independently from context (#55934)

This commit is contained in:
Sven Grossmann 2022-09-29 10:00:01 +02:00 committed by GitHub
parent bf07deb992
commit 998a368c69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 31 deletions

View File

@ -22,6 +22,7 @@ import {
LoadingState, LoadingState,
SplitOpen, SplitOpen,
DataQueryResponse, DataQueryResponse,
CoreApp,
} from '@grafana/data'; } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { import {
@ -475,6 +476,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
showDetectedFields={showDetectedFields} showDetectedFields={showDetectedFields}
onClickShowDetectedField={this.showDetectedField} onClickShowDetectedField={this.showDetectedField}
onClickHideDetectedField={this.hideDetectedField} onClickHideDetectedField={this.hideDetectedField}
app={CoreApp.Explore}
/> />
</div> </div>
<LogsNavigation <LogsNavigation

View File

@ -12,6 +12,7 @@ import {
checkLogsError, checkLogsError,
escapeUnescapedString, escapeUnescapedString,
GrafanaTheme2, GrafanaTheme2,
CoreApp,
} from '@grafana/data'; } from '@grafana/data';
import { styleMixins, withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui'; import { styleMixins, withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui';
@ -42,6 +43,8 @@ interface Props extends Themeable2 {
logsSortOrder?: LogsSortOrder | null; logsSortOrder?: LogsSortOrder | null;
forceEscape?: boolean; forceEscape?: boolean;
showDetectedFields?: string[]; showDetectedFields?: string[];
showRowMenu?: boolean;
app?: CoreApp;
getRows: () => LogRowModel[]; getRows: () => LogRowModel[];
onClickFilterLabel?: (key: string, value: string) => void; onClickFilterLabel?: (key: string, value: string) => void;
onClickFilterOutLabel?: (key: string, value: string) => void; onClickFilterOutLabel?: (key: string, value: string) => void;
@ -52,6 +55,7 @@ interface Props extends Themeable2 {
onClickShowDetectedField?: (key: string) => void; onClickShowDetectedField?: (key: string) => void;
onClickHideDetectedField?: (key: string) => void; onClickHideDetectedField?: (key: string) => void;
onLogRowHover?: (row?: LogRowModel) => void; onLogRowHover?: (row?: LogRowModel) => void;
toggleContextIsOpen?: () => void;
} }
interface State { interface State {
@ -91,6 +95,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
}; };
toggleContext = () => { toggleContext = () => {
this.props.toggleContextIsOpen?.();
this.setState((state) => { this.setState((state) => {
return { return {
showContext: !state.showContext, showContext: !state.showContext,
@ -132,6 +137,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
row, row,
showDuplicates, showDuplicates,
showContextToggle, showContextToggle,
showRowMenu,
showLabels, showLabels,
showTime, showTime,
showDetectedFields, showDetectedFields,
@ -141,6 +147,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
getFieldLinks, getFieldLinks,
forceEscape, forceEscape,
onLogRowHover, onLogRowHover,
app,
} = this.props; } = this.props;
const { showDetails, showContext } = this.state; const { showDetails, showContext } = this.state;
const style = getLogRowStyles(theme, row.logLevel); const style = getLogRowStyles(theme, row.logLevel);
@ -207,9 +214,11 @@ class UnThemedLogRow extends PureComponent<Props, State> {
context={context} context={context}
contextIsOpen={showContext} contextIsOpen={showContext}
showContextToggle={showContextToggle} showContextToggle={showContextToggle}
showRowMenu={showRowMenu}
wrapLogMessage={wrapLogMessage} wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage} prettifyLogMessage={prettifyLogMessage}
onToggleContext={this.toggleContext} onToggleContext={this.toggleContext}
app={app}
logsSortOrder={logsSortOrder} logsSortOrder={logsSortOrder}
/> />
)} )}

View File

@ -4,7 +4,7 @@ import React, { PureComponent } from 'react';
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { LogRowModel, findHighlightChunksInText, GrafanaTheme2, LogsSortOrder } from '@grafana/data'; import { LogRowModel, findHighlightChunksInText, GrafanaTheme2, LogsSortOrder, CoreApp } from '@grafana/data';
import { withTheme2, Themeable2, IconButton, Tooltip } from '@grafana/ui'; import { withTheme2, Themeable2, IconButton, Tooltip } from '@grafana/ui';
import { LogMessageAnsi } from './LogMessageAnsi'; import { LogMessageAnsi } from './LogMessageAnsi';
@ -12,8 +12,6 @@ import { LogRowContext } from './LogRowContext';
import { LogRowContextQueryErrors, HasMoreContextRows, LogRowContextRows } from './LogRowContextProvider'; import { LogRowContextQueryErrors, HasMoreContextRows, LogRowContextRows } from './LogRowContextProvider';
import { getLogRowStyles } from './getLogRowStyles'; import { getLogRowStyles } from './getLogRowStyles';
//Components
export const MAX_CHARACTERS = 100000; export const MAX_CHARACTERS = 100000;
interface Props extends Themeable2 { interface Props extends Themeable2 {
@ -24,6 +22,8 @@ interface Props extends Themeable2 {
prettifyLogMessage: boolean; prettifyLogMessage: boolean;
errors?: LogRowContextQueryErrors; errors?: LogRowContextQueryErrors;
context?: LogRowContextRows; context?: LogRowContextRows;
showRowMenu?: boolean;
app?: CoreApp;
showContextToggle?: (row?: LogRowModel) => boolean; showContextToggle?: (row?: LogRowModel) => boolean;
getRows: () => LogRowModel[]; getRows: () => LogRowModel[];
onToggleContext: () => void; onToggleContext: () => void;
@ -31,7 +31,7 @@ interface Props extends Themeable2 {
logsSortOrder?: LogsSortOrder | null; logsSortOrder?: LogsSortOrder | null;
} }
const getStyles = (theme: GrafanaTheme2) => { const getStyles = (theme: GrafanaTheme2, showContextButton: boolean, isInDashboard: boolean | undefined) => {
const outlineColor = tinycolor(theme.components.dashboard.background).setAlpha(0.7).toRgbString(); const outlineColor = tinycolor(theme.components.dashboard.background).setAlpha(0.7).toRgbString();
return { return {
@ -52,7 +52,7 @@ const getStyles = (theme: GrafanaTheme2) => {
display: block; display: block;
margin-left: 0px; margin-left: 0px;
`, `,
contextButton: css` rowMenu: css`
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-direction: row; flex-direction: row;
@ -60,15 +60,16 @@ const getStyles = (theme: GrafanaTheme2) => {
justify-content: space-evenly; justify-content: space-evenly;
align-items: center; align-items: center;
position: absolute; position: absolute;
right: -8px; right: ${isInDashboard ? '0px' : '-8px'};
top: 0; top: 0;
bottom: auto; bottom: auto;
width: 80px;
height: 36px; height: 36px;
background: ${theme.colors.background.primary}; background: ${theme.colors.background.primary};
box-shadow: ${theme.shadows.z3}; box-shadow: ${theme.shadows.z3};
padding: ${theme.spacing(0, 0, 0, 0.5)}; padding: ${theme.spacing(0, 0, 0, 0.5)};
z-index: 100; z-index: 100;
visibility: hidden;
width: ${showContextButton ? '80px' : '40px'};
`, `,
}; };
}; };
@ -125,17 +126,20 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
updateLimit, updateLimit,
context, context,
contextIsOpen, contextIsOpen,
showContextToggle, showRowMenu,
wrapLogMessage, wrapLogMessage,
prettifyLogMessage, prettifyLogMessage,
onToggleContext, onToggleContext,
app,
logsSortOrder, logsSortOrder,
showContextToggle,
} = this.props; } = this.props;
const style = getLogRowStyles(theme, row.logLevel); const style = getLogRowStyles(theme, row.logLevel);
const { hasAnsi, raw } = row; const { hasAnsi, raw } = row;
const restructuredEntry = restructureLog(raw, prettifyLogMessage); const restructuredEntry = restructureLog(raw, prettifyLogMessage);
const styles = getStyles(theme); const shouldShowContextToggle = showContextToggle ? showContextToggle(row) : false;
const styles = getStyles(theme, shouldShowContextToggle, app === CoreApp.Dashboard);
return ( return (
// When context is open, the position has to be NOT relative. // When context is open, the position has to be NOT relative.
@ -163,14 +167,13 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
<span className={cx(styles.positionRelative, { [styles.rowWithContext]: contextIsOpen })}> <span className={cx(styles.positionRelative, { [styles.rowWithContext]: contextIsOpen })}>
{renderLogMessage(hasAnsi, restructuredEntry, row.searchWords, style.logsRowMatchHighLight)} {renderLogMessage(hasAnsi, restructuredEntry, row.searchWords, style.logsRowMatchHighLight)}
</span> </span>
{!contextIsOpen && showContextToggle?.(row) && ( {showRowMenu && (
<span <span className={cx('log-row-menu', styles.rowMenu)} onClick={(e) => e.stopPropagation()}>
className={cx('log-row-context', style.context, styles.contextButton)} {shouldShowContextToggle && (
onClick={(e) => e.stopPropagation()} <Tooltip placement="top" content={'Show context'}>
> <IconButton size="md" name="gf-show-context" onClick={this.onContextToggle} />
<Tooltip placement="top" content={'Show context'}> </Tooltip>
<IconButton size="md" name="gf-show-context" onClick={this.onContextToggle} /> )}
</Tooltip>
<Tooltip placement="top" content={'Copy'}> <Tooltip placement="top" content={'Copy'}>
<IconButton <IconButton
size="md" size="md"

View File

@ -1,7 +1,16 @@
import memoizeOne from 'memoize-one'; import memoizeOne from 'memoize-one';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { TimeZone, LogsDedupStrategy, LogRowModel, Field, LinkModel, LogsSortOrder, sortLogRows } from '@grafana/data'; import {
TimeZone,
LogsDedupStrategy,
LogRowModel,
Field,
LinkModel,
LogsSortOrder,
sortLogRows,
CoreApp,
} from '@grafana/data';
import { withTheme2, Themeable2 } from '@grafana/ui'; import { withTheme2, Themeable2 } from '@grafana/ui';
//Components //Components
@ -25,6 +34,7 @@ export interface Props extends Themeable2 {
previewLimit?: number; previewLimit?: number;
forceEscape?: boolean; forceEscape?: boolean;
showDetectedFields?: string[]; showDetectedFields?: string[];
app?: CoreApp;
showContextToggle?: (row?: LogRowModel) => boolean; showContextToggle?: (row?: LogRowModel) => boolean;
onClickFilterLabel?: (key: string, value: string) => void; onClickFilterLabel?: (key: string, value: string) => void;
onClickFilterOutLabel?: (key: string, value: string) => void; onClickFilterOutLabel?: (key: string, value: string) => void;
@ -37,6 +47,7 @@ export interface Props extends Themeable2 {
interface State { interface State {
renderAll: boolean; renderAll: boolean;
contextIsOpen: boolean;
} }
class UnThemedLogRows extends PureComponent<Props, State> { class UnThemedLogRows extends PureComponent<Props, State> {
@ -48,6 +59,18 @@ class UnThemedLogRows extends PureComponent<Props, State> {
state: State = { state: State = {
renderAll: false, renderAll: false,
contextIsOpen: false,
};
/**
* Toggle the `contextIsOpen` state when a context of one LogRow is opened in order to not show the menu of the other log rows.
*/
toggleContextIsOpen = (): void => {
this.setState((state) => {
return {
contextIsOpen: !state.contextIsOpen,
};
});
}; };
componentDidMount() { componentDidMount() {
@ -100,8 +123,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onClickHideDetectedField, onClickHideDetectedField,
forceEscape, forceEscape,
onLogRowHover, onLogRowHover,
app,
} = this.props; } = this.props;
const { renderAll } = this.state; const { renderAll, contextIsOpen } = this.state;
const { logsRowsTable } = getLogRowStyles(theme); const { logsRowsTable } = getLogRowStyles(theme);
const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows; const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows;
const hasData = logRows && logRows.length > 0; const hasData = logRows && logRows.length > 0;
@ -130,6 +154,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getRowContext={getRowContext} getRowContext={getRowContext}
row={row} row={row}
showContextToggle={showContextToggle} showContextToggle={showContextToggle}
showRowMenu={!contextIsOpen}
showDuplicates={showDuplicates} showDuplicates={showDuplicates}
showLabels={showLabels} showLabels={showLabels}
showTime={showTime} showTime={showTime}
@ -145,7 +170,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getFieldLinks={getFieldLinks} getFieldLinks={getFieldLinks}
logsSortOrder={logsSortOrder} logsSortOrder={logsSortOrder}
forceEscape={forceEscape} forceEscape={forceEscape}
toggleContextIsOpen={this.toggleContextIsOpen}
onLogRowHover={onLogRowHover} onLogRowHover={onLogRowHover}
app={app}
/> />
))} ))}
{hasData && {hasData &&
@ -157,6 +184,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getRowContext={getRowContext} getRowContext={getRowContext}
row={row} row={row}
showContextToggle={showContextToggle} showContextToggle={showContextToggle}
showRowMenu={!contextIsOpen}
showDuplicates={showDuplicates} showDuplicates={showDuplicates}
showLabels={showLabels} showLabels={showLabels}
showTime={showTime} showTime={showTime}
@ -172,7 +200,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getFieldLinks={getFieldLinks} getFieldLinks={getFieldLinks}
logsSortOrder={logsSortOrder} logsSortOrder={logsSortOrder}
forceEscape={forceEscape} forceEscape={forceEscape}
toggleContextIsOpen={this.toggleContextIsOpen}
onLogRowHover={onLogRowHover} onLogRowHover={onLogRowHover}
app={app}
/> />
))} ))}
{hasData && !renderAll && ( {hasData && !renderAll && (

View File

@ -45,13 +45,6 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
font-size: ${theme.typography.bodySmall.fontSize}; font-size: ${theme.typography.bodySmall.fontSize};
width: 100%; width: 100%;
`, `,
context: css`
label: context;
visibility: hidden;
white-space: nowrap;
position: relative;
margin-left: 10px;
`,
logsRow: css` logsRow: css`
label: logs-row; label: logs-row;
width: 100%; width: 100%;
@ -59,13 +52,9 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
vertical-align: top; vertical-align: top;
&:hover { &:hover {
.log-row-context { .log-row-menu {
visibility: visible; visibility: visible;
z-index: 1; z-index: 1;
text-decoration: underline;
&:hover {
color: ${theme.colors.warning.main};
}
} }
} }

View File

@ -10,6 +10,7 @@ import {
LogRowModel, LogRowModel,
DataHoverClearEvent, DataHoverClearEvent,
DataHoverEvent, DataHoverEvent,
CoreApp,
} from '@grafana/data'; } from '@grafana/data';
import { CustomScrollbar, useStyles2, usePanelContext } from '@grafana/ui'; import { CustomScrollbar, useStyles2, usePanelContext } from '@grafana/ui';
import { dataFrameToLogsModel, dedupLogRows, COMMON_LABELS } from 'app/core/logsModel'; import { dataFrameToLogsModel, dedupLogRows, COMMON_LABELS } from 'app/core/logsModel';
@ -116,6 +117,7 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
enableLogDetails={enableLogDetails} enableLogDetails={enableLogDetails}
previewLimit={isAscending ? logRows.length : undefined} previewLimit={isAscending ? logRows.length : undefined}
onLogRowHover={onLogRowHover} onLogRowHover={onLogRowHover}
app={CoreApp.Dashboard}
/> />
{showCommonLabels && isAscending && renderCommonLabels()} {showCommonLabels && isAscending && renderCommonLabels()}
</div> </div>