Logs Panel: Table UI - Refactor to use includeByName transformation (#78070)

* remove excludeByName logic from LogsTable.tsx, migrate to includeByName. Update tests
pull/78116/head
Galen Kistler 2 years ago committed by GitHub
parent fcc2f01c7e
commit a18eee4093
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      public/app/features/explore/Logs/LogsTable.test.tsx
  2. 82
      public/app/features/explore/Logs/LogsTable.tsx

@ -122,6 +122,12 @@ describe('LogsTable', () => {
it('should render extracted labels as columns (elastic)', async () => {
setup({
logsFrames: [getMockElasticFrame()],
columnsWithMeta: {
counter: { active: true, percentOfLinesWithLabel: 3 },
level: { active: true, percentOfLinesWithLabel: 3 },
line: { active: true, percentOfLinesWithLabel: 3 },
'@timestamp': { active: true, percentOfLinesWithLabel: 3 },
},
});
await waitFor(() => {
@ -137,6 +143,8 @@ describe('LogsTable', () => {
setup({
columnsWithMeta: {
foo: { active: true, percentOfLinesWithLabel: 3 },
Time: { active: true, percentOfLinesWithLabel: 3 },
line: { active: true, percentOfLinesWithLabel: 3 },
},
});
@ -196,7 +204,16 @@ describe('LogsTable', () => {
});
it('should render a datalink for each row', async () => {
render(getComponent({}, getMockLokiFrameDataPlane()));
render(
getComponent(
{
columnsWithMeta: {
traceID: { active: true, percentOfLinesWithLabel: 3 },
},
},
getMockLokiFrameDataPlane()
)
);
await waitFor(() => {
const links = screen.getAllByRole('link');
@ -229,12 +246,13 @@ describe('LogsTable', () => {
setup({
columnsWithMeta: {
foo: { active: true, percentOfLinesWithLabel: 3 },
line: { active: true, percentOfLinesWithLabel: 3 },
Time: { active: true, percentOfLinesWithLabel: 3 },
},
});
await waitFor(() => {
const columns = screen.getAllByRole('columnheader');
expect(columns[0].textContent).toContain('Time');
expect(columns[1].textContent).toContain('line');
expect(columns[2].textContent).toContain('foo');

@ -19,7 +19,6 @@ import {
import { config } from '@grafana/runtime';
import { AdHocFilterItem, Table } from '@grafana/ui';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR } from '@grafana/ui/src/components/Table/types';
import { separateVisibleFields } from 'app/features/logs/components/logParser';
import { LogsFrame, parseLogsFrame } from 'app/features/logs/logsFrame';
import { getFieldLinksForExplore } from '../utils/links';
@ -110,17 +109,25 @@ export function LogsTable(props: Props) {
let dataFrame = logFrameRaw;
// create extract JSON transformation for every field that is `json.RawMessage`
const transformations: Array<DataTransformerConfig | CustomTransformOperator> =
extractFieldsAndExclude(dataFrame);
const transformations: Array<DataTransformerConfig | CustomTransformOperator> = extractFields(dataFrame);
// remove hidden fields
transformations.push(...removeHiddenFields(dataFrame));
let labelFilters = buildLabelFilters(columnsWithMeta, logsFrame);
let labelFilters = buildLabelFilters(columnsWithMeta);
// Add the label filters to the transformations
const transform = getLabelFiltersTransform(labelFilters);
if (transform) {
transformations.push(transform);
} else {
// If no fields are filtered, filter the default fields, so we don't render all columns
transformations.push({
id: 'organize',
options: {
includeByName: {
[logsFrame.bodyField.name]: true,
[logsFrame.timeField.name]: true,
},
},
});
}
if (transformations.length > 0) {
@ -181,7 +188,7 @@ const isFieldFilterable = (field: Field, logsFrame?: LogsFrame | undefined) => {
// TODO: explore if `logsFrame.ts` can help us with getting the right fields
// TODO Why is typeInfo not defined on the Field interface?
function extractFieldsAndExclude(dataFrame: DataFrame) {
function extractFields(dataFrame: DataFrame) {
return dataFrame.fields
.filter((field: Field & { typeInfo?: { frame: string } }) => {
const isFieldLokiLabels =
@ -203,72 +210,19 @@ function extractFieldsAndExclude(dataFrame: DataFrame) {
source: field.name,
},
},
// hide the field that was extracted
{
id: 'organize',
options: {
excludeByName: {
[field.name]: true,
},
},
},
];
});
}
function removeHiddenFields(dataFrame: DataFrame): Array<DataTransformerConfig | CustomTransformOperator> {
const transformations: Array<DataTransformerConfig | CustomTransformOperator> = [];
const hiddenFields = separateVisibleFields(dataFrame, { keepBody: true, keepTimestamp: true }).hidden;
hiddenFields.forEach((field: Field) => {
transformations.push({
id: 'organize',
options: {
excludeByName: {
[field.name]: true,
},
},
});
});
return transformations;
}
function buildLabelFilters(columnsWithMeta: Record<string, fieldNameMeta>, logsFrame: LogsFrame) {
// Create object of label filters to filter out any columns not selected by the user
function buildLabelFilters(columnsWithMeta: Record<string, fieldNameMeta>) {
// Create object of label filters to include columns selected by the user
let labelFilters: Record<string, true> = {};
Object.keys(columnsWithMeta)
.filter((key) => !columnsWithMeta[key].active)
.filter((key) => columnsWithMeta[key].active)
.forEach((key) => {
labelFilters[key] = true;
});
// We could be getting fresh data
const uniqueLabels = new Set<string>();
const logFrameLabels = logsFrame?.getLogFrameLabelsAsLabels();
// Populate the set with all labels from latest dataframe
logFrameLabels?.forEach((labels) => {
Object.keys(labels).forEach((label) => {
uniqueLabels.add(label);
});
});
// Check if there are labels in the data, that aren't yet in the labelFilters, and set them to be hidden by the transform
Object.keys(labelFilters).forEach((label) => {
if (!uniqueLabels.has(label)) {
labelFilters[label] = true;
}
});
// Check if there are labels in the label filters that aren't yet in the data, and set those to also be hidden
// The next time the column filters are synced any extras will be removed
Array.from(uniqueLabels).forEach((label) => {
if (label in columnsWithMeta && !columnsWithMeta[label]?.active) {
labelFilters[label] = true;
} else if (!labelFilters[label] && !(label in columnsWithMeta)) {
labelFilters[label] = true;
}
});
return labelFilters;
}
@ -277,7 +231,7 @@ function getLabelFiltersTransform(labelFilters: Record<string, true>) {
return {
id: 'organize',
options: {
excludeByName: labelFilters,
includeByName: labelFilters,
},
};
}

Loading…
Cancel
Save