chore: PoC wip
This commit is contained in:
@@ -29,11 +29,11 @@ function getField(cache: FieldCache, name: string, fieldType: FieldType): FieldW
|
||||
return field.type === fieldType ? field : undefined;
|
||||
}
|
||||
|
||||
const DATAPLANE_TIMESTAMP_NAME = 'timestamp';
|
||||
const DATAPLANE_BODY_NAME = 'body';
|
||||
const DATAPLANE_SEVERITY_NAME = 'severity';
|
||||
const DATAPLANE_ID_NAME = 'id';
|
||||
const DATAPLANE_LABELS_NAME = 'labels';
|
||||
export const LOGS_DATAPLANE_TIMESTAMP_NAME = 'timestamp';
|
||||
export const LOGS_DATAPLANE_BODY_NAME = 'body';
|
||||
const LOGS_DATAPLANE_SEVERITY_NAME = 'severity';
|
||||
const LOGS_DATAPLANE_ID_NAME = 'id';
|
||||
const LOGS_DATAPLANE_LABELS_NAME = 'labels';
|
||||
|
||||
// NOTE: this is a hot fn, we need to avoid allocating new objects here
|
||||
export function logFrameLabelsToLabels(logFrameLabels: LogFrameLabels): Labels {
|
||||
@@ -66,17 +66,17 @@ export function logFrameLabelsToLabels(logFrameLabels: LogFrameLabels): Labels {
|
||||
export function parseDataplaneLogsFrame(frame: DataFrame): LogsFrame | null {
|
||||
const cache = new FieldCache(frame);
|
||||
|
||||
const timestampField = getField(cache, DATAPLANE_TIMESTAMP_NAME, FieldType.time);
|
||||
const bodyField = getField(cache, DATAPLANE_BODY_NAME, FieldType.string);
|
||||
const timestampField = getField(cache, LOGS_DATAPLANE_TIMESTAMP_NAME, FieldType.time);
|
||||
const bodyField = getField(cache, LOGS_DATAPLANE_BODY_NAME, FieldType.string);
|
||||
|
||||
// these two are mandatory
|
||||
if (timestampField === undefined || bodyField === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const severityField = getField(cache, DATAPLANE_SEVERITY_NAME, FieldType.string) ?? null;
|
||||
const idField = getField(cache, DATAPLANE_ID_NAME, FieldType.string) ?? null;
|
||||
const labelsField = getField(cache, DATAPLANE_LABELS_NAME, FieldType.other) ?? null;
|
||||
const severityField = getField(cache, LOGS_DATAPLANE_SEVERITY_NAME, FieldType.string) ?? null;
|
||||
const idField = getField(cache, LOGS_DATAPLANE_ID_NAME, FieldType.string) ?? null;
|
||||
const labelsField = getField(cache, LOGS_DATAPLANE_LABELS_NAME, FieldType.other) ?? null;
|
||||
|
||||
const labels = labelsField === null ? null : labelsField.values;
|
||||
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { DataFrame, PanelProps, transformDataFrame } from '@grafana/data';
|
||||
import {
|
||||
applyFieldOverrides,
|
||||
DataFrame,
|
||||
GrafanaTheme2,
|
||||
PanelProps,
|
||||
transformDataFrame,
|
||||
useDataLinksContext,
|
||||
} from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { config } from '../../../core/config';
|
||||
import { getLogsExtractFields } from '../../../features/explore/Logs/LogsTable';
|
||||
import { LogsFrame, parseLogsFrame } from '../../../features/logs/logsFrame';
|
||||
import { FieldNameMetaStore } from '../../../features/explore/Logs/LogsTableWrap';
|
||||
import { LogsTableFieldSelector } from '../../../features/logs/components/fieldSelector/FieldSelector';
|
||||
import {
|
||||
LOGS_DATAPLANE_BODY_NAME,
|
||||
LOGS_DATAPLANE_TIMESTAMP_NAME,
|
||||
LogsFrame,
|
||||
parseLogsFrame,
|
||||
} from '../../../features/logs/logsFrame';
|
||||
import { TablePanel } from '../table/TablePanel';
|
||||
import type { Options as TableOptions } from '../table/panelcfg.gen';
|
||||
|
||||
@@ -15,6 +33,8 @@ interface LogsTablePanelProps extends PanelProps<Options> {
|
||||
showHeader?: boolean;
|
||||
}
|
||||
|
||||
const sidebarWidth = 200;
|
||||
|
||||
export const LogsTable = ({
|
||||
data,
|
||||
width,
|
||||
@@ -35,13 +55,21 @@ export const LogsTable = ({
|
||||
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 dataFrame = data.series[frameIndex];
|
||||
const logsFrame: LogsFrame | null = useMemo(() => parseLogsFrame(dataFrame), [dataFrame]);
|
||||
const defaultDisplayedFields = useMemo(
|
||||
() => [
|
||||
logsFrame?.timeField.name ?? LOGS_DATAPLANE_TIMESTAMP_NAME,
|
||||
logsFrame?.bodyField.name ?? LOGS_DATAPLANE_BODY_NAME,
|
||||
],
|
||||
[logsFrame?.timeField.name, logsFrame?.bodyField.name]
|
||||
);
|
||||
|
||||
const [extractedFrame, setExtractedFrame] = useState<DataFrame | null>(null);
|
||||
const [organizedFrame, setOrganizedFrame] = useState<DataFrame | null>(null);
|
||||
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({});
|
||||
@@ -60,9 +88,21 @@ export const LogsTable = ({
|
||||
};
|
||||
|
||||
extractFields().then((frame) => {
|
||||
setExtractedFrame(frame[0]);
|
||||
setExtractedFrame(
|
||||
applyFieldOverrides({
|
||||
data: frame,
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||
theme: config.theme2,
|
||||
timeZone: timeZone,
|
||||
dataLinkPostProcessor: dataLinksContext.dataLinkPostProcessor,
|
||||
})
|
||||
);
|
||||
});
|
||||
}, [unTransformedDataFrame]);
|
||||
}, [dataLinksContext.dataLinkPostProcessor, timeZone, unTransformedDataFrame]);
|
||||
|
||||
/**
|
||||
* Organize fields transform
|
||||
@@ -93,42 +133,102 @@ export const LogsTable = ({
|
||||
},
|
||||
},
|
||||
],
|
||||
[extractedFrame]
|
||||
extractedFrame
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
organizeFields(options.displayedFields ?? [logsFrame.timeField.name, logsFrame.bodyField.name]).then((frame) => {
|
||||
organizeFields(displayedFields).then((frame) => {
|
||||
if (frame) {
|
||||
setOrganizedFrame(frame[0]);
|
||||
setOrganizedFrame(frame);
|
||||
}
|
||||
});
|
||||
}, [extractedFrame, options.displayedFields, logsFrame.timeField.name, logsFrame.bodyField.name]);
|
||||
}, [extractedFrame, displayedFields]);
|
||||
|
||||
if (extractedFrame === null || organizedFrame === null) {
|
||||
if (extractedFrame === null || organizedFrame === null || logsFrame === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('render::LogsTable', { extractedFrame });
|
||||
|
||||
return (
|
||||
<TablePanel
|
||||
data={{ ...data, series: [organizedFrame] }}
|
||||
width={width}
|
||||
height={height}
|
||||
id={id}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
options={{ ...options, frameIndex: 0, showHeader }}
|
||||
transparent={transparent}
|
||||
fieldConfig={fieldConfig}
|
||||
renderCounter={renderCounter}
|
||||
title={title}
|
||||
eventBus={eventBus}
|
||||
onOptionsChange={onTableOptionsChange}
|
||||
onFieldConfigChange={onFieldConfigChange}
|
||||
replaceVariables={replaceVariables}
|
||||
onChangeTimeRange={onChangeTimeRange}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.sidebarWrapper}>
|
||||
<LogsTableFieldSelector
|
||||
clear={() => {}}
|
||||
columnsWithMeta={displayedFieldsToColumns(displayedFields, logsFrame)}
|
||||
dataFrames={extractedFrame}
|
||||
logs={[]}
|
||||
reorder={(columns: string[]) => {}}
|
||||
setSidebarWidth={(width) => {}}
|
||||
sidebarWidth={sidebarWidth}
|
||||
toggle={(key: string) => {
|
||||
if (displayedFields.includes(key)) {
|
||||
setDisplayedFields(displayedFields.filter((f) => f !== key));
|
||||
} else {
|
||||
setDisplayedFields([...displayedFields, key]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.tableWrapper}>
|
||||
<TablePanel
|
||||
data={{ ...data, series: organizedFrame }}
|
||||
width={width - sidebarWidth}
|
||||
height={height}
|
||||
id={id}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
options={{ ...options, frameIndex, showHeader }}
|
||||
transparent={transparent}
|
||||
fieldConfig={fieldConfig}
|
||||
renderCounter={renderCounter}
|
||||
title={title}
|
||||
eventBus={eventBus}
|
||||
onOptionsChange={onTableOptionsChange}
|
||||
onFieldConfigChange={onFieldConfigChange}
|
||||
replaceVariables={replaceVariables}
|
||||
onChangeTimeRange={onChangeTimeRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function displayedFieldsToColumns(displayedFields: string[], logsFrame: LogsFrame): FieldNameMetaStore {
|
||||
const columns: FieldNameMetaStore = {};
|
||||
for (const [idx, field] of displayedFields.entries()) {
|
||||
columns[field] = {
|
||||
percentOfLinesWithLabel: 0,
|
||||
type:
|
||||
field === logsFrame.bodyField.name
|
||||
? 'BODY_FIELD'
|
||||
: field === logsFrame.timeField.name
|
||||
? 'TIME_FIELD'
|
||||
: undefined,
|
||||
active: true,
|
||||
index: idx,
|
||||
};
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, sidebarWidth: number, height: number, width: number) => {
|
||||
return {
|
||||
tableWrapper: css({
|
||||
paddingLeft: sidebarWidth,
|
||||
height,
|
||||
width,
|
||||
}),
|
||||
sidebarWrapper: css({
|
||||
position: 'absolute',
|
||||
height: height,
|
||||
width: sidebarWidth,
|
||||
}),
|
||||
wrapper: css({
|
||||
height,
|
||||
width,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user