diff --git a/public/app/plugins/panel/logstable/LogsTable.tsx b/public/app/plugins/panel/logstable/LogsTable.tsx index f8fca1576d6..2d533b844a8 100644 --- a/public/app/plugins/panel/logstable/LogsTable.tsx +++ b/public/app/plugins/panel/logstable/LogsTable.tsx @@ -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 { +interface LogsTablePanelProps extends PanelProps { 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(null); const [organizedFrame, setOrganizedFrame] = useState(null); const [displayedFields, setDisplayedFields] = useState(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} /> diff --git a/public/app/plugins/panel/logstable/module.tsx b/public/app/plugins/panel/logstable/module.tsx index 254a7512847..c1538a48a32 100644 --- a/public/app/plugins/panel/logstable/module.tsx +++ b/public/app/plugins/panel/logstable/module.tsx @@ -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(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(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({ + 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); diff --git a/public/app/plugins/panel/logstable/types.ts b/public/app/plugins/panel/logstable/types.ts index fddac6b62ce..19929075381 100644 --- a/public/app/plugins/panel/logstable/types.ts +++ b/public/app/plugins/panel/logstable/types.ts @@ -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'; +}