diff --git a/packages/grafana-schema/src/raw/composable/logstable/panelcfg/x/LogsTablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logstable/panelcfg/x/LogsTablePanelCfg_types.gen.ts index a96cf463b3e..c0ef0805e13 100644 --- a/packages/grafana-schema/src/raw/composable/logstable/panelcfg/x/LogsTablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logstable/panelcfg/x/LogsTablePanelCfg_types.gen.ts @@ -43,14 +43,14 @@ export interface Options { onClickFilterLabel?: unknown; onClickFilterOutLabel?: unknown; setDisplayedFields?: unknown; - showControls?: boolean; /** * showLabels: bool * showCommonLabels: bool * showFieldSelector?: bool * showTime: bool + * showLogContextToggle: bool */ - showLogContextToggle: boolean; + showControls?: boolean; } export const defaultOptions: Partial = { diff --git a/public/app/features/explore/Logs/LogsTable.tsx b/public/app/features/explore/Logs/LogsTable.tsx index 96165ca03be..2de3f2b4577 100644 --- a/public/app/features/explore/Logs/LogsTable.tsx +++ b/public/app/features/explore/Logs/LogsTable.tsx @@ -367,8 +367,7 @@ const isFieldFilterable = (field: Field, bodyName: string, timeName: string) => return true; }; -// TODO: explore if `logsFrame.ts` can help us with getting the right fields -// TODO Why is typeInfo not defined on the Field interface? +// @todo move to logsFrame export function getLogsExtractFields(dataFrame: DataFrame) { return dataFrame.fields .filter((field: Field & { typeInfo?: { frame: string } }) => { diff --git a/public/app/plugins/panel/logstable/LogsTable.tsx b/public/app/plugins/panel/logstable/LogsTable.tsx index cf67fb441e2..b9f9f7c864c 100644 --- a/public/app/plugins/panel/logstable/LogsTable.tsx +++ b/public/app/plugins/panel/logstable/LogsTable.tsx @@ -1,4 +1,134 @@ -export const LogsTable = () => { - // eslint-disable-next-line @grafana/i18n/no-untranslated-strings - return
Hello plugin
; +import { useEffect, useState } from 'react'; +import { lastValueFrom } from 'rxjs'; + +import { DataFrame, PanelProps, transformDataFrame } from '@grafana/data'; + +import { getLogsExtractFields } from '../../../features/explore/Logs/LogsTable'; +import { LogsFrame, parseLogsFrame } from '../../../features/logs/logsFrame'; +import { TablePanel } from '../table/TablePanel'; +import type { Options as TableOptions } from '../table/panelcfg.gen'; + +import type { Options } from './panelcfg.gen'; + +interface LogsTablePanelProps extends PanelProps { + frameIndex?: number; + showHeader?: boolean; +} + +export const LogsTable = ({ + data, + width, + height, + timeRange, + fieldConfig, + options, + eventBus, + frameIndex = 0, + showHeader = true, + onOptionsChange, + onFieldConfigChange, + replaceVariables, + onChangeTimeRange, + title, + transparent, + timeZone, + id, + renderCounter, +}: LogsTablePanelProps) => { + const logsFrame: LogsFrame | null = parseLogsFrame(data.series[frameIndex]); + if (!logsFrame?.timeField.name || !logsFrame?.bodyField.name) { + throw new Error(`Invalid logsFrame frame name: ${frameIndex}`); + } + + const [extractedFrame, setExtractedFrame] = useState(null); + const [organizedFrame, setOrganizedFrame] = useState(null); + + const onTableOptionsChange = (options: TableOptions) => { + onOptionsChange({}); + }; + + const unTransformedDataFrame = data.series[frameIndex]; + + /** + * Extract fields transform + */ + useEffect(() => { + const extractFields = async () => { + return await lastValueFrom( + transformDataFrame(getLogsExtractFields(unTransformedDataFrame), [unTransformedDataFrame]) + ); + }; + + extractFields().then((frame) => { + setExtractedFrame(frame[0]); + }); + }, [unTransformedDataFrame]); + + /** + * Organize fields transform + */ + useEffect(() => { + const organizeFields = async (displayedFields: string[]) => { + if (!extractedFrame) { + return Promise.resolve(null); + } + + let indexByName: Record = {}; + let includeByName: Record = {}; + if (displayedFields) { + for (const [idx, field] of displayedFields.entries()) { + indexByName[field] = idx; + includeByName[field] = true; + } + } + + return await lastValueFrom( + transformDataFrame( + [ + { + id: 'organize', + options: { + indexByName, + includeByName, + }, + }, + ], + [extractedFrame] + ) + ); + }; + + organizeFields(options.displayedFields ?? [logsFrame.timeField.name, logsFrame.bodyField.name]).then((frame) => { + if (frame) { + setOrganizedFrame(frame[0]); + } + }); + }, [extractedFrame, options.displayedFields, logsFrame.timeField.name, logsFrame.bodyField.name]); + + if (extractedFrame === null || organizedFrame === null) { + return; + } + + console.log('render::LogsTable', { extractedFrame }); + + return ( + + ); }; diff --git a/public/app/plugins/panel/logstable/module.tsx b/public/app/plugins/panel/logstable/module.tsx index 2dceca7dc0f..254a7512847 100644 --- a/public/app/plugins/panel/logstable/module.tsx +++ b/public/app/plugins/panel/logstable/module.tsx @@ -1,70 +1,54 @@ -import { PanelPlugin, LogsSortOrder, LogsDedupStrategy, LogsDedupDescription } from '@grafana/data'; +import { PanelPlugin } from '@grafana/data'; import { LogsTable } from './LogsTable'; import { Options } from './panelcfg.gen'; export const plugin = new PanelPlugin(LogsTable).setPanelOptions((builder) => { - builder - .addBooleanSwitch({ - path: 'showTime', - name: 'Time', - description: '', - defaultValue: false, - }) - .addBooleanSwitch({ - path: 'wrapLogMessage', - name: 'Wrap lines', - description: '', - defaultValue: false, - }) - .addBooleanSwitch({ - path: 'enableLogDetails', - name: 'Enable log details', - description: '', - defaultValue: true, - }) - .addBooleanSwitch({ - path: 'enableInfiniteScrolling', - name: 'Enable infinite scrolling', - description: 'Experimental. Request more results by scrolling to the bottom of the logs list.', - defaultValue: false, - }) - .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, - }); + 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, + // }); }); diff --git a/public/app/plugins/panel/logstable/panelcfg.cue b/public/app/plugins/panel/logstable/panelcfg.cue index b19a99988f6..f8de532f0fd 100644 --- a/public/app/plugins/panel/logstable/panelcfg.cue +++ b/public/app/plugins/panel/logstable/panelcfg.cue @@ -30,7 +30,7 @@ composableKinds: PanelCfg: { // showCommonLabels: bool // showFieldSelector?: bool // showTime: bool - showLogContextToggle: bool +// showLogContextToggle: bool showControls?: bool controlsStorageKey?: string // wrapLogMessage: bool diff --git a/public/app/plugins/panel/logstable/panelcfg.gen.ts b/public/app/plugins/panel/logstable/panelcfg.gen.ts index cbac454574d..1c7cb3025b2 100644 --- a/public/app/plugins/panel/logstable/panelcfg.gen.ts +++ b/public/app/plugins/panel/logstable/panelcfg.gen.ts @@ -41,14 +41,14 @@ export interface Options { onClickFilterLabel?: unknown; onClickFilterOutLabel?: unknown; setDisplayedFields?: unknown; - showControls?: boolean; /** * showLabels: bool * showCommonLabels: bool * showFieldSelector?: bool * showTime: bool + * showLogContextToggle: bool */ - showLogContextToggle: boolean; + showControls?: boolean; } export const defaultOptions: Partial = {