mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
Allow toggling of derived fields (#27148)
This commit is contained in:
parent
f5ee1f93cd
commit
6a01bab67c
@ -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';
|
||||
|
@ -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}
|
||||
|
@ -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 => {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user