mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore/Logs: Escaping of incorrectly escaped log lines (#31352)
* POC: Escaping of incorrectly escaped log lines * Remove unused import * Fix test, change copy * Make escapedNewlines optional * Fix typechecks * Remove loading state from the escaping button * Update namings
This commit is contained in:
@@ -62,6 +62,7 @@ export interface LogRowModel {
|
|||||||
// Actual log line
|
// Actual log line
|
||||||
entry: string;
|
entry: string;
|
||||||
hasAnsi: boolean;
|
hasAnsi: boolean;
|
||||||
|
hasUnescapedContent: boolean;
|
||||||
labels: Labels;
|
labels: Labels;
|
||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
raw: string;
|
raw: string;
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ describe('sortLogsResult', () => {
|
|||||||
dataFrame: new MutableDataFrame(),
|
dataFrame: new MutableDataFrame(),
|
||||||
entry: '',
|
entry: '',
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
logLevel: LogLevel.info,
|
logLevel: LogLevel.info,
|
||||||
raw: '',
|
raw: '',
|
||||||
@@ -312,6 +313,7 @@ describe('sortLogsResult', () => {
|
|||||||
dataFrame: new MutableDataFrame(),
|
dataFrame: new MutableDataFrame(),
|
||||||
entry: '',
|
entry: '',
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
logLevel: LogLevel.info,
|
logLevel: LogLevel.info,
|
||||||
raw: '',
|
raw: '',
|
||||||
|
|||||||
@@ -223,3 +223,6 @@ export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorM
|
|||||||
hasError: false,
|
hasError: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const escapeUnescapedString = (string: string) =>
|
||||||
|
string.replace(/\\n|\\t|\\r/g, (match: string) => (match.slice(1) === 't' ? '\t' : '\n'));
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowMode
|
|||||||
timeLocal: '',
|
timeLocal: '',
|
||||||
timeUtc: '',
|
timeUtc: '',
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
entry: '',
|
entry: '',
|
||||||
raw: '',
|
raw: '',
|
||||||
uid: '0',
|
uid: '0',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
GrafanaTheme,
|
GrafanaTheme,
|
||||||
dateTimeFormat,
|
dateTimeFormat,
|
||||||
checkLogsError,
|
checkLogsError,
|
||||||
|
escapeUnescapedString,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { Tooltip } from '../Tooltip/Tooltip';
|
import { Tooltip } from '../Tooltip/Tooltip';
|
||||||
@@ -42,6 +43,8 @@ interface Props extends Themeable {
|
|||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
allowDetails?: boolean;
|
allowDetails?: boolean;
|
||||||
logsSortOrder?: LogsSortOrder | null;
|
logsSortOrder?: LogsSortOrder | null;
|
||||||
|
forceEscape?: boolean;
|
||||||
|
showDetectedFields?: string[];
|
||||||
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;
|
||||||
@@ -49,7 +52,6 @@ interface Props extends Themeable {
|
|||||||
getRowContext: (row: LogRowModel, options?: RowContextOptions) => Promise<DataQueryResponse>;
|
getRowContext: (row: LogRowModel, options?: RowContextOptions) => Promise<DataQueryResponse>;
|
||||||
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||||
showContextToggle?: (row?: LogRowModel) => boolean;
|
showContextToggle?: (row?: LogRowModel) => boolean;
|
||||||
showDetectedFields?: string[];
|
|
||||||
onClickShowDetectedField?: (key: string) => void;
|
onClickShowDetectedField?: (key: string) => void;
|
||||||
onClickHideDetectedField?: (key: string) => void;
|
onClickHideDetectedField?: (key: string) => void;
|
||||||
}
|
}
|
||||||
@@ -139,6 +141,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
wrapLogMessage,
|
wrapLogMessage,
|
||||||
theme,
|
theme,
|
||||||
getFieldLinks,
|
getFieldLinks,
|
||||||
|
forceEscape,
|
||||||
} = 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);
|
||||||
@@ -148,12 +151,15 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
[styles.errorLogRow]: hasError,
|
[styles.errorLogRow]: hasError,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const processedRow =
|
||||||
|
row.hasUnescapedContent && forceEscape ? { ...row, entry: escapeUnescapedString(row.entry) } : row;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr className={logRowBackground} onClick={this.toggleDetails}>
|
<tr className={logRowBackground} onClick={this.toggleDetails}>
|
||||||
{showDuplicates && (
|
{showDuplicates && (
|
||||||
<td className={style.logsRowDuplicates}>
|
<td className={style.logsRowDuplicates}>
|
||||||
{row.duplicates && row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
|
{processedRow.duplicates && processedRow.duplicates > 0 ? `${processedRow.duplicates + 1}x` : null}
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td className={cx({ [style.logsRowLevel]: !hasError })}>
|
<td className={cx({ [style.logsRowLevel]: !hasError })}>
|
||||||
@@ -169,14 +175,14 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
{showTime && <td className={style.logsRowLocalTime}>{this.renderTimeStamp(row.timeEpochMs)}</td>}
|
{showTime && <td className={style.logsRowLocalTime}>{this.renderTimeStamp(row.timeEpochMs)}</td>}
|
||||||
{showLabels && row.uniqueLabels && (
|
{showLabels && processedRow.uniqueLabels && (
|
||||||
<td className={style.logsRowLabels}>
|
<td className={style.logsRowLabels}>
|
||||||
<LogLabels labels={row.uniqueLabels} />
|
<LogLabels labels={processedRow.uniqueLabels} />
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
{showDetectedFields && showDetectedFields.length > 0 ? (
|
{showDetectedFields && showDetectedFields.length > 0 ? (
|
||||||
<LogRowMessageDetectedFields
|
<LogRowMessageDetectedFields
|
||||||
row={row}
|
row={processedRow}
|
||||||
showDetectedFields={showDetectedFields!}
|
showDetectedFields={showDetectedFields!}
|
||||||
getFieldLinks={getFieldLinks}
|
getFieldLinks={getFieldLinks}
|
||||||
wrapLogMessage={wrapLogMessage}
|
wrapLogMessage={wrapLogMessage}
|
||||||
@@ -184,7 +190,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
) : (
|
) : (
|
||||||
<LogRowMessage
|
<LogRowMessage
|
||||||
highlighterExpressions={highlighterExpressions}
|
highlighterExpressions={highlighterExpressions}
|
||||||
row={row}
|
row={processedRow}
|
||||||
getRows={getRows}
|
getRows={getRows}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
hasMoreContextRows={hasMoreContextRows}
|
hasMoreContextRows={hasMoreContextRows}
|
||||||
@@ -207,7 +213,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
onClickShowDetectedField={onClickShowDetectedField}
|
onClickShowDetectedField={onClickShowDetectedField}
|
||||||
onClickHideDetectedField={onClickHideDetectedField}
|
onClickHideDetectedField={onClickHideDetectedField}
|
||||||
getRows={getRows}
|
getRows={getRows}
|
||||||
row={row}
|
row={processedRow}
|
||||||
wrapLogMessage={wrapLogMessage}
|
wrapLogMessage={wrapLogMessage}
|
||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
showDetectedFields={showDetectedFields}
|
showDetectedFields={showDetectedFields}
|
||||||
@@ -219,16 +225,12 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showContext } = this.state;
|
const { showContext } = this.state;
|
||||||
const { logsSortOrder } = this.props;
|
const { logsSortOrder, row, getRowContext } = this.props;
|
||||||
|
|
||||||
if (showContext) {
|
if (showContext) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LogRowContextProvider
|
<LogRowContextProvider row={row} getRowContext={getRowContext} logsSortOrder={logsSortOrder}>
|
||||||
row={this.props.row}
|
|
||||||
getRowContext={this.props.getRowContext}
|
|
||||||
logsSortOrder={logsSortOrder}
|
|
||||||
>
|
|
||||||
{({ result, errors, hasMoreContextRows, updateLimit }) => {
|
{({ result, errors, hasMoreContextRows, updateLimit }) => {
|
||||||
return <>{this.renderLogRow(result, errors, hasMoreContextRows, updateLimit)}</>;
|
return <>{this.renderLogRow(result, errors, hasMoreContextRows, updateLimit)}</>;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ const row: LogRowModel = {
|
|||||||
entry: '4',
|
entry: '4',
|
||||||
labels: (null as any) as Labels,
|
labels: (null as any) as Labels,
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
raw: '4',
|
raw: '4',
|
||||||
logLevel: LogLevel.info,
|
logLevel: LogLevel.info,
|
||||||
timeEpochMs: 4,
|
timeEpochMs: 4,
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ const makeLog = (overrides: Partial<LogRowModel>): LogRowModel => {
|
|||||||
logLevel: LogLevel.debug,
|
logLevel: LogLevel.debug,
|
||||||
entry,
|
entry,
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
raw: entry,
|
raw: entry,
|
||||||
timeFromNow: '',
|
timeFromNow: '',
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export interface Props extends Themeable {
|
|||||||
deduplicatedRows?: LogRowModel[];
|
deduplicatedRows?: LogRowModel[];
|
||||||
dedupStrategy: LogsDedupStrategy;
|
dedupStrategy: LogsDedupStrategy;
|
||||||
highlighterExpressions?: string[];
|
highlighterExpressions?: string[];
|
||||||
showContextToggle?: (row?: LogRowModel) => boolean;
|
|
||||||
showLabels: boolean;
|
showLabels: boolean;
|
||||||
showTime: boolean;
|
showTime: boolean;
|
||||||
wrapLogMessage: boolean;
|
wrapLogMessage: boolean;
|
||||||
@@ -28,11 +27,13 @@ export interface Props extends Themeable {
|
|||||||
// Passed to fix problems with inactive scrolling in Logs Panel
|
// Passed to fix problems with inactive scrolling in Logs Panel
|
||||||
// Can be removed when we unify scrolling for Panel and Explore
|
// Can be removed when we unify scrolling for Panel and Explore
|
||||||
disableCustomHorizontalScroll?: boolean;
|
disableCustomHorizontalScroll?: boolean;
|
||||||
|
forceEscape?: boolean;
|
||||||
|
showDetectedFields?: string[];
|
||||||
|
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;
|
||||||
getRowContext?: (row: LogRowModel, options?: RowContextOptions) => Promise<any>;
|
getRowContext?: (row: LogRowModel, options?: RowContextOptions) => Promise<any>;
|
||||||
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||||
showDetectedFields?: string[];
|
|
||||||
onClickShowDetectedField?: (key: string) => void;
|
onClickShowDetectedField?: (key: string) => void;
|
||||||
onClickHideDetectedField?: (key: string) => void;
|
onClickHideDetectedField?: (key: string) => void;
|
||||||
}
|
}
|
||||||
@@ -101,6 +102,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
|||||||
showDetectedFields,
|
showDetectedFields,
|
||||||
onClickShowDetectedField,
|
onClickShowDetectedField,
|
||||||
onClickHideDetectedField,
|
onClickHideDetectedField,
|
||||||
|
forceEscape,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { renderAll } = this.state;
|
const { renderAll } = this.state;
|
||||||
const { logsRowsTable, logsRowsHorizontalScroll } = getLogRowStyles(theme);
|
const { logsRowsTable, logsRowsHorizontalScroll } = getLogRowStyles(theme);
|
||||||
@@ -151,6 +153,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
|||||||
onClickHideDetectedField={onClickHideDetectedField}
|
onClickHideDetectedField={onClickHideDetectedField}
|
||||||
getFieldLinks={getFieldLinks}
|
getFieldLinks={getFieldLinks}
|
||||||
logsSortOrder={logsSortOrder}
|
logsSortOrder={logsSortOrder}
|
||||||
|
forceEscape={forceEscape}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{hasData &&
|
{hasData &&
|
||||||
@@ -175,6 +178,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
|||||||
onClickHideDetectedField={onClickHideDetectedField}
|
onClickHideDetectedField={onClickHideDetectedField}
|
||||||
getFieldLinks={getFieldLinks}
|
getFieldLinks={getFieldLinks}
|
||||||
logsSortOrder={logsSortOrder}
|
logsSortOrder={logsSortOrder}
|
||||||
|
forceEscape={forceEscape}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{hasData && !renderAll && (
|
{hasData && !renderAll && (
|
||||||
|
|||||||
@@ -361,6 +361,9 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
|
|||||||
const message: string = typeof messageValue === 'string' ? messageValue : JSON.stringify(messageValue);
|
const message: string = typeof messageValue === 'string' ? messageValue : JSON.stringify(messageValue);
|
||||||
|
|
||||||
const hasAnsi = textUtil.hasAnsiCodes(message);
|
const hasAnsi = textUtil.hasAnsiCodes(message);
|
||||||
|
|
||||||
|
const hasUnescapedContent = !!message.match(/\\n|\\t|\\r/);
|
||||||
|
|
||||||
const searchWords = series.meta && series.meta.searchWords ? series.meta.searchWords : [];
|
const searchWords = series.meta && series.meta.searchWords ? series.meta.searchWords : [];
|
||||||
|
|
||||||
let logLevel = LogLevel.unknown;
|
let logLevel = LogLevel.unknown;
|
||||||
@@ -383,6 +386,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
|
|||||||
timeUtc: dateTimeFormat(ts, { timeZone: 'utc' }),
|
timeUtc: dateTimeFormat(ts, { timeZone: 'utc' }),
|
||||||
uniqueLabels,
|
uniqueLabels,
|
||||||
hasAnsi,
|
hasAnsi,
|
||||||
|
hasUnescapedContent,
|
||||||
searchWords,
|
searchWords,
|
||||||
entry: hasAnsi ? ansicolor.strip(message) : message,
|
entry: hasAnsi ? ansicolor.strip(message) : message,
|
||||||
raw: message,
|
raw: message,
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ const makeLog = (overrides: Partial<LogRowModel>): LogRowModel => {
|
|||||||
logLevel: LogLevel.debug,
|
logLevel: LogLevel.debug,
|
||||||
entry,
|
entry,
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
raw: entry,
|
raw: entry,
|
||||||
timeFromNow: '',
|
timeFromNow: '',
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import {
|
|||||||
InlineSwitch,
|
InlineSwitch,
|
||||||
withTheme,
|
withTheme,
|
||||||
stylesFactory,
|
stylesFactory,
|
||||||
|
Icon,
|
||||||
|
Tooltip,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { ExploreGraphPanel } from './ExploreGraphPanel';
|
import { ExploreGraphPanel } from './ExploreGraphPanel';
|
||||||
@@ -89,6 +91,8 @@ interface State {
|
|||||||
logsSortOrder: LogsSortOrder | null;
|
logsSortOrder: LogsSortOrder | null;
|
||||||
isFlipping: boolean;
|
isFlipping: boolean;
|
||||||
showDetectedFields: string[];
|
showDetectedFields: string[];
|
||||||
|
hasUnescapedContent: boolean;
|
||||||
|
forceEscape: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnthemedLogs extends PureComponent<Props, State> {
|
export class UnthemedLogs extends PureComponent<Props, State> {
|
||||||
@@ -102,6 +106,8 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
|||||||
logsSortOrder: null,
|
logsSortOrder: null,
|
||||||
isFlipping: false,
|
isFlipping: false,
|
||||||
showDetectedFields: [],
|
showDetectedFields: [],
|
||||||
|
hasUnescapedContent: this.props.logRows.some((r) => r.hasUnescapedContent),
|
||||||
|
forceEscape: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -123,6 +129,12 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
|||||||
this.cancelFlippingTimer = setTimeout(() => this.setState({ isFlipping: false }), 1000);
|
this.cancelFlippingTimer = setTimeout(() => this.setState({ isFlipping: false }), 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onEscapeNewlines = () => {
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
forceEscape: !prevState.forceEscape,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
onChangeDedup = (dedup: LogsDedupStrategy) => {
|
onChangeDedup = (dedup: LogsDedupStrategy) => {
|
||||||
const { onDedupStrategyChange } = this.props;
|
const { onDedupStrategyChange } = this.props;
|
||||||
if (this.props.dedupStrategy === dedup) {
|
if (this.props.dedupStrategy === dedup) {
|
||||||
@@ -237,7 +249,16 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
|||||||
theme,
|
theme,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { showLabels, showTime, wrapLogMessage, logsSortOrder, isFlipping, showDetectedFields } = this.state;
|
const {
|
||||||
|
showLabels,
|
||||||
|
showTime,
|
||||||
|
wrapLogMessage,
|
||||||
|
logsSortOrder,
|
||||||
|
isFlipping,
|
||||||
|
showDetectedFields,
|
||||||
|
hasUnescapedContent,
|
||||||
|
forceEscape,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const hasData = logRows && logRows.length > 0;
|
const hasData = logRows && logRows.length > 0;
|
||||||
const dedupCount = dedupedRows
|
const dedupCount = dedupedRows
|
||||||
@@ -346,6 +367,27 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hasUnescapedContent && (
|
||||||
|
<MetaInfoText
|
||||||
|
metaItems={[
|
||||||
|
{
|
||||||
|
label: 'Your logs might have incorrectly escaped content',
|
||||||
|
value: (
|
||||||
|
<Tooltip
|
||||||
|
content="We suggest to try to fix the escaping of your log lines first. This is an experimental feature, your logs might not be correctly escaped."
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<Button variant="secondary" size="sm" onClick={this.onEscapeNewlines}>
|
||||||
|
<span>{forceEscape ? 'Remove escaping' : 'Escape newlines'} </span>
|
||||||
|
<Icon name="exclamation-triangle" className="muted" size="sm" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<LogRows
|
<LogRows
|
||||||
logRows={logRows}
|
logRows={logRows}
|
||||||
deduplicatedRows={dedupedRows}
|
deduplicatedRows={dedupedRows}
|
||||||
@@ -357,6 +399,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
|||||||
showContextToggle={showContextToggle}
|
showContextToggle={showContextToggle}
|
||||||
showLabels={showLabels}
|
showLabels={showLabels}
|
||||||
showTime={showTime}
|
showTime={showTime}
|
||||||
|
forceEscape={forceEscape}
|
||||||
wrapLogMessage={wrapLogMessage}
|
wrapLogMessage={wrapLogMessage}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
getFieldLinks={getFieldLinks}
|
getFieldLinks={getFieldLinks}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
|
|
||||||
export interface MetaItemProps {
|
export interface MetaItemProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
value: string;
|
value: string | JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MetaInfoItem = memo(function MetaInfoItem(props: MetaItemProps) {
|
export const MetaInfoItem = memo(function MetaInfoItem(props: MetaItemProps) {
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ describe('decorateWithLogsResult', () => {
|
|||||||
entry: 'this is a message',
|
entry: 'this is a message',
|
||||||
entryFieldIndex: 3,
|
entryFieldIndex: 3,
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
logLevel: 'unknown',
|
logLevel: 'unknown',
|
||||||
raw: 'this is a message',
|
raw: 'this is a message',
|
||||||
@@ -313,6 +314,7 @@ describe('decorateWithLogsResult', () => {
|
|||||||
entry: 'third',
|
entry: 'third',
|
||||||
entryFieldIndex: 3,
|
entryFieldIndex: 3,
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
logLevel: 'unknown',
|
logLevel: 'unknown',
|
||||||
raw: 'third',
|
raw: 'third',
|
||||||
@@ -331,6 +333,7 @@ describe('decorateWithLogsResult', () => {
|
|||||||
entry: 'second message',
|
entry: 'second message',
|
||||||
entryFieldIndex: 3,
|
entryFieldIndex: 3,
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
|
hasUnescapedContent: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
logLevel: 'unknown',
|
logLevel: 'unknown',
|
||||||
raw: 'second message',
|
raw: 'second message',
|
||||||
|
|||||||
Reference in New Issue
Block a user