mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
LogRowMessageDisplayedFields: increase rendering performance (#84407)
* getAllFields: refactor for improved performance * LogRowMessageDisplayedFields: refactor line construction for performance * AsyncIconButton: refactor to prevent infinite loops
This commit is contained in:
parent
a7c7a1ffed
commit
3e97999ac5
@ -1,7 +1,7 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import memoizeOne from 'memoize-one';
|
import memoizeOne from 'memoize-one';
|
||||||
import React, { PureComponent, useState } from 'react';
|
import React, { PureComponent, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CoreApp,
|
CoreApp,
|
||||||
@ -290,7 +290,8 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
<AsyncIconButton
|
<AsyncIconButton
|
||||||
name="search-plus"
|
name="search-plus"
|
||||||
onClick={this.filterLabel}
|
onClick={this.filterLabel}
|
||||||
isActive={this.isFilterLabelActive}
|
// We purposely want to pass a new function on every render to allow the active state to be updated when log details remains open between updates.
|
||||||
|
isActive={() => this.isFilterLabelActive()}
|
||||||
tooltipSuffix={refIdTooltip}
|
tooltipSuffix={refIdTooltip}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -368,11 +369,9 @@ const AsyncIconButton = ({ isActive, tooltipSuffix, ...rest }: AsyncIconButtonPr
|
|||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
const tooltip = active ? 'Remove filter' : 'Filter for value';
|
const tooltip = active ? 'Remove filter' : 'Filter for value';
|
||||||
|
|
||||||
/**
|
useEffect(() => {
|
||||||
* We purposely want to run this on every render to allow the active state to be updated
|
|
||||||
* when log details remains open between updates.
|
|
||||||
*/
|
|
||||||
isActive().then(setActive);
|
isActive().then(setActive);
|
||||||
|
}, [isActive]);
|
||||||
|
|
||||||
return <IconButton {...rest} variant={active ? 'primary' : undefined} tooltip={tooltip + tooltipSuffix} />;
|
return <IconButton {...rest} variant={active ? 'primary' : undefined} tooltip={tooltip + tooltipSuffix} />;
|
||||||
};
|
};
|
||||||
|
@ -25,32 +25,28 @@ export interface Props {
|
|||||||
|
|
||||||
export const LogRowMessageDisplayedFields = React.memo((props: Props) => {
|
export const LogRowMessageDisplayedFields = React.memo((props: Props) => {
|
||||||
const { row, detectedFields, getFieldLinks, wrapLogMessage, styles, mouseIsOver, pinned, ...rest } = props;
|
const { row, detectedFields, getFieldLinks, wrapLogMessage, styles, mouseIsOver, pinned, ...rest } = props;
|
||||||
const fields = getAllFields(row, getFieldLinks);
|
|
||||||
const wrapClassName = wrapLogMessage ? '' : displayedFieldsStyles.noWrap;
|
const wrapClassName = wrapLogMessage ? '' : displayedFieldsStyles.noWrap;
|
||||||
|
const fields = useMemo(() => getAllFields(row, getFieldLinks), [getFieldLinks, row]);
|
||||||
// only single key/value rows are filterable, so we only need the first field key for filtering
|
// only single key/value rows are filterable, so we only need the first field key for filtering
|
||||||
const line = useMemo(
|
const line = useMemo(() => {
|
||||||
() =>
|
let line = '';
|
||||||
detectedFields
|
for (let i = 0; i < detectedFields.length; i++) {
|
||||||
.map((parsedKey) => {
|
const parsedKey = detectedFields[i];
|
||||||
const field = fields.find((field) => {
|
const field = fields.find((field) => {
|
||||||
const { keys } = field;
|
const { keys } = field;
|
||||||
return keys[0] === parsedKey;
|
return keys[0] === parsedKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (field !== undefined && field !== null) {
|
if (field) {
|
||||||
return `${parsedKey}=${field.values}`;
|
line += ` ${parsedKey}=${field.values}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.labels[parsedKey] !== undefined && row.labels[parsedKey] !== null) {
|
if (row.labels[parsedKey] !== undefined && row.labels[parsedKey] !== null) {
|
||||||
return `${parsedKey}=${row.labels[parsedKey]}`;
|
line += ` ${parsedKey}=${row.labels[parsedKey]}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return line.trimStart();
|
||||||
})
|
}, [detectedFields, fields, row.labels]);
|
||||||
.filter((s) => s !== null)
|
|
||||||
.join(' '),
|
|
||||||
[detectedFields, fields, row.labels]
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldShowMenu = useMemo(() => mouseIsOver || pinned, [mouseIsOver, pinned]);
|
const shouldShowMenu = useMemo(() => mouseIsOver || pinned, [mouseIsOver, pinned]);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { partition } from 'lodash';
|
import { partition } from 'lodash';
|
||||||
import memoizeOne from 'memoize-one';
|
|
||||||
|
|
||||||
import { DataFrame, Field, FieldWithIndex, LinkModel, LogRowModel } from '@grafana/data';
|
import { DataFrame, Field, FieldWithIndex, LinkModel, LogRowModel } from '@grafana/data';
|
||||||
import { safeStringifyValue } from 'app/core/utils/explore';
|
import { safeStringifyValue } from 'app/core/utils/explore';
|
||||||
@ -18,8 +17,7 @@ export type FieldDef = {
|
|||||||
* Returns all fields for log row which consists of fields we parse from the message itself and additional fields
|
* Returns all fields for log row which consists of fields we parse from the message itself and additional fields
|
||||||
* found in the dataframe (they may contain links).
|
* found in the dataframe (they may contain links).
|
||||||
*/
|
*/
|
||||||
export const getAllFields = memoizeOne(
|
export const getAllFields = (
|
||||||
(
|
|
||||||
row: LogRowModel,
|
row: LogRowModel,
|
||||||
getFieldLinks?: (
|
getFieldLinks?: (
|
||||||
field: Field,
|
field: Field,
|
||||||
@ -27,17 +25,14 @@ export const getAllFields = memoizeOne(
|
|||||||
dataFrame: DataFrame
|
dataFrame: DataFrame
|
||||||
) => Array<LinkModel<Field>> | ExploreFieldLinkModel[]
|
) => Array<LinkModel<Field>> | ExploreFieldLinkModel[]
|
||||||
) => {
|
) => {
|
||||||
const dataframeFields = getDataframeFields(row, getFieldLinks);
|
return getDataframeFields(row, getFieldLinks);
|
||||||
|
};
|
||||||
return Object.values(dataframeFields);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A log line may contain many links that would all need to go on their own logs detail row
|
* A log line may contain many links that would all need to go on their own logs detail row
|
||||||
* This iterates through and creates a FieldDef (row) per link.
|
* This iterates through and creates a FieldDef (row) per link.
|
||||||
*/
|
*/
|
||||||
export const createLogLineLinks = memoizeOne((hiddenFieldsWithLinks: FieldDef[]): FieldDef[] => {
|
export const createLogLineLinks = (hiddenFieldsWithLinks: FieldDef[]): FieldDef[] => {
|
||||||
let fieldsWithLinksFromVariableMap: FieldDef[] = [];
|
let fieldsWithLinksFromVariableMap: FieldDef[] = [];
|
||||||
hiddenFieldsWithLinks.forEach((linkField) => {
|
hiddenFieldsWithLinks.forEach((linkField) => {
|
||||||
linkField.links?.forEach((link: ExploreFieldLinkModel) => {
|
linkField.links?.forEach((link: ExploreFieldLinkModel) => {
|
||||||
@ -58,25 +53,21 @@ export const createLogLineLinks = memoizeOne((hiddenFieldsWithLinks: FieldDef[])
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
return fieldsWithLinksFromVariableMap;
|
return fieldsWithLinksFromVariableMap;
|
||||||
});
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* creates fields from the dataframe-fields, adding data-links, when field.config.links exists
|
* creates fields from the dataframe-fields, adding data-links, when field.config.links exists
|
||||||
*/
|
*/
|
||||||
export const getDataframeFields = memoizeOne(
|
export const getDataframeFields = (
|
||||||
(
|
|
||||||
row: LogRowModel,
|
row: LogRowModel,
|
||||||
getFieldLinks?: (field: Field, rowIndex: number, dataFrame: DataFrame) => Array<LinkModel<Field>>
|
getFieldLinks?: (field: Field, rowIndex: number, dataFrame: DataFrame) => Array<LinkModel<Field>>
|
||||||
): FieldDef[] => {
|
): FieldDef[] => {
|
||||||
const visibleFields = separateVisibleFields(row.dataFrame).visible;
|
const nonEmptyVisibleFields = getNonEmptyVisibleFields(row);
|
||||||
const nonEmptyVisibleFields = visibleFields.filter((f) => f.values[row.rowIndex] != null);
|
|
||||||
return nonEmptyVisibleFields.map((field) => {
|
return nonEmptyVisibleFields.map((field) => {
|
||||||
const links = getFieldLinks ? getFieldLinks(field, row.rowIndex, row.dataFrame) : [];
|
const links = getFieldLinks ? getFieldLinks(field, row.rowIndex, row.dataFrame) : [];
|
||||||
const fieldVal = field.values[row.rowIndex];
|
const fieldVal = field.values[row.rowIndex];
|
||||||
const outputVal =
|
const outputVal =
|
||||||
typeof fieldVal === 'string' || typeof fieldVal === 'number'
|
typeof fieldVal === 'string' || typeof fieldVal === 'number' ? fieldVal.toString() : safeStringifyValue(fieldVal);
|
||||||
? fieldVal.toString()
|
|
||||||
: safeStringifyValue(fieldVal);
|
|
||||||
return {
|
return {
|
||||||
keys: [field.name],
|
keys: [field.name],
|
||||||
values: [outputVal],
|
values: [outputVal],
|
||||||
@ -84,8 +75,7 @@ export const getDataframeFields = memoizeOne(
|
|||||||
fieldIndex: field.index,
|
fieldIndex: field.index,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
type VisOptions = {
|
type VisOptions = {
|
||||||
keepTimestamp?: boolean;
|
keepTimestamp?: boolean;
|
||||||
@ -148,3 +138,27 @@ export function separateVisibleFields(
|
|||||||
|
|
||||||
return { visible, hidden };
|
return { visible, hidden };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimized version of separateVisibleFields() to only return visible fields for getAllFields()
|
||||||
|
function getNonEmptyVisibleFields(row: LogRowModel, opts?: VisOptions): FieldWithIndex[] {
|
||||||
|
const frame = row.dataFrame;
|
||||||
|
const visibleFieldIndices = getVisibleFieldIndices(frame, opts ?? {});
|
||||||
|
const visibleFields: FieldWithIndex[] = [];
|
||||||
|
for (let index = 0; index < frame.fields.length; index++) {
|
||||||
|
const field = frame.fields[index];
|
||||||
|
// ignore empty fields
|
||||||
|
if (field.values[row.rowIndex] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// hidden fields are always hidden
|
||||||
|
if (field.config.custom?.hidden) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fields with data-links are visible
|
||||||
|
if ((field.config.links && field.config.links.length > 0) || visibleFieldIndices.has(index)) {
|
||||||
|
visibleFields.push({ ...field, index });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visibleFields;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user