Allow toggling of derived fields (#27148)

This commit is contained in:
Fredrik Enestad 2020-08-25 11:37:19 +02:00 committed by GitHub
parent f5ee1f93cd
commit 6a01bab67c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 75 deletions

View File

@ -18,7 +18,7 @@ import { getLogRowStyles } from './getLogRowStyles';
import { stylesFactory } from '../../themes/stylesFactory';
import { selectThemeVariant } from '../../themes/selectThemeVariant';
import { parseMessage, FieldDef } from './logParser';
import { getAllFields } from './logParser';
//Components
import { LogDetailsRow } from './LogDetailsRow';
@ -61,61 +61,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
class UnThemedLogDetails extends PureComponent<Props> {
getParser = memoizeOne(getParser);
getDerivedFields = memoizeOne((row: LogRowModel): FieldDef[] => {
return (
row.dataFrame.fields
.map((field, index) => ({ ...field, index }))
// Remove Id which we use for react key and entry field which we are showing as the log message. Also remove hidden fields.
.filter(
(field, index) => !('id' === field.name || row.entryFieldIndex === index || field.config.custom?.hidden)
)
// Filter out fields without values. For example in elastic the fields are parsed from the document which can
// have different structure per row and so the dataframe is pretty sparse.
.filter(field => {
const value = field.values.get(row.rowIndex);
// Not sure exactly what will be the empty value here. And we want to keep 0 as some values can be non
// string.
return value !== null && value !== undefined;
})
.map(field => {
const { getFieldLinks } = this.props;
const links = getFieldLinks ? getFieldLinks(field, row.rowIndex) : [];
return {
key: field.name,
value: field.values.get(row.rowIndex).toString(),
links: links,
fieldIndex: field.index,
};
})
);
});
/**
* Returns all fields for log row which consists of fields we parse from the message itself and any derived fields
* setup in data source config.
*/
getAllFields = memoizeOne((row: LogRowModel) => {
const fields = parseMessage(row.entry);
const derivedFields = this.getDerivedFields(row);
const fieldsMap = [...derivedFields, ...fields].reduce((acc, field) => {
// Strip enclosing quotes for hashing. When values are parsed from log line the quotes are kept, but if same
// value is in the dataFrame it will be without the quotes. We treat them here as the same value.
const value = field.value.replace(/(^")|("$)/g, '');
const fieldHash = `${field.key}=${value}`;
if (acc[fieldHash]) {
acc[fieldHash].links = [...(acc[fieldHash].links || []), ...(field.links || [])];
} else {
acc[fieldHash] = field;
}
return acc;
}, {} as { [key: string]: FieldDef });
const allFields = Object.values(fieldsMap);
allFields.sort(sortFieldsLinkFirst);
return allFields;
});
getStatsForParsedField = (key: string) => {
const matcher = this.getParser(this.props.row.entry)!.buildMatcher(key);
return calculateFieldStats(this.props.getRows(), matcher);
@ -135,12 +80,13 @@ class UnThemedLogDetails extends PureComponent<Props> {
onClickShowParsedField,
onClickHideParsedField,
showParsedFields,
getFieldLinks,
} = this.props;
const style = getLogRowStyles(theme, row.logLevel);
const styles = getStyles(theme);
const labels = row.labels ? row.labels : {};
const labelsAvailable = Object.keys(labels).length > 0;
const fields = this.getAllFields(row);
const fields = getAllFields(row, getFieldLinks);
const parsedFieldsAvailable = fields && fields.length > 0;
return (
@ -219,15 +165,5 @@ class UnThemedLogDetails extends PureComponent<Props> {
}
}
function sortFieldsLinkFirst(fieldA: FieldDef, fieldB: FieldDef) {
if (fieldA.links?.length && !fieldB.links?.length) {
return -1;
}
if (!fieldA.links?.length && fieldB.links?.length) {
return 1;
}
return fieldA.key > fieldB.key ? 1 : fieldA.key < fieldB.key ? -1 : 0;
}
export const LogDetails = withTheme(UnThemedLogDetails);
LogDetails.displayName = 'LogDetails';

View File

@ -183,7 +183,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
</td>
)}
{showParsedFields && showParsedFields.length > 0 ? (
<LogRowMessageParsed row={row} showParsedFields={showParsedFields!} />
<LogRowMessageParsed row={row} showParsedFields={showParsedFields!} getFieldLinks={getFieldLinks} />
) : (
<LogRowMessage
highlighterExpressions={highlighterExpressions}

View File

@ -1,20 +1,21 @@
import React, { PureComponent } from 'react';
import { LogRowModel } from '@grafana/data';
import { LogRowModel, Field, LinkModel } from '@grafana/data';
import { Themeable } from '../../types/theme';
import { withTheme } from '../../themes/index';
import { parseMessage } from './logParser';
import { getAllFields } from './logParser';
export interface Props extends Themeable {
row: LogRowModel;
showParsedFields: string[];
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
}
class UnThemedLogRowMessageParsed extends PureComponent<Props> {
render() {
const { row, showParsedFields } = this.props;
const fields = parseMessage(row.entry);
const { row, showParsedFields, getFieldLinks } = this.props;
const fields = getAllFields(row, getFieldLinks);
const line = showParsedFields
.map(parsedKey => {

View File

@ -1,18 +1,46 @@
import { Field, getParser, LinkModel } from '@grafana/data';
import { Field, getParser, LinkModel, LogRowModel } from '@grafana/data';
import memoizeOne from 'memoize-one';
import { MAX_CHARACTERS } from './LogRowMessage';
const memoizedGetParser = memoizeOne(getParser);
export type FieldDef = {
type FieldDef = {
key: string;
value: string;
links?: Array<LinkModel<Field>>;
fieldIndex?: number;
};
export const parseMessage = memoizeOne((rowEntry): FieldDef[] => {
/**
* Returns all fields for log row which consists of fields we parse from the message itself and any derived fields
* setup in data source config.
*/
export const getAllFields = memoizeOne(
(row: LogRowModel, getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>) => {
const fields = parseMessage(row.entry);
const derivedFields = getDerivedFields(row, getFieldLinks);
const fieldsMap = [...derivedFields, ...fields].reduce((acc, field) => {
// Strip enclosing quotes for hashing. When values are parsed from log line the quotes are kept, but if same
// value is in the dataFrame it will be without the quotes. We treat them here as the same value.
const value = field.value.replace(/(^")|("$)/g, '');
const fieldHash = `${field.key}=${value}`;
if (acc[fieldHash]) {
acc[fieldHash].links = [...(acc[fieldHash].links || []), ...(field.links || [])];
} else {
acc[fieldHash] = field;
}
return acc;
}, {} as { [key: string]: FieldDef });
const allFields = Object.values(fieldsMap);
allFields.sort(sortFieldsLinkFirst);
return allFields;
}
);
const parseMessage = memoizeOne((rowEntry): FieldDef[] => {
if (rowEntry.length > MAX_CHARACTERS) {
return [];
}
@ -30,3 +58,43 @@ export const parseMessage = memoizeOne((rowEntry): FieldDef[] => {
return fields;
});
const getDerivedFields = memoizeOne(
(row: LogRowModel, getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>): FieldDef[] => {
return (
row.dataFrame.fields
.map((field, index) => ({ ...field, index }))
// Remove Id which we use for react key and entry field which we are showing as the log message. Also remove hidden fields.
.filter(
(field, index) => !('id' === field.name || row.entryFieldIndex === index || field.config.custom?.hidden)
)
// Filter out fields without values. For example in elastic the fields are parsed from the document which can
// have different structure per row and so the dataframe is pretty sparse.
.filter(field => {
const value = field.values.get(row.rowIndex);
// Not sure exactly what will be the empty value here. And we want to keep 0 as some values can be non
// string.
return value !== null && value !== undefined;
})
.map(field => {
const links = getFieldLinks ? getFieldLinks(field, row.rowIndex) : [];
return {
key: field.name,
value: field.values.get(row.rowIndex).toString(),
links: links,
fieldIndex: field.index,
};
})
);
}
);
function sortFieldsLinkFirst(fieldA: FieldDef, fieldB: FieldDef) {
if (fieldA.links?.length && !fieldB.links?.length) {
return -1;
}
if (!fieldA.links?.length && fieldB.links?.length) {
return 1;
}
return fieldA.key > fieldB.key ? 1 : fieldA.key < fieldB.key ? -1 : 0;
}