mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Logs Panel: Table UI - Add time and body fields to column selection in logs table visualization (#77468)
* add the ability to set visibility of time and body fields, set default of time and body fields when no other fields are selected
This commit is contained in:
@@ -48,6 +48,9 @@ export function LogsTable(props: Props) {
|
||||
|
||||
const prepareTableFrame = useCallback(
|
||||
(frame: DataFrame): DataFrame => {
|
||||
if (!frame.length) {
|
||||
return frame;
|
||||
}
|
||||
// Parse the dataframe to a logFrame
|
||||
const logsFrame = parseLogsFrame(frame);
|
||||
const timeIndex = logsFrame?.timeField.index;
|
||||
|
||||
@@ -33,6 +33,65 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
};
|
||||
}
|
||||
|
||||
function sortLabels(labels: Record<string, fieldNameMeta>) {
|
||||
return (a: string, b: string) => {
|
||||
// First sort by active
|
||||
if (labels[a].active && labels[b].active) {
|
||||
// If both fields are active, sort time first
|
||||
if (labels[a]?.type === 'TIME_FIELD') {
|
||||
return -1;
|
||||
}
|
||||
if (labels[b]?.type === 'TIME_FIELD') {
|
||||
return 1;
|
||||
}
|
||||
// And then line second
|
||||
if (labels[a]?.type === 'BODY_FIELD') {
|
||||
return -1;
|
||||
}
|
||||
// special fields are next
|
||||
if (labels[b]?.type === 'BODY_FIELD') {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If just one label is active, sort it first
|
||||
if (labels[b].active) {
|
||||
return 1;
|
||||
}
|
||||
if (labels[a].active) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If both fields are special, and not selected, sort time first
|
||||
if (labels[a]?.type && labels[b]?.type) {
|
||||
if (labels[a]?.type === 'TIME_FIELD') {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If only one special field, stick to the top of inactive fields
|
||||
if (labels[a]?.type && !labels[b]?.type) {
|
||||
return -1;
|
||||
}
|
||||
// if the b field is special, sort it first
|
||||
if (!labels[a]?.type && labels[b]?.type) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Finally sort by percent enabled, this could have conflicts with the special fields above, except they are always on 100% of logs
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// otherwise do not sort
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
export const LogsTableNavColumn = (props: {
|
||||
labels: Record<string, fieldNameMeta>;
|
||||
valueFilter: (value: number) => boolean;
|
||||
@@ -45,7 +104,7 @@ export const LogsTableNavColumn = (props: {
|
||||
if (labelKeys.length) {
|
||||
return (
|
||||
<div className={styles.columnWrapper}>
|
||||
{labelKeys.map((labelName) => (
|
||||
{labelKeys.sort(sortLabels(labels)).map((labelName) => (
|
||||
<div className={styles.wrap} key={labelName}>
|
||||
<Checkbox
|
||||
className={styles.checkbox}
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('LogsTableWrap', () => {
|
||||
checkboxLabel.click();
|
||||
expect(updatePanelState).toBeCalledWith({
|
||||
visualisationType: 'table',
|
||||
columns: { 0: 'app' },
|
||||
columns: { 0: 'app', 1: 'Line', 2: 'Time' },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('LogsTableWrap', () => {
|
||||
checkboxLabel.click();
|
||||
expect(updatePanelState).toBeCalledWith({
|
||||
visualisationType: 'table',
|
||||
columns: {},
|
||||
columns: { 0: 'Line', 1: 'Time' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,11 @@ interface Props extends Themeable2 {
|
||||
onClickFilterOutLabel?: (key: string, value: string, refId?: string) => void;
|
||||
}
|
||||
|
||||
export type fieldNameMeta = { percentOfLinesWithLabel: number; active: boolean | undefined };
|
||||
export type fieldNameMeta = {
|
||||
percentOfLinesWithLabel: number;
|
||||
active: boolean | undefined;
|
||||
type?: 'BODY_FIELD' | 'TIME_FIELD';
|
||||
};
|
||||
type fieldName = string;
|
||||
type fieldNameMetaStore = Record<fieldName, fieldNameMeta>;
|
||||
|
||||
@@ -96,14 +100,28 @@ export function LogsTableWrap(props: Props) {
|
||||
*
|
||||
*/
|
||||
useEffect(() => {
|
||||
// If the data frame is empty, there's nothing to viz, it could mean the user has unselected all columns
|
||||
if (!dataFrame.length) {
|
||||
return;
|
||||
}
|
||||
const numberOfLogLines = dataFrame ? dataFrame.length : 0;
|
||||
const logsFrame = parseLogsFrame(dataFrame);
|
||||
const labels = logsFrame?.getLogFrameLabelsAsLabels();
|
||||
|
||||
const otherFields = logsFrame ? logsFrame.extraFields.filter((field) => !field?.config?.custom?.hidden) : [];
|
||||
const otherFields = [];
|
||||
|
||||
if (logsFrame) {
|
||||
otherFields.push(...logsFrame.extraFields.filter((field) => !field?.config?.custom?.hidden));
|
||||
}
|
||||
if (logsFrame?.severityField) {
|
||||
otherFields.push(logsFrame?.severityField);
|
||||
}
|
||||
if (logsFrame?.bodyField) {
|
||||
otherFields.push(logsFrame?.bodyField);
|
||||
}
|
||||
if (logsFrame?.timeField) {
|
||||
otherFields.push(logsFrame?.timeField);
|
||||
}
|
||||
|
||||
// Use a map to dedupe labels and count their occurrences in the logs
|
||||
const labelCardinality = new Map<fieldName, fieldNameMeta>();
|
||||
@@ -159,6 +177,24 @@ export function LogsTableWrap(props: Props) {
|
||||
|
||||
pendingLabelState = getColumnsFromProps(pendingLabelState);
|
||||
|
||||
// Get all active columns
|
||||
const active = Object.keys(pendingLabelState).filter((key) => pendingLabelState[key].active);
|
||||
|
||||
// If nothing is selected, then select the default columns
|
||||
if (active.length === 0) {
|
||||
if (logsFrame?.bodyField?.name) {
|
||||
pendingLabelState[logsFrame.bodyField.name].active = true;
|
||||
}
|
||||
if (logsFrame?.timeField?.name) {
|
||||
pendingLabelState[logsFrame.timeField.name].active = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (logsFrame?.bodyField?.name && logsFrame?.timeField?.name) {
|
||||
pendingLabelState[logsFrame.bodyField.name].type = 'BODY_FIELD';
|
||||
pendingLabelState[logsFrame.timeField.name].type = 'TIME_FIELD';
|
||||
}
|
||||
|
||||
setColumnsWithMeta(pendingLabelState);
|
||||
|
||||
// The panel state is updated when the user interacts with the multi-select sidebar
|
||||
|
||||
Reference in New Issue
Block a user