mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Logs: Add log line to content outline when clicking on datalinks (#90207)
* feat: add bg color to pinned logs, pin logs when opening datalinks
This commit is contained in:
parent
2d35b11323
commit
1367d5d721
@ -0,0 +1,44 @@
|
||||
import { LogLevel } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
|
||||
export function contentOutlineTrackPinAdded() {
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-added',
|
||||
});
|
||||
}
|
||||
|
||||
export function contentOutlineTrackPinRemoved() {
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-deleted',
|
||||
});
|
||||
}
|
||||
|
||||
export function contentOutlineTrackPinLimitReached() {
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-limit-reached',
|
||||
});
|
||||
}
|
||||
|
||||
export function contentOutlineTrackPinClicked() {
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-clicked',
|
||||
});
|
||||
}
|
||||
|
||||
export function contentOutlineTrackUnpinClicked() {
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-deleted',
|
||||
});
|
||||
}
|
||||
|
||||
export function contentOutlineTrackLevelFilter(level: { levelStr: string; logLevel: LogLevel }) {
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: `Logs:filter:${level.levelStr}`,
|
||||
});
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { uniqueId } from 'lodash';
|
||||
import { useState, useContext, createContext, ReactNode, useCallback, useRef, useEffect } from 'react';
|
||||
import { SetOptional } from 'type-fest';
|
||||
|
||||
import { ContentOutlineItemBaseProps, ITEM_TYPES } from './ContentOutlineItem';
|
||||
|
||||
@ -10,7 +11,7 @@ export interface ContentOutlineItemContextProps extends ContentOutlineItemBasePr
|
||||
children?: ContentOutlineItemContextProps[];
|
||||
}
|
||||
|
||||
type RegisterFunction = (outlineItem: Omit<ContentOutlineItemContextProps, 'id'>) => string;
|
||||
type RegisterFunction = (outlineItem: SetOptional<ContentOutlineItemContextProps, 'id'>) => string;
|
||||
|
||||
export interface ContentOutlineContextProps {
|
||||
outlineItems: ContentOutlineItemContextProps[];
|
||||
@ -44,7 +45,10 @@ export function ContentOutlineContextProvider({ children, refreshDependencies }:
|
||||
const parentlessItemsRef = useRef<ParentlessItems>({});
|
||||
|
||||
const register: RegisterFunction = useCallback((outlineItem) => {
|
||||
const id = uniqueId(`${outlineItem.panelId}-${outlineItem.title}-${outlineItem.icon}_`);
|
||||
// Allow the caller to define unique ID so the outlineItem can be differentiated
|
||||
const id = outlineItem.id
|
||||
? outlineItem.id
|
||||
: uniqueId(`${outlineItem.panelId}-${outlineItem.title}-${outlineItem.icon}_`);
|
||||
|
||||
setOutlineItems((prevItems) => {
|
||||
if (outlineItem.level === 'root') {
|
||||
|
@ -61,6 +61,14 @@ import { getLogLevel, getLogLevelFromKey, getLogLevelInfo } from 'app/features/l
|
||||
import { getState } from 'app/store/store';
|
||||
import { ExploreItemState, useDispatch } from 'app/types';
|
||||
|
||||
import {
|
||||
contentOutlineTrackLevelFilter,
|
||||
contentOutlineTrackPinAdded,
|
||||
contentOutlineTrackPinClicked,
|
||||
contentOutlineTrackPinLimitReached,
|
||||
contentOutlineTrackPinRemoved,
|
||||
contentOutlineTrackUnpinClicked,
|
||||
} from '../ContentOutline/ContentOutlineAnalyticEvents';
|
||||
import { useContentOutlineContext } from '../ContentOutline/ContentOutlineContext';
|
||||
import { getUrlStateFromPaneState } from '../hooks/useStateSync';
|
||||
import { changePanelState } from '../state/explorePane';
|
||||
@ -145,7 +153,10 @@ const getDefaultVisualisationType = (): LogsVisualisationType => {
|
||||
return 'logs';
|
||||
};
|
||||
|
||||
const PINNED_LOGS_LIMIT = 3;
|
||||
const PINNED_LOGS_LIMIT = 10;
|
||||
const PINNED_LOGS_TITLE = 'Pinned log';
|
||||
const PINNED_LOGS_MESSAGE = 'Pin to content outline';
|
||||
const PINNED_LOGS_PANELID = 'Logs';
|
||||
|
||||
const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const {
|
||||
@ -194,7 +205,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const [forceEscape, setForceEscape] = useState<boolean>(false);
|
||||
const [contextOpen, setContextOpen] = useState<boolean>(false);
|
||||
const [contextRow, setContextRow] = useState<LogRowModel | undefined>(undefined);
|
||||
const [pinLineButtonTooltipTitle, setPinLineButtonTooltipTitle] = useState<PopoverContent>('Pin to content outline');
|
||||
const [pinLineButtonTooltipTitle, setPinLineButtonTooltipTitle] = useState<PopoverContent>(PINNED_LOGS_MESSAGE);
|
||||
const [visualisationType, setVisualisationType] = useState<LogsVisualisationType | undefined>(
|
||||
panelState?.logs?.visualisationType ?? getDefaultVisualisationType()
|
||||
);
|
||||
@ -215,6 +226,17 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const hasData = logRows && logRows.length > 0;
|
||||
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
|
||||
|
||||
// Get pinned log lines
|
||||
const logsParent = outlineItems?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
|
||||
const pinnedLogs = logsParent?.children
|
||||
?.filter((outlines) => outlines.title === PINNED_LOGS_TITLE)
|
||||
.map((pinnedLogs) => pinnedLogs.id);
|
||||
|
||||
const getPinnedLogsCount = useCallback(() => {
|
||||
const logsParent = outlineItems?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
|
||||
return logsParent?.children?.filter((child) => child.title === PINNED_LOGS_TITLE).length ?? 0;
|
||||
}, [outlineItems]);
|
||||
|
||||
const registerLogLevelsWithContentOutline = useCallback(() => {
|
||||
const levelsArr = Object.keys(LogLevelColor);
|
||||
const logVolumeDataFrames = new Set(logsVolumeData?.data);
|
||||
@ -228,7 +250,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
// clean up all current log levels
|
||||
if (unregisterAllChildren) {
|
||||
unregisterAllChildren((items) => {
|
||||
const logsParent = items?.find((item) => item.panelId === 'Logs' && item.level === 'root');
|
||||
const logsParent = items?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
|
||||
return logsParent?.id;
|
||||
}, 'filter');
|
||||
}
|
||||
@ -256,16 +278,13 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
register({
|
||||
title: level.levelStr,
|
||||
icon: 'gf-logs',
|
||||
panelId: 'Logs',
|
||||
panelId: PINNED_LOGS_PANELID,
|
||||
level: 'child',
|
||||
type: 'filter',
|
||||
highlight: currentLevelSelected && !allLevelsSelected,
|
||||
onClick: (e: React.MouseEvent) => {
|
||||
toggleLegendRef.current?.(level.levelStr, mapMouseEventToMode(e));
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: `Logs:filter:${level.levelStr}`,
|
||||
});
|
||||
contentOutlineTrackLevelFilter(level);
|
||||
},
|
||||
ref: null,
|
||||
color: LogLevelColor[level.logLevel],
|
||||
@ -275,6 +294,21 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
}
|
||||
}, [logsVolumeData?.data, unregisterAllChildren, logsVolumeEnabled, hiddenLogLevels, register, toggleLegendRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getPinnedLogsCount() === PINNED_LOGS_LIMIT) {
|
||||
setPinLineButtonTooltipTitle(
|
||||
<span style={{ display: 'flex', textAlign: 'center' }}>
|
||||
❗️
|
||||
<Trans i18nKey="explore.logs.maximum-pinned-logs">
|
||||
Maximum of {{ PINNED_LOGS_LIMIT }} pinned logs reached. Unpin a log to add another.
|
||||
</Trans>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
setPinLineButtonTooltipTitle(PINNED_LOGS_MESSAGE);
|
||||
}
|
||||
}, [outlineItems, getPinnedLogsCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading && !previousLoading && panelState?.logs?.id) {
|
||||
// loading stopped, so we need to remove any permalinked log lines
|
||||
@ -652,70 +686,47 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
topLogsRef.current?.scrollIntoView();
|
||||
}, [logsContainerRef, topLogsRef]);
|
||||
|
||||
const onPinToContentOutlineClick = (row: LogRowModel) => {
|
||||
if (getPinnedLogsCount() === PINNED_LOGS_LIMIT) {
|
||||
setPinLineButtonTooltipTitle(
|
||||
<span style={{ display: 'flex', textAlign: 'center' }}>
|
||||
❗️
|
||||
<Trans i18nKey="explore.logs.maximum-pinned-logs">
|
||||
Maximum of {{ PINNED_LOGS_LIMIT }} pinned logs reached. Unpin a log to add another.
|
||||
</Trans>
|
||||
</span>
|
||||
);
|
||||
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-limit-reached',
|
||||
});
|
||||
const onPinToContentOutlineClick = (row: LogRowModel, allowUnPin = true) => {
|
||||
if (getPinnedLogsCount() === PINNED_LOGS_LIMIT && !allowUnPin) {
|
||||
contentOutlineTrackPinLimitReached();
|
||||
return;
|
||||
}
|
||||
|
||||
// find the Logs parent item
|
||||
const logsParent = outlineItems?.find((item) => item.panelId === 'Logs' && item.level === 'root');
|
||||
const logsParent = outlineItems?.find((item) => item.panelId === PINNED_LOGS_PANELID && item.level === 'root');
|
||||
|
||||
//update the parent's expanded state
|
||||
if (logsParent && updateItem) {
|
||||
updateItem(logsParent.id, { expanded: true });
|
||||
}
|
||||
|
||||
register?.({
|
||||
icon: 'gf-logs',
|
||||
title: 'Pinned log',
|
||||
panelId: 'Logs',
|
||||
level: 'child',
|
||||
ref: null,
|
||||
color: LogLevelColor[row.logLevel],
|
||||
childOnTop: true,
|
||||
onClick: () => {
|
||||
onOpenContext(row, () => {});
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-clicked',
|
||||
});
|
||||
},
|
||||
onRemove: (id: string) => {
|
||||
unregister?.(id);
|
||||
if (getPinnedLogsCount() < PINNED_LOGS_LIMIT) {
|
||||
setPinLineButtonTooltipTitle('Pin to content outline');
|
||||
}
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-deleted',
|
||||
});
|
||||
},
|
||||
});
|
||||
const alreadyPinned = pinnedLogs?.find((pin) => pin === row.rowId);
|
||||
if (alreadyPinned && row.rowId && allowUnPin) {
|
||||
unregister?.(row.rowId);
|
||||
contentOutlineTrackPinRemoved();
|
||||
} else if (getPinnedLogsCount() !== PINNED_LOGS_LIMIT && !alreadyPinned) {
|
||||
register?.({
|
||||
id: row.rowId,
|
||||
icon: 'gf-logs',
|
||||
title: PINNED_LOGS_TITLE,
|
||||
panelId: PINNED_LOGS_PANELID,
|
||||
level: 'child',
|
||||
ref: null,
|
||||
color: LogLevelColor[row.logLevel],
|
||||
childOnTop: true,
|
||||
onClick: () => {
|
||||
onOpenContext(row, () => {});
|
||||
contentOutlineTrackPinClicked();
|
||||
},
|
||||
onRemove: (id: string) => {
|
||||
unregister?.(id);
|
||||
contentOutlineTrackUnpinClicked();
|
||||
},
|
||||
});
|
||||
contentOutlineTrackPinAdded();
|
||||
}
|
||||
|
||||
props.onPinLineCallback?.();
|
||||
|
||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||
item: 'section',
|
||||
type: 'Logs:pinned:pinned-log-added',
|
||||
});
|
||||
};
|
||||
|
||||
const getPinnedLogsCount = () => {
|
||||
const logsParent = outlineItems?.find((item) => item.panelId === 'Logs' && item.level === 'root');
|
||||
return logsParent?.children?.filter((child) => child.title === 'Pinned log').length ?? 0;
|
||||
};
|
||||
|
||||
const hasUnescapedContent = checkUnescapedContent(logRows);
|
||||
@ -929,6 +940,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
sortOrder={logsSortOrder}
|
||||
>
|
||||
<LogRows
|
||||
pinnedLogs={pinnedLogs}
|
||||
logRows={logRows}
|
||||
deduplicatedRows={dedupedRows}
|
||||
dedupStrategy={dedupStrategy}
|
||||
@ -958,6 +970,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
containerRendered={!!logsContainerRef}
|
||||
onClickFilterString={props.onClickFilterString}
|
||||
onClickFilterOutString={props.onClickFilterOutString}
|
||||
onUnpinLine={onPinToContentOutlineClick}
|
||||
onPinLine={onPinToContentOutlineClick}
|
||||
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
||||
/>
|
||||
|
@ -2,7 +2,7 @@ import { cx } from '@emotion/css';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { CoreApp, DataFrame, DataFrameType, Field, LinkModel, LogRowModel } from '@grafana/data';
|
||||
import { Themeable2, withTheme2 } from '@grafana/ui';
|
||||
import { PopoverContent, Themeable2, withTheme2 } from '@grafana/ui';
|
||||
|
||||
import { calculateLogsLabelStats, calculateStats } from '../utils';
|
||||
|
||||
@ -27,6 +27,9 @@ export interface Props extends Themeable2 {
|
||||
onClickShowField?: (key: string) => void;
|
||||
onClickHideField?: (key: string) => void;
|
||||
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
||||
|
||||
onPinLine?: (row: LogRowModel) => void;
|
||||
pinLineButtonTooltipTitle?: PopoverContent;
|
||||
}
|
||||
|
||||
class UnThemedLogDetails extends PureComponent<Props> {
|
||||
@ -46,7 +49,9 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
displayedFields,
|
||||
getFieldLinks,
|
||||
wrapLogMessage,
|
||||
onPinLine,
|
||||
styles,
|
||||
pinLineButtonTooltipTitle,
|
||||
} = this.props;
|
||||
const levelStyles = getLogLevelStyles(theme, row.logLevel);
|
||||
const labels = row.labels ? row.labels : {};
|
||||
@ -151,6 +156,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
links={links}
|
||||
onClickShowField={onClickShowField}
|
||||
onClickHideField={onClickHideField}
|
||||
onPinLine={onPinLine}
|
||||
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
||||
getStats={() => calculateStats(row.dataFrame.fields[fieldIndex].values)}
|
||||
displayedFields={displayedFields}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
@ -170,6 +177,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
links={links}
|
||||
onClickShowField={onClickShowField}
|
||||
onClickHideField={onClickHideField}
|
||||
onPinLine={onPinLine}
|
||||
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
||||
getStats={() => calculateStats(row.dataFrame.fields[fieldIndex].values)}
|
||||
displayedFields={displayedFields}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
import { CoreApp } from '@grafana/data';
|
||||
import { CoreApp, FieldType, LinkModel } from '@grafana/data';
|
||||
import { Field } from '@grafana/data/';
|
||||
|
||||
import { LogDetailsRow } from './LogDetailsRow';
|
||||
import { createLogRow } from './__mocks__/logRow';
|
||||
@ -147,4 +148,34 @@ describe('LogDetailsRow', () => {
|
||||
// Asserting visibility on mouse-over is currently not possible.
|
||||
});
|
||||
});
|
||||
|
||||
describe('datalinks', () => {
|
||||
it('datalinks should pin and call the original link click', () => {
|
||||
const onLinkClick = jest.fn();
|
||||
const onPinLine = jest.fn();
|
||||
const links: Array<LinkModel<Field>> = [
|
||||
{
|
||||
onClick: onLinkClick,
|
||||
href: '#',
|
||||
title: 'Hello link',
|
||||
target: '_self',
|
||||
origin: {
|
||||
name: 'name',
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: ['string'],
|
||||
},
|
||||
},
|
||||
];
|
||||
setup({ links, onPinLine });
|
||||
|
||||
expect(onLinkClick).not.toHaveBeenCalled();
|
||||
expect(onPinLine).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Hello link' }));
|
||||
|
||||
expect(onLinkClick).toHaveBeenCalled();
|
||||
expect(onPinLine).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
LogRowModel,
|
||||
} from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { ClipboardButton, DataLinkButton, IconButton, Themeable2, withTheme2 } from '@grafana/ui';
|
||||
import { ClipboardButton, DataLinkButton, IconButton, PopoverContent, Themeable2, withTheme2 } from '@grafana/ui';
|
||||
|
||||
import { logRowToSingleRowDataFrame } from '../logsModel';
|
||||
|
||||
@ -38,6 +38,8 @@ export interface Props extends Themeable2 {
|
||||
row: LogRowModel;
|
||||
app?: CoreApp;
|
||||
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
||||
onPinLine?: (row: LogRowModel, allowUnPin?: boolean) => void;
|
||||
pinLineButtonTooltipTitle?: PopoverContent;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -263,6 +265,8 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
disableActions,
|
||||
row,
|
||||
app,
|
||||
onPinLine,
|
||||
pinLineButtonTooltipTitle,
|
||||
} = this.props;
|
||||
const { showFieldsStats, fieldStats, fieldCount } = this.state;
|
||||
const styles = getStyles(theme);
|
||||
@ -324,11 +328,32 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
{singleVal ? parsedValues[0] : this.generateMultiVal(parsedValues, true)}
|
||||
{singleVal && this.generateClipboardButton(parsedValues[0])}
|
||||
<div className={cx((singleVal || isMultiParsedValueWithNoContent) && styles.adjoiningLinkButton)}>
|
||||
{links?.map((link, i) => (
|
||||
<span key={`${link.title}-${i}`}>
|
||||
<DataLinkButton link={link} />
|
||||
</span>
|
||||
))}
|
||||
{links?.map((link, i) => {
|
||||
if (link.onClick && onPinLine) {
|
||||
const originalOnClick = link.onClick;
|
||||
link.onClick = (e, origin) => {
|
||||
// Pin the line
|
||||
onPinLine(row, false);
|
||||
|
||||
// Execute the link onClick function
|
||||
originalOnClick(e, origin);
|
||||
};
|
||||
}
|
||||
return (
|
||||
<span key={`${link.title}-${i}`}>
|
||||
<DataLinkButton
|
||||
buttonProps={{
|
||||
// Show tooltip message if max number of pinned lines has been reached
|
||||
tooltip:
|
||||
typeof pinLineButtonTooltipTitle === 'object' && link.onClick
|
||||
? pinLineButtonTooltipTitle
|
||||
: undefined,
|
||||
}}
|
||||
link={link}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -1,24 +1,24 @@
|
||||
import { cx } from '@emotion/css';
|
||||
import { debounce } from 'lodash';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { PureComponent, MouseEvent } from 'react';
|
||||
import * as React from 'react';
|
||||
import { MouseEvent, PureComponent } from 'react';
|
||||
|
||||
import {
|
||||
Field,
|
||||
LinkModel,
|
||||
LogRowModel,
|
||||
LogsSortOrder,
|
||||
dateTimeFormat,
|
||||
CoreApp,
|
||||
DataFrame,
|
||||
dateTimeFormat,
|
||||
Field,
|
||||
LinkModel,
|
||||
LogRowContextOptions,
|
||||
LogRowModel,
|
||||
LogsSortOrder,
|
||||
} from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery, TimeZone } from '@grafana/schema';
|
||||
import { withTheme2, Themeable2, Icon, Tooltip, PopoverContent } from '@grafana/ui';
|
||||
import { Icon, PopoverContent, Themeable2, Tooltip, withTheme2 } from '@grafana/ui';
|
||||
|
||||
import { checkLogsError, escapeUnescapedString, checkLogsSampled } from '../utils';
|
||||
import { checkLogsError, checkLogsSampled, escapeUnescapedString } from '../utils';
|
||||
|
||||
import { LogDetails } from './LogDetails';
|
||||
import { LogLabels } from './LogLabels';
|
||||
@ -59,7 +59,7 @@ interface Props extends Themeable2 {
|
||||
permalinkedRowId?: string;
|
||||
scrollIntoView?: (element: HTMLElement) => void;
|
||||
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
||||
onPinLine?: (row: LogRowModel) => void;
|
||||
onPinLine?: (row: LogRowModel, allowUnPin?: boolean) => void;
|
||||
onUnpinLine?: (row: LogRowModel) => void;
|
||||
pinLineButtonTooltipTitle?: PopoverContent;
|
||||
pinned?: boolean;
|
||||
@ -219,14 +219,16 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
app,
|
||||
styles,
|
||||
getRowContextQuery,
|
||||
pinned,
|
||||
} = this.props;
|
||||
|
||||
const { showDetails, showingContext, permalinked } = this.state;
|
||||
const levelStyles = getLogLevelStyles(theme, row.logLevel);
|
||||
const { errorMessage, hasError } = checkLogsError(row);
|
||||
const { sampleMessage, isSampled } = checkLogsSampled(row);
|
||||
const logRowBackground = cx(styles.logsRow, {
|
||||
[styles.errorLogRow]: hasError,
|
||||
[styles.highlightBackground]: showingContext || permalinked,
|
||||
[styles.highlightBackground]: showingContext || permalinked || pinned,
|
||||
});
|
||||
const logRowDetailsBackground = cx(styles.logsRow, {
|
||||
[styles.errorLogRow]: hasError,
|
||||
@ -327,6 +329,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
</tr>
|
||||
{this.state.showDetails && (
|
||||
<LogDetails
|
||||
onPinLine={this.props.onPinLine}
|
||||
className={logRowDetailsBackground}
|
||||
showDuplicates={showDuplicates}
|
||||
getFieldLinks={getFieldLinks}
|
||||
@ -342,6 +345,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
app={app}
|
||||
styles={styles}
|
||||
isFilterLabelActive={this.props.isFilterLabelActive}
|
||||
pinLineButtonTooltipTitle={this.props.pinLineButtonTooltipTitle}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -48,7 +48,7 @@ export interface Props extends Themeable2 {
|
||||
getFieldLinks?: (field: Field, rowIndex: number, dataFrame: DataFrame) => Array<LinkModel<Field>>;
|
||||
onClickShowField?: (key: string) => void;
|
||||
onClickHideField?: (key: string) => void;
|
||||
onPinLine?: (row: LogRowModel) => void;
|
||||
onPinLine?: (row: LogRowModel, allowUnPin?: boolean) => void;
|
||||
onUnpinLine?: (row: LogRowModel) => void;
|
||||
pinLineButtonTooltipTitle?: PopoverContent;
|
||||
onLogRowHover?: (row?: LogRowModel) => void;
|
||||
@ -63,6 +63,7 @@ export interface Props extends Themeable2 {
|
||||
scrollIntoView?: (element: HTMLElement) => void;
|
||||
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
||||
pinnedRowId?: string;
|
||||
pinnedLogs?: string[];
|
||||
containerRendered?: boolean;
|
||||
/**
|
||||
* If false or undefined, the `contain:strict` css property will be added to the wrapping `<table>` for performance reasons.
|
||||
@ -191,7 +192,8 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
);
|
||||
|
||||
render() {
|
||||
const { deduplicatedRows, logRows, dedupStrategy, theme, logsSortOrder, previewLimit, ...rest } = this.props;
|
||||
const { deduplicatedRows, logRows, dedupStrategy, theme, logsSortOrder, previewLimit, pinnedLogs, ...rest } =
|
||||
this.props;
|
||||
const { renderAll } = this.state;
|
||||
const styles = getLogRowStyles(theme);
|
||||
const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows;
|
||||
@ -241,7 +243,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
onPinLine={this.props.onPinLine}
|
||||
onUnpinLine={this.props.onUnpinLine}
|
||||
pinLineButtonTooltipTitle={this.props.pinLineButtonTooltipTitle}
|
||||
pinned={this.props.pinnedRowId === row.uid}
|
||||
pinned={this.props.pinnedRowId === row.uid || pinnedLogs?.some((logId) => logId === row.rowId)}
|
||||
isFilterLabelActive={this.props.isFilterLabelActive}
|
||||
handleTextSelection={this.popoverMenuSupported() ? this.handleSelection : undefined}
|
||||
{...rest}
|
||||
@ -264,7 +266,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
|
||||
onPinLine={this.props.onPinLine}
|
||||
onUnpinLine={this.props.onUnpinLine}
|
||||
pinLineButtonTooltipTitle={this.props.pinLineButtonTooltipTitle}
|
||||
pinned={this.props.pinnedRowId === row.uid}
|
||||
pinned={this.props.pinnedRowId === row.uid || pinnedLogs?.some((logId) => logId === row.rowId)}
|
||||
isFilterLabelActive={this.props.isFilterLabelActive}
|
||||
handleTextSelection={this.popoverMenuSupported() ? this.handleSelection : undefined}
|
||||
{...rest}
|
||||
|
Loading…
Reference in New Issue
Block a user