chore: pull in table options

This commit is contained in:
Galen
2026-01-13 15:23:16 -06:00
parent c16ab6dd21
commit a3f9ad5c0d
3 changed files with 297 additions and 68 deletions
@@ -1,10 +1,11 @@
import { css } from '@emotion/css';
import { useEffect, useState, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { lastValueFrom } from 'rxjs';
import {
applyFieldOverrides,
DataFrame,
FieldConfigSource,
GrafanaTheme2,
PanelProps,
transformDataFrame,
@@ -23,16 +24,23 @@ import {
LogsFrame,
parseLogsFrame,
} from '../../../features/logs/logsFrame';
import { isSetDisplayedFields } from '../logs/types';
import { TablePanel } from '../table/TablePanel';
import type { Options as TableOptions } from '../table/panelcfg.gen';
import type { Options } from './panelcfg.gen';
import type { Options as LogsTableOptions } from './panelcfg.gen';
import { isOnLogsTableOptionsChange, onLogsTableOptionsChangeType } from './types';
interface LogsTablePanelProps extends PanelProps<Options> {
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;
export const LogsTable = ({
@@ -55,7 +63,10 @@ export const LogsTable = ({
id,
renderCounter,
}: LogsTablePanelProps) => {
// Variables
const dataFrame = data.series[frameIndex];
// Hooks
const logsFrame: LogsFrame | null = useMemo(() => parseLogsFrame(dataFrame), [dataFrame]);
const defaultDisplayedFields = useMemo(
() => [
@@ -65,18 +76,54 @@ export const LogsTable = ({
[logsFrame?.timeField.name, logsFrame?.bodyField.name]
);
// State
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 dataLinksContext = useDataLinksContext();
const onTableOptionsChange = (options: TableOptions) => {
onOptionsChange({});
};
const unTransformedDataFrame = data.series[frameIndex];
// Methods
const onLogsTableOptionsChange: onLogsTableOptionsChangeType | undefined = isOnLogsTableOptionsChange(onOptionsChange)
? onOptionsChange
: undefined;
const setDisplayedFieldsFn = isSetDisplayedFields(options.setDisplayedFields)
? options.setDisplayedFields
: setDisplayedFields;
const onTableOptionsChange = useCallback(
(options: TableOptions) => {
console.log('onTableOptionsChange', options);
onLogsTableOptionsChange?.(options);
},
[onLogsTableOptionsChange]
);
const handleLogsTableOptionsChange = useCallback(
(options: LogsTableOptions) => {
onOptionsChange(options);
},
[onOptionsChange]
);
const handleSetDisplayedFields = useCallback(
(displayedFields: string[]) => {
setDisplayedFieldsFn(displayedFields);
handleLogsTableOptionsChange({ ...options, displayedFields: displayedFields });
},
[handleLogsTableOptionsChange, options, setDisplayedFieldsFn]
);
const handleTableOnFieldConfigChange = useCallback(
(fieldConfig: FieldConfigSource) => {
console.log('onTableOnFieldConfigChange', fieldConfig);
onFieldConfigChange(fieldConfig);
},
[onFieldConfigChange]
);
/**
* Extract fields transform
*/
@@ -91,10 +138,7 @@ export const LogsTable = ({
setExtractedFrame(
applyFieldOverrides({
data: frame,
fieldConfig: {
defaults: {},
overrides: [],
},
fieldConfig,
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
theme: config.theme2,
timeZone: timeZone,
@@ -102,7 +146,7 @@ export const LogsTable = ({
})
);
});
}, [dataLinksContext.dataLinkPostProcessor, timeZone, unTransformedDataFrame]);
}, [dataLinksContext.dataLinkPostProcessor, fieldConfig, timeZone, unTransformedDataFrame]);
/**
* Organize fields transform
@@ -164,9 +208,9 @@ export const LogsTable = ({
sidebarWidth={sidebarWidth}
toggle={(key: string) => {
if (displayedFields.includes(key)) {
setDisplayedFields(displayedFields.filter((f) => f !== key));
handleSetDisplayedFields(displayedFields.filter((f) => f !== key));
} else {
setDisplayedFields([...displayedFields, key]);
handleSetDisplayedFields([...displayedFields, key]);
}
}}
/>
@@ -186,7 +230,7 @@ export const LogsTable = ({
title={title}
eventBus={eventBus}
onOptionsChange={onTableOptionsChange}
onFieldConfigChange={onFieldConfigChange}
onFieldConfigChange={handleTableOnFieldConfigChange}
replaceVariables={replaceVariables}
onChangeTimeRange={onChangeTimeRange}
/>
+229 -50
View File
@@ -1,54 +1,233 @@
import { PanelPlugin } from '@grafana/data';
import { FieldConfigProperty, identityOverrideProcessor, PanelPlugin, standardEditorsRegistry } from '@grafana/data';
import { t } from '@grafana/i18n';
import {
TableCellDisplayMode,
TableCellHeight,
TableCellOptions,
TableCellTooltipPlacement,
} from '@grafana/schema/dist/esm/common/common.gen';
import { defaultTableFieldOptions } from '@grafana/schema/dist/esm/veneer/common.types';
import { PaginationEditor } from '../table/PaginationEditor';
import { TableCellOptionEditor } from '../table/TableCellOptionEditor';
import { tablePanelChangedHandler } from '../table/migrations';
import { defaultOptions, FieldConfig as TableFieldConfig } from '../table/panelcfg.gen';
import { tableSuggestionsSupplier } from '../table/suggestions';
import { LogsTable } from './LogsTable';
import { Options } from './panelcfg.gen';
export const plugin = new PanelPlugin<Options>(LogsTable).setPanelOptions((builder) => {
builder.addBooleanSwitch({
path: 'wrapLogMessage',
name: 'Wrap lines',
description: '',
defaultValue: false,
});
// @todo add table options?
//
// .addRadio({
// path: 'dedupStrategy',
// name: 'Deduplication',
// description: '',
// settings: {
// options: [
// { value: LogsDedupStrategy.none, label: 'None', description: LogsDedupDescription[LogsDedupStrategy.none] },
// {
// value: LogsDedupStrategy.exact,
// label: 'Exact',
// description: LogsDedupDescription[LogsDedupStrategy.exact],
// },
// {
// value: LogsDedupStrategy.numbers,
// label: 'Numbers',
// description: LogsDedupDescription[LogsDedupStrategy.numbers],
// },
// {
// value: LogsDedupStrategy.signature,
// label: 'Signature',
// description: LogsDedupDescription[LogsDedupStrategy.signature],
// },
// ],
// },
// defaultValue: LogsDedupStrategy.none,
// })
// .addRadio({
// path: 'sortOrder',
// name: 'Order',
// description: '',
// settings: {
// options: [
// { value: LogsSortOrder.Descending, label: 'Newest first' },
// { value: LogsSortOrder.Ascending, label: 'Oldest first' },
// ],
// },
// defaultValue: LogsSortOrder.Descending,
// });
});
export const plugin = new PanelPlugin<Options, TableFieldConfig>(LogsTable)
.setPanelChangeHandler(tablePanelChangedHandler)
.useFieldConfig({
standardOptions: {
[FieldConfigProperty.Actions]: {
hideFromDefaults: false,
},
},
useCustomConfig: (builder) => {
const category = [t('table.category-logs-table', 'Logs Table')];
const cellCategory = [t('table.category-cell-options', 'Cell options')];
builder
.addNumberInput({
path: 'minWidth',
name: t('table.name-min-column-width', 'Minimum column width'),
category,
description: t('table.description-min-column-width', 'The minimum width for column auto resizing'),
settings: {
placeholder: '150',
min: 50,
max: 500,
},
shouldApply: () => true,
defaultValue: defaultTableFieldOptions.minWidth,
})
.addNumberInput({
path: 'width',
name: t('table.name-column-width', 'Column width'),
category,
settings: {
placeholder: t('table.placeholder-column-width', 'auto'),
min: 20,
},
shouldApply: () => true,
defaultValue: defaultTableFieldOptions.width,
})
.addRadio({
path: 'align',
name: t('table.name-column-alignment', 'Column alignment'),
category,
settings: {
options: [
{ label: t('table.column-alignment-options.label-auto', 'Auto'), value: 'auto' },
{ label: t('table.column-alignment-options.label-left', 'Left'), value: 'left' },
{ label: t('table.column-alignment-options.label-center', 'Center'), value: 'center' },
{ label: t('table.column-alignment-options.label-right', 'Right'), value: 'right' },
],
},
defaultValue: defaultTableFieldOptions.align,
})
.addBooleanSwitch({
path: 'filterable',
name: t('table.name-column-filter', 'Column filter'),
category,
description: t('table.description-column-filter', 'Enables/disables field filters in table'),
defaultValue: defaultTableFieldOptions.filterable,
})
.addBooleanSwitch({
path: 'wrapText',
name: t('table.name-wrap-text', 'Wrap text'),
category,
})
.addBooleanSwitch({
path: 'wrapHeaderText',
name: t('table.name-wrap-header-text', 'Wrap header text'),
category,
})
.addBooleanSwitch({
path: 'hideFrom.viz',
name: t('table.name-hide-in-table', 'Hide in table'),
category,
defaultValue: undefined,
hideFromDefaults: true,
})
.addCustomEditor({
id: 'footer.reducers',
category: [t('table.category-table-footer', 'Table footer')],
path: 'footer.reducers',
name: t('table.name-calculation', 'Calculation'),
description: t('table.description-calculation', 'Choose a reducer function / calculation'),
editor: standardEditorsRegistry.get('stats-picker').editor,
override: standardEditorsRegistry.get('stats-picker').editor,
defaultValue: [],
process: identityOverrideProcessor,
shouldApply: () => true,
settings: {
allowMultiple: true,
},
})
.addCustomEditor<void, TableCellOptions>({
id: 'cellOptions',
path: 'cellOptions',
name: t('table.name-cell-type', 'Cell type'),
editor: TableCellOptionEditor,
override: TableCellOptionEditor,
defaultValue: defaultTableFieldOptions.cellOptions,
process: identityOverrideProcessor,
category: cellCategory,
shouldApply: () => true,
})
.addBooleanSwitch({
path: 'inspect',
name: t('table.name-cell-value-inspect', 'Cell value inspect'),
description: t('table.description-cell-value-inspect', 'Enable cell value inspection in a modal window'),
defaultValue: false,
category: cellCategory,
showIf: (cfg) => {
return (
cfg.cellOptions.type === TableCellDisplayMode.Auto ||
cfg.cellOptions.type === TableCellDisplayMode.JSONView ||
cfg.cellOptions.type === TableCellDisplayMode.ColorText ||
cfg.cellOptions.type === TableCellDisplayMode.ColorBackground
);
},
})
.addFieldNamePicker({
path: 'tooltip.field',
name: t('table.name-tooltip-from-field', 'Tooltip from field'),
description: t(
'table.description-tooltip-from-field',
'Render a cell from a field (hidden or visible) in a tooltip'
),
category: cellCategory,
})
.addSelect({
path: 'tooltip.placement',
name: t('table.name-tooltip-placement', 'Tooltip placement'),
category: cellCategory,
settings: {
options: [
{
label: t('table.tooltip-placement-options.label-auto', 'Auto'),
value: TableCellTooltipPlacement.Auto,
},
{
label: t('table.tooltip-placement-options.label-top', 'Top'),
value: TableCellTooltipPlacement.Top,
},
{
label: t('table.tooltip-placement-options.label-right', 'Right'),
value: TableCellTooltipPlacement.Right,
},
{
label: t('table.tooltip-placement-options.label-bottom', 'Bottom'),
value: TableCellTooltipPlacement.Bottom,
},
{
label: t('table.tooltip-placement-options.label-left', 'Left'),
value: TableCellTooltipPlacement.Left,
},
],
},
showIf: (cfg) => cfg.tooltip?.field !== undefined,
})
.addFieldNamePicker({
path: 'styleField',
name: t('table.name-styling-from-field', 'Styling from field'),
description: t('table.description-styling-from-field', 'A field containing JSON objects with CSS properties'),
category: cellCategory,
});
},
})
.setPanelOptions((builder) => {
const category = [t('table.category-table', 'Table')];
builder
.addBooleanSwitch({
path: 'showHeader',
name: t('table.name-show-table-header', 'Show table header'),
category,
defaultValue: defaultOptions.showHeader,
})
.addNumberInput({
path: 'frozenColumns.left',
name: t('table.name-frozen-columns', 'Frozen columns'),
description: t('table.description-frozen-columns', 'Columns are frozen from the left side of the table'),
settings: {
placeholder: 'none',
},
category,
})
.addRadio({
path: 'cellHeight',
name: t('table.name-cell-height', 'Cell height'),
category,
defaultValue: defaultOptions.cellHeight,
settings: {
options: [
{ value: TableCellHeight.Sm, label: t('table.cell-height-options.label-small', 'Small') },
{ value: TableCellHeight.Md, label: t('table.cell-height-options.label-medium', 'Medium') },
{ value: TableCellHeight.Lg, label: t('table.cell-height-options.label-large', 'Large') },
],
},
})
.addNumberInput({
path: 'maxRowHeight',
name: t('table.name-max-height', 'Max row height'),
category,
settings: {
placeholder: t('table.placeholder-max-height', 'none'),
min: 0,
},
})
.addCustomEditor({
id: 'enablePagination',
path: 'enablePagination',
name: t('table.name-enable-pagination', 'Enable pagination'),
category,
editor: PaginationEditor,
defaultValue: defaultOptions?.enablePagination,
});
})
// @todo
//@ts-expect-error
.setSuggestionsSupplier(tableSuggestionsSupplier);
+8 -2
View File
@@ -1,2 +1,8 @@
// @todo add some stuff
export type TODO = string;
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 {
return typeof callback === 'function';
}