mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
Logs: Show copy button independently from context (#55934)
This commit is contained in:
parent
bf07deb992
commit
998a368c69
@ -22,6 +22,7 @@ import {
|
||||
LoadingState,
|
||||
SplitOpen,
|
||||
DataQueryResponse,
|
||||
CoreApp,
|
||||
} from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import {
|
||||
@ -475,6 +476,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
|
||||
showDetectedFields={showDetectedFields}
|
||||
onClickShowDetectedField={this.showDetectedField}
|
||||
onClickHideDetectedField={this.hideDetectedField}
|
||||
app={CoreApp.Explore}
|
||||
/>
|
||||
</div>
|
||||
<LogsNavigation
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
checkLogsError,
|
||||
escapeUnescapedString,
|
||||
GrafanaTheme2,
|
||||
CoreApp,
|
||||
} from '@grafana/data';
|
||||
import { styleMixins, withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui';
|
||||
|
||||
@ -42,6 +43,8 @@ interface Props extends Themeable2 {
|
||||
logsSortOrder?: LogsSortOrder | null;
|
||||
forceEscape?: boolean;
|
||||
showDetectedFields?: string[];
|
||||
showRowMenu?: boolean;
|
||||
app?: CoreApp;
|
||||
getRows: () => LogRowModel[];
|
||||
onClickFilterLabel?: (key: string, value: string) => void;
|
||||
onClickFilterOutLabel?: (key: string, value: string) => void;
|
||||
@ -52,6 +55,7 @@ interface Props extends Themeable2 {
|
||||
onClickShowDetectedField?: (key: string) => void;
|
||||
onClickHideDetectedField?: (key: string) => void;
|
||||
onLogRowHover?: (row?: LogRowModel) => void;
|
||||
toggleContextIsOpen?: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -91,6 +95,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
toggleContext = () => {
|
||||
this.props.toggleContextIsOpen?.();
|
||||
this.setState((state) => {
|
||||
return {
|
||||
showContext: !state.showContext,
|
||||
@ -132,6 +137,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
row,
|
||||
showDuplicates,
|
||||
showContextToggle,
|
||||
showRowMenu,
|
||||
showLabels,
|
||||
showTime,
|
||||
showDetectedFields,
|
||||
@ -141,6 +147,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
getFieldLinks,
|
||||
forceEscape,
|
||||
onLogRowHover,
|
||||
app,
|
||||
} = this.props;
|
||||
const { showDetails, showContext } = this.state;
|
||||
const style = getLogRowStyles(theme, row.logLevel);
|
||||
@ -207,9 +214,11 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
context={context}
|
||||
contextIsOpen={showContext}
|
||||
showContextToggle={showContextToggle}
|
||||
showRowMenu={showRowMenu}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
prettifyLogMessage={prettifyLogMessage}
|
||||
onToggleContext={this.toggleContext}
|
||||
app={app}
|
||||
logsSortOrder={logsSortOrder}
|
||||
/>
|
||||
)}
|
||||
|
@ -4,7 +4,7 @@ import React, { PureComponent } from 'react';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
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 { LogMessageAnsi } from './LogMessageAnsi';
|
||||
@ -12,8 +12,6 @@ import { LogRowContext } from './LogRowContext';
|
||||
import { LogRowContextQueryErrors, HasMoreContextRows, LogRowContextRows } from './LogRowContextProvider';
|
||||
import { getLogRowStyles } from './getLogRowStyles';
|
||||
|
||||
//Components
|
||||
|
||||
export const MAX_CHARACTERS = 100000;
|
||||
|
||||
interface Props extends Themeable2 {
|
||||
@ -24,6 +22,8 @@ interface Props extends Themeable2 {
|
||||
prettifyLogMessage: boolean;
|
||||
errors?: LogRowContextQueryErrors;
|
||||
context?: LogRowContextRows;
|
||||
showRowMenu?: boolean;
|
||||
app?: CoreApp;
|
||||
showContextToggle?: (row?: LogRowModel) => boolean;
|
||||
getRows: () => LogRowModel[];
|
||||
onToggleContext: () => void;
|
||||
@ -31,7 +31,7 @@ interface Props extends Themeable2 {
|
||||
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();
|
||||
|
||||
return {
|
||||
@ -52,7 +52,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
display: block;
|
||||
margin-left: 0px;
|
||||
`,
|
||||
contextButton: css`
|
||||
rowMenu: css`
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
@ -60,15 +60,16 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
right: ${isInDashboard ? '0px' : '-8px'};
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
background: ${theme.colors.background.primary};
|
||||
box-shadow: ${theme.shadows.z3};
|
||||
padding: ${theme.spacing(0, 0, 0, 0.5)};
|
||||
z-index: 100;
|
||||
visibility: hidden;
|
||||
width: ${showContextButton ? '80px' : '40px'};
|
||||
`,
|
||||
};
|
||||
};
|
||||
@ -125,17 +126,20 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
|
||||
updateLimit,
|
||||
context,
|
||||
contextIsOpen,
|
||||
showContextToggle,
|
||||
showRowMenu,
|
||||
wrapLogMessage,
|
||||
prettifyLogMessage,
|
||||
onToggleContext,
|
||||
app,
|
||||
logsSortOrder,
|
||||
showContextToggle,
|
||||
} = this.props;
|
||||
|
||||
const style = getLogRowStyles(theme, row.logLevel);
|
||||
const { hasAnsi, raw } = row;
|
||||
const restructuredEntry = restructureLog(raw, prettifyLogMessage);
|
||||
const styles = getStyles(theme);
|
||||
const shouldShowContextToggle = showContextToggle ? showContextToggle(row) : false;
|
||||
const styles = getStyles(theme, shouldShowContextToggle, app === CoreApp.Dashboard);
|
||||
|
||||
return (
|
||||
// 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 })}>
|
||||
{renderLogMessage(hasAnsi, restructuredEntry, row.searchWords, style.logsRowMatchHighLight)}
|
||||
</span>
|
||||
{!contextIsOpen && showContextToggle?.(row) && (
|
||||
<span
|
||||
className={cx('log-row-context', style.context, styles.contextButton)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{showRowMenu && (
|
||||
<span className={cx('log-row-menu', styles.rowMenu)} onClick={(e) => e.stopPropagation()}>
|
||||
{shouldShowContextToggle && (
|
||||
<Tooltip placement="top" content={'Show context'}>
|
||||
<IconButton size="md" name="gf-show-context" onClick={this.onContextToggle} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip placement="top" content={'Copy'}>
|
||||
<IconButton
|
||||
size="md"
|
||||
|
@ -1,7 +1,16 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
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';
|
||||
|
||||
//Components
|
||||
@ -25,6 +34,7 @@ export interface Props extends Themeable2 {
|
||||
previewLimit?: number;
|
||||
forceEscape?: boolean;
|
||||
showDetectedFields?: string[];
|
||||
app?: CoreApp;
|
||||
showContextToggle?: (row?: LogRowModel) => boolean;
|
||||
onClickFilterLabel?: (key: string, value: string) => void;
|
||||
onClickFilterOutLabel?: (key: string, value: string) => void;
|
||||
@ -37,6 +47,7 @@ export interface Props extends Themeable2 {
|
||||
|
||||
interface State {
|
||||
renderAll: boolean;
|
||||
contextIsOpen: boolean;
|
||||
}
|
||||
|
||||
class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
@ -48,6 +59,18 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
|
||||
state: State = {
|
||||
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() {
|
||||
@ -100,8 +123,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
onClickHideDetectedField,
|
||||
forceEscape,
|
||||
onLogRowHover,
|
||||
app,
|
||||
} = this.props;
|
||||
const { renderAll } = this.state;
|
||||
const { renderAll, contextIsOpen } = this.state;
|
||||
const { logsRowsTable } = getLogRowStyles(theme);
|
||||
const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows;
|
||||
const hasData = logRows && logRows.length > 0;
|
||||
@ -130,6 +154,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
getRowContext={getRowContext}
|
||||
row={row}
|
||||
showContextToggle={showContextToggle}
|
||||
showRowMenu={!contextIsOpen}
|
||||
showDuplicates={showDuplicates}
|
||||
showLabels={showLabels}
|
||||
showTime={showTime}
|
||||
@ -145,7 +170,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
getFieldLinks={getFieldLinks}
|
||||
logsSortOrder={logsSortOrder}
|
||||
forceEscape={forceEscape}
|
||||
toggleContextIsOpen={this.toggleContextIsOpen}
|
||||
onLogRowHover={onLogRowHover}
|
||||
app={app}
|
||||
/>
|
||||
))}
|
||||
{hasData &&
|
||||
@ -157,6 +184,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
getRowContext={getRowContext}
|
||||
row={row}
|
||||
showContextToggle={showContextToggle}
|
||||
showRowMenu={!contextIsOpen}
|
||||
showDuplicates={showDuplicates}
|
||||
showLabels={showLabels}
|
||||
showTime={showTime}
|
||||
@ -172,7 +200,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
getFieldLinks={getFieldLinks}
|
||||
logsSortOrder={logsSortOrder}
|
||||
forceEscape={forceEscape}
|
||||
toggleContextIsOpen={this.toggleContextIsOpen}
|
||||
onLogRowHover={onLogRowHover}
|
||||
app={app}
|
||||
/>
|
||||
))}
|
||||
{hasData && !renderAll && (
|
||||
|
@ -45,13 +45,6 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
width: 100%;
|
||||
`,
|
||||
context: css`
|
||||
label: context;
|
||||
visibility: hidden;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
margin-left: 10px;
|
||||
`,
|
||||
logsRow: css`
|
||||
label: logs-row;
|
||||
width: 100%;
|
||||
@ -59,13 +52,9 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
|
||||
vertical-align: top;
|
||||
|
||||
&:hover {
|
||||
.log-row-context {
|
||||
.log-row-menu {
|
||||
visibility: visible;
|
||||
z-index: 1;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
color: ${theme.colors.warning.main};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
LogRowModel,
|
||||
DataHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
CoreApp,
|
||||
} from '@grafana/data';
|
||||
import { CustomScrollbar, useStyles2, usePanelContext } from '@grafana/ui';
|
||||
import { dataFrameToLogsModel, dedupLogRows, COMMON_LABELS } from 'app/core/logsModel';
|
||||
@ -116,6 +117,7 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||
enableLogDetails={enableLogDetails}
|
||||
previewLimit={isAscending ? logRows.length : undefined}
|
||||
onLogRowHover={onLogRowHover}
|
||||
app={CoreApp.Dashboard}
|
||||
/>
|
||||
{showCommonLabels && isAscending && renderCommonLabels()}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user