chore: wip
This commit is contained in:
Generated
+1
@@ -11,6 +11,7 @@
|
||||
export const pluginVersion = "12.4.0-pre";
|
||||
|
||||
export interface Options {
|
||||
buildLinkToLogLine?: unknown;
|
||||
controlsStorageKey?: string;
|
||||
/**
|
||||
* isFilterLabelActive?: _
|
||||
|
||||
@@ -28,6 +28,11 @@ interface Props extends CustomCellRendererProps {
|
||||
index?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param props
|
||||
* @constructor
|
||||
*/
|
||||
export function LogsTableActionButtons(props: Props) {
|
||||
const { exploreId, absoluteRange, logRows, rowIndex, panelState, displayedFields, logsFrame, frame } = props;
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { CustomCellRendererProps, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { LogsFrame } from '../../../features/logs/logsFrame';
|
||||
|
||||
import { LogsNGTableRowActionButtons } from './LogsNGTableRowActionButtons';
|
||||
import { BuildLinkToLogLine } from './types';
|
||||
|
||||
export function LogsTableCustomCellRenderer(props: {
|
||||
cellProps: CustomCellRendererProps;
|
||||
logsFrame: LogsFrame;
|
||||
buildLinkToLog?: BuildLinkToLogLine;
|
||||
}) {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<>
|
||||
<LogsNGTableRowActionButtons
|
||||
{...props.cellProps}
|
||||
buildLinkToLog={props.buildLinkToLog ?? buildLinkToLog}
|
||||
logsFrame={props.logsFrame}
|
||||
/>
|
||||
<span className={styles.firstColumnCell}>
|
||||
{props.cellProps.field.display?.(props.cellProps.value).text ?? String(props.cellProps.value)}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const buildLinkToLog: BuildLinkToLogLine = (logsFrame, rowIndex, field) => {
|
||||
return '@todo';
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
firstColumnCell: css({
|
||||
paddingLeft: theme.spacing(7),
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { memoize } from 'lodash';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { ClipboardButton, CustomCellRendererProps, IconButton, Modal, useTheme2 } from '@grafana/ui';
|
||||
import { LogsFrame } from 'app/features/logs/logsFrame';
|
||||
|
||||
import { BuildLinkToLogLine } from './types';
|
||||
|
||||
interface Props extends CustomCellRendererProps {
|
||||
logsFrame: LogsFrame;
|
||||
buildLinkToLog?: BuildLinkToLogLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs row actions buttons
|
||||
* @todo use new inspector and default to code mode
|
||||
* @param props
|
||||
* @constructor
|
||||
*/
|
||||
export function LogsNGTableRowActionButtons(props: Props) {
|
||||
const { rowIndex, logsFrame, field, frame, buildLinkToLog } = props;
|
||||
const theme = useTheme2();
|
||||
const [isInspecting, setIsInspecting] = useState(false);
|
||||
const styles = getStyles(theme);
|
||||
|
||||
const handleViewClick = () => {
|
||||
setIsInspecting(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<IconButton
|
||||
className={styles.inspectButton}
|
||||
tooltip={t('explore.logs-table.action-buttons.view-log-line', 'View log line')}
|
||||
variant="secondary"
|
||||
aria-label={t('explore.logs-table.action-buttons.view-log-line', 'View log line')}
|
||||
tooltipPlacement="top"
|
||||
size="md"
|
||||
name="eye"
|
||||
onClick={handleViewClick}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
{buildLinkToLog && (
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ClipboardButton
|
||||
className={styles.clipboardButton}
|
||||
icon="share-alt"
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
size="md"
|
||||
tooltip={t('explore.logs-table.action-buttons.copy-link', 'Copy link to log line')}
|
||||
tooltipPlacement="top"
|
||||
tabIndex={0}
|
||||
aria-label={t('explore.logs-table.action-buttons.copy-link', 'Copy link to log line')}
|
||||
getText={() => buildLinkToLog(logsFrame, rowIndex, field)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isInspecting && (
|
||||
<Modal
|
||||
onDismiss={() => setIsInspecting(false)}
|
||||
isOpen={true}
|
||||
title={t('explore.logs-table.action-buttons.inspect-value', 'Inspect value')}
|
||||
>
|
||||
<pre>{getLineValue(logsFrame, frame, rowIndex)}</pre>
|
||||
<Modal.ButtonRow>
|
||||
<ClipboardButton icon="copy" getText={() => getLineValue(logsFrame, frame, rowIndex)}>
|
||||
{t('explore.logs-table.action-buttons.copy-to-clipboard', 'Copy to Clipboard')}
|
||||
</ClipboardButton>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const getLineValue = memoize((logsFrame: LogsFrame, frame: DataFrame, rowIndex: number) => {
|
||||
const bodyFieldName = logsFrame?.bodyField?.name;
|
||||
const bodyField = bodyFieldName
|
||||
? frame.fields.find((field) => field.name === bodyFieldName)
|
||||
: frame.fields.find((field) => field.type === 'string');
|
||||
return bodyField?.values[rowIndex];
|
||||
});
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css({
|
||||
background: theme.colors.background.secondary,
|
||||
boxShadow: theme.shadows.z2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: '100%',
|
||||
left: 0,
|
||||
top: 0,
|
||||
padding: `0 ${theme.spacing(0.5)}`,
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
}),
|
||||
buttonWrapper: css({
|
||||
height: '100%',
|
||||
'& button svg': {
|
||||
marginRight: 'auto',
|
||||
},
|
||||
'&:hover': {
|
||||
color: theme.colors.text.link,
|
||||
},
|
||||
padding: theme.spacing(0, 1),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
inspectButton: css({
|
||||
borderRadius: theme.shape.radius.default,
|
||||
display: 'inline-flex',
|
||||
margin: 0,
|
||||
overflow: 'hidden',
|
||||
verticalAlign: 'middle',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
clipboardButton: css({
|
||||
height: 30,
|
||||
lineHeight: '1',
|
||||
padding: 0,
|
||||
width: '20px',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
});
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
useDataLinksContext,
|
||||
} from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { CustomCellRendererProps, TableCellDisplayMode, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { config } from '../../../core/config';
|
||||
import { getLogsExtractFields } from '../../../features/explore/Logs/LogsTable';
|
||||
@@ -29,20 +29,17 @@ import { isSetDisplayedFields } from '../logs/types';
|
||||
import { TablePanel } from '../table/TablePanel';
|
||||
import type { Options as TableOptions } from '../table/panelcfg.gen';
|
||||
|
||||
import { LogsTableCustomCellRenderer } from './CustomCellRenderer';
|
||||
import type { Options as LogsTableOptions } from './panelcfg.gen';
|
||||
import { isOnLogsTableOptionsChange, onLogsTableOptionsChangeType } from './types';
|
||||
import { isBuildLinkToLogLine, isOnLogsTableOptionsChange, OnLogsTableOptionsChange } from './types';
|
||||
|
||||
interface LogsTablePanelProps extends PanelProps<LogsTableOptions> {
|
||||
frameIndex?: number;
|
||||
showHeader?: boolean;
|
||||
}
|
||||
|
||||
// keeping alias @todo remove
|
||||
//@ts-expect-error
|
||||
const a: TableOptions = {};
|
||||
console.log('a', a);
|
||||
|
||||
const sidebarWidth = 200;
|
||||
// Defaults
|
||||
const DEFAULT_SIDEBAR_WIDTH = 200;
|
||||
|
||||
export const LogsTable = ({
|
||||
data,
|
||||
@@ -53,7 +50,7 @@ export const LogsTable = ({
|
||||
options,
|
||||
eventBus,
|
||||
frameIndex = 0,
|
||||
showHeader = true,
|
||||
showHeader = true, // @todo not pulling from panel settings
|
||||
onOptionsChange,
|
||||
onFieldConfigChange,
|
||||
replaceVariables,
|
||||
@@ -65,10 +62,10 @@ export const LogsTable = ({
|
||||
renderCounter,
|
||||
}: LogsTablePanelProps) => {
|
||||
// Variables
|
||||
const dataFrame = data.series[frameIndex];
|
||||
const unTransformedDataFrame = data.series[frameIndex];
|
||||
|
||||
// Hooks
|
||||
const logsFrame: LogsFrame | null = useMemo(() => parseLogsFrame(dataFrame), [dataFrame]);
|
||||
const logsFrame: LogsFrame | null = useMemo(() => parseLogsFrame(unTransformedDataFrame), [unTransformedDataFrame]);
|
||||
const defaultDisplayedFields = useMemo(
|
||||
() => [
|
||||
logsFrame?.timeField.name ?? LOGS_DATAPLANE_TIMESTAMP_NAME,
|
||||
@@ -81,12 +78,12 @@ export const LogsTable = ({
|
||||
const [extractedFrame, setExtractedFrame] = useState<DataFrame[] | null>(null);
|
||||
const [organizedFrame, setOrganizedFrame] = useState<DataFrame[] | null>(null);
|
||||
const [displayedFields, setDisplayedFields] = useState<string[]>(options.displayedFields ?? defaultDisplayedFields);
|
||||
const styles = useStyles2(getStyles, sidebarWidth, height, width);
|
||||
const styles = useStyles2(getStyles, DEFAULT_SIDEBAR_WIDTH, height, width);
|
||||
const dataLinksContext = useDataLinksContext();
|
||||
const unTransformedDataFrame = data.series[frameIndex];
|
||||
const dataLinkPostProcessor = dataLinksContext.dataLinkPostProcessor;
|
||||
|
||||
// Methods
|
||||
const onLogsTableOptionsChange: onLogsTableOptionsChangeType | undefined = isOnLogsTableOptionsChange(onOptionsChange)
|
||||
const onLogsTableOptionsChange: OnLogsTableOptionsChange | undefined = isOnLogsTableOptionsChange(onOptionsChange)
|
||||
? onOptionsChange
|
||||
: undefined;
|
||||
|
||||
@@ -97,7 +94,6 @@ export const LogsTable = ({
|
||||
// Callbacks
|
||||
const onTableOptionsChange = useCallback(
|
||||
(options: TableOptions) => {
|
||||
console.log('onTableOptionsChange', options);
|
||||
onLogsTableOptionsChange?.(options);
|
||||
},
|
||||
[onLogsTableOptionsChange]
|
||||
@@ -135,33 +131,18 @@ export const LogsTable = ({
|
||||
);
|
||||
};
|
||||
|
||||
extractFields().then((frame) => {
|
||||
extractFields().then((data) => {
|
||||
const extractedFrames = applyFieldOverrides({
|
||||
data: frame,
|
||||
fieldConfig: fieldConfig,
|
||||
data,
|
||||
fieldConfig,
|
||||
replaceVariables: replaceVariables ?? getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||
theme: config.theme2,
|
||||
timeZone: timeZone,
|
||||
dataLinkPostProcessor: dataLinksContext.dataLinkPostProcessor,
|
||||
dataLinkPostProcessor,
|
||||
});
|
||||
|
||||
for (let frameIndex = 0; frameIndex < extractedFrames.length; frameIndex++) {
|
||||
const frame = extractedFrames[frameIndex];
|
||||
for (const [, field] of frame.fields.entries()) {
|
||||
field.config = {
|
||||
...field.config,
|
||||
filterable: field.config?.filterable ?? doesFieldSupportAdHocFiltering(field),
|
||||
custom: {
|
||||
...field.config.custom,
|
||||
inspect: field.config?.custom?.inspect ?? true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setExtractedFrame(extractedFrames);
|
||||
});
|
||||
}, [dataLinksContext.dataLinkPostProcessor, fieldConfig, replaceVariables, timeZone, unTransformedDataFrame]);
|
||||
}, [dataLinkPostProcessor, fieldConfig, replaceVariables, timeZone, unTransformedDataFrame]);
|
||||
|
||||
/**
|
||||
* Organize fields transform
|
||||
@@ -181,7 +162,7 @@ export const LogsTable = ({
|
||||
}
|
||||
}
|
||||
|
||||
return await lastValueFrom(
|
||||
const organizedFrame = await lastValueFrom(
|
||||
transformDataFrame(
|
||||
[
|
||||
{
|
||||
@@ -195,6 +176,44 @@ export const LogsTable = ({
|
||||
extractedFrame
|
||||
)
|
||||
);
|
||||
|
||||
if (!logsFrame) {
|
||||
throw new Error('missing logsFrame');
|
||||
}
|
||||
|
||||
for (let frameIndex = 0; frameIndex < organizedFrame.length; frameIndex++) {
|
||||
const frame = organizedFrame[frameIndex];
|
||||
for (const [fieldIndex, field] of frame.fields.entries()) {
|
||||
const isFirstField = fieldIndex === 0;
|
||||
|
||||
field.config = {
|
||||
...field.config,
|
||||
filterable: field.config?.filterable ?? doesFieldSupportAdHocFiltering(field, logsFrame),
|
||||
custom: {
|
||||
...field.config.custom,
|
||||
inspect: field.config?.custom?.inspect ?? doesFieldSupportInspector(field, logsFrame),
|
||||
// @todo add row actions panel option
|
||||
cellOptions:
|
||||
isFirstField && logsFrame
|
||||
? {
|
||||
type: TableCellDisplayMode.Custom,
|
||||
cellComponent: (cellProps: CustomCellRendererProps) => (
|
||||
<LogsTableCustomCellRenderer
|
||||
cellProps={cellProps}
|
||||
logsFrame={logsFrame}
|
||||
buildLinkToLog={
|
||||
isBuildLinkToLogLine(options.buildLinkToLogLine) ? options.buildLinkToLogLine : undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: field.config.custom?.cellOptions,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return organizedFrame;
|
||||
};
|
||||
|
||||
organizeFields(displayedFields).then((frame) => {
|
||||
@@ -202,13 +221,13 @@ export const LogsTable = ({
|
||||
setOrganizedFrame(frame);
|
||||
}
|
||||
});
|
||||
}, [extractedFrame, displayedFields]);
|
||||
}, [extractedFrame, displayedFields, logsFrame, options.buildLinkToLogLine]);
|
||||
|
||||
if (extractedFrame === null || organizedFrame === null || logsFrame === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('render::LogsTable', { extractedFrame });
|
||||
console.log('render::LogsTable', { extractedFrame, organizedFrame });
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
@@ -220,7 +239,7 @@ export const LogsTable = ({
|
||||
logs={[]}
|
||||
reorder={(columns: string[]) => {}}
|
||||
setSidebarWidth={(width) => {}}
|
||||
sidebarWidth={sidebarWidth}
|
||||
sidebarWidth={DEFAULT_SIDEBAR_WIDTH}
|
||||
toggle={(key: string) => {
|
||||
if (displayedFields.includes(key)) {
|
||||
handleSetDisplayedFields(displayedFields.filter((f) => f !== key));
|
||||
@@ -233,7 +252,7 @@ export const LogsTable = ({
|
||||
<div className={styles.tableWrapper}>
|
||||
<TablePanel
|
||||
data={{ ...data, series: organizedFrame }}
|
||||
width={width - sidebarWidth}
|
||||
width={width - DEFAULT_SIDEBAR_WIDTH}
|
||||
height={height}
|
||||
id={id}
|
||||
timeRange={timeRange}
|
||||
@@ -273,8 +292,15 @@ function displayedFieldsToColumns(displayedFields: string[], logsFrame: LogsFram
|
||||
return columns;
|
||||
}
|
||||
|
||||
function doesFieldSupportAdHocFiltering(field: Field): boolean {
|
||||
return true;
|
||||
function doesFieldSupportInspector(field: Field, logsFrame: LogsFrame) {
|
||||
// const unsupportedFields = [logsFrame.bodyField.name]
|
||||
// return !unsupportedFields.includes(field.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
function doesFieldSupportAdHocFiltering(field: Field, logsFrame: LogsFrame): boolean {
|
||||
const unsupportedFields = [logsFrame.timeField.name, logsFrame.bodyField.name];
|
||||
return !unsupportedFields.includes(field.name);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, sidebarWidth: number, height: number, width: number) => {
|
||||
|
||||
@@ -26,40 +26,41 @@ composableKinds: PanelCfg: {
|
||||
version: [0, 0]
|
||||
schema: {
|
||||
Options: {
|
||||
// showLabels: bool
|
||||
// showCommonLabels: bool
|
||||
// showFieldSelector?: bool
|
||||
// showTime: bool
|
||||
// showLogContextToggle: bool
|
||||
showControls?: bool
|
||||
controlsStorageKey?: string
|
||||
// wrapLogMessage: bool
|
||||
// prettifyLogMessage: bool
|
||||
// enableLogDetails: bool
|
||||
// syntaxHighlighting?: bool
|
||||
// sortOrder: common.LogsSortOrder
|
||||
// dedupStrategy: common.LogsDedupStrategy
|
||||
// enableInfiniteScrolling?: bool
|
||||
// noInteractions?: bool
|
||||
// showLogAttributes?: bool
|
||||
// fontSize?: "default" | "small" @cuetsy(kind="enum", memberNames="default|small")
|
||||
// detailsMode?: "inline" | "sidebar" @cuetsy(kind="enum", memberNames="inline|sidebar")
|
||||
// timestampResolution?: "ms" | "ns" @cuetsy(kind="enum", memberNames="ms|ns")
|
||||
// TODO: figure out how to define callbacks
|
||||
onClickFilterLabel?: _
|
||||
onClickFilterOutLabel?: _
|
||||
// isFilterLabelActive?: _
|
||||
// onClickFilterString?: _
|
||||
// onClickFilterOutString?: _
|
||||
// onClickShowField?: _
|
||||
// onClickHideField?: _
|
||||
// onLogOptionsChange?: _
|
||||
// logRowMenuIconsBefore?: _
|
||||
// logRowMenuIconsAfter?: _
|
||||
// logLineMenuCustomItems?: _
|
||||
// onNewLogsReceived?: _
|
||||
// showLabels: bool
|
||||
// showCommonLabels: bool
|
||||
// showFieldSelector?: bool
|
||||
// showTime: bool
|
||||
// showLogContextToggle: bool
|
||||
showControls?: bool
|
||||
controlsStorageKey?: string
|
||||
// wrapLogMessage: bool
|
||||
// prettifyLogMessage: bool
|
||||
// enableLogDetails: bool
|
||||
// syntaxHighlighting?: bool
|
||||
// sortOrder: common.LogsSortOrder
|
||||
// dedupStrategy: common.LogsDedupStrategy
|
||||
// enableInfiniteScrolling?: bool
|
||||
// noInteractions?: bool
|
||||
// showLogAttributes?: bool
|
||||
// fontSize?: "default" | "small" @cuetsy(kind="enum", memberNames="default|small")
|
||||
// detailsMode?: "inline" | "sidebar" @cuetsy(kind="enum", memberNames="inline|sidebar")
|
||||
// timestampResolution?: "ms" | "ns" @cuetsy(kind="enum", memberNames="ms|ns")
|
||||
// @todo filter methods no longer needed as props since these are defined by context?
|
||||
// onClickFilterLabel?: _
|
||||
// onClickFilterOutLabel?: _
|
||||
// isFilterLabelActive?: _
|
||||
// onClickFilterString?: _
|
||||
// onClickFilterOutString?: _
|
||||
// onClickShowField?: _
|
||||
// onClickHideField?: _
|
||||
// onLogOptionsChange?: _
|
||||
// logRowMenuIconsBefore?: _
|
||||
// logRowMenuIconsAfter?: _
|
||||
// logLineMenuCustomItems?: _
|
||||
// onNewLogsReceived?: _
|
||||
displayedFields?: [...string]
|
||||
setDisplayedFields?: _
|
||||
setDisplayedFields?: _
|
||||
buildLinkToLogLine?: _
|
||||
} @cuetsy(kind="interface")
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export interface Options {
|
||||
buildLinkToLogLine?: unknown;
|
||||
controlsStorageKey?: string;
|
||||
/**
|
||||
* isFilterLabelActive?: _
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { Field } from '@grafana/data';
|
||||
|
||||
import { LogsFrame } from '../../../features/logs/logsFrame';
|
||||
import type { Options as TableOptions } from '../table/panelcfg.gen';
|
||||
|
||||
import type { Options as LogsTableOptions } from './panelcfg.gen';
|
||||
|
||||
export type onLogsTableOptionsChangeType = (option: LogsTableOptions & TableOptions) => void;
|
||||
export function isOnLogsTableOptionsChange(callback: unknown): callback is onLogsTableOptionsChangeType {
|
||||
export type OnLogsTableOptionsChange = (option: LogsTableOptions & TableOptions) => void;
|
||||
export type BuildLinkToLogLine = (logsFrame: LogsFrame, rowIndex: number, field: Field) => string;
|
||||
export function isOnLogsTableOptionsChange(callback: unknown): callback is OnLogsTableOptionsChange {
|
||||
return typeof callback === 'function';
|
||||
}
|
||||
|
||||
export function isBuildLinkToLogLine(callback: unknown): callback is BuildLinkToLogLine {
|
||||
return typeof callback === 'function';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user