Loki: Added support to show label types in Log Details (#97284)
* Log Details: add support to resolve label types if supported * Update public/app/features/logs/utils.ts Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> * LogDetailsRow: create component to display label type * Remove log * Add missing index resolution for label type values * Make type icon less prominent * Update new icon styles * Formatting * Update public/app/features/logs/utils.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Update public/app/features/logs/utils.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * LogDetails: update test * Formatting --------- Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
co-authored by
Galen Kistler
Sven Grossmann
parent
7b934f0d2b
commit
ebfa06e040
@@ -295,4 +295,89 @@ describe('LogDetails', () => {
|
||||
expect(screen.getByText('shouldShowLinkName')).toBeInTheDocument();
|
||||
expect(screen.getByText('shouldShowLinkValue')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Label types', () => {
|
||||
const entry = 'test';
|
||||
const labels = {
|
||||
label1: 'value1',
|
||||
label2: 'value2',
|
||||
label3: 'value3',
|
||||
};
|
||||
const dataFrame = createDataFrame({
|
||||
fields: [
|
||||
{ name: 'timestamp', config: {}, type: FieldType.time, values: [1] },
|
||||
{ name: 'body', type: FieldType.string, values: [entry] },
|
||||
{ name: 'id', type: FieldType.string, values: ['1'] },
|
||||
{
|
||||
name: 'labels',
|
||||
type: FieldType.other,
|
||||
values: [labels],
|
||||
},
|
||||
{
|
||||
name: 'labelTypes',
|
||||
type: FieldType.other,
|
||||
values: [
|
||||
{
|
||||
label1: 'I',
|
||||
label2: 'S',
|
||||
label3: 'P',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
type: DataFrameType.LogLines,
|
||||
},
|
||||
});
|
||||
it('should show label types if they are available and supported', () => {
|
||||
setup(
|
||||
{},
|
||||
{
|
||||
entry,
|
||||
dataFrame,
|
||||
entryFieldIndex: 0,
|
||||
rowIndex: 0,
|
||||
labels,
|
||||
datasourceType: 'loki',
|
||||
rowId: '1',
|
||||
}
|
||||
);
|
||||
|
||||
// Show labels and links
|
||||
expect(screen.getByText('label1')).toBeInTheDocument();
|
||||
expect(screen.getByText('value1')).toBeInTheDocument();
|
||||
expect(screen.getByText('label2')).toBeInTheDocument();
|
||||
expect(screen.getByText('value2')).toBeInTheDocument();
|
||||
expect(screen.getByText('label3')).toBeInTheDocument();
|
||||
expect(screen.getByText('value3')).toBeInTheDocument();
|
||||
expect(screen.getByText('I')).toBeInTheDocument();
|
||||
expect(screen.getByText('S')).toBeInTheDocument();
|
||||
expect(screen.getByText('P')).toBeInTheDocument();
|
||||
});
|
||||
it('should not show label types if they are unavailable or not supported', () => {
|
||||
setup(
|
||||
{},
|
||||
{
|
||||
entry,
|
||||
dataFrame,
|
||||
entryFieldIndex: 0,
|
||||
rowIndex: 0,
|
||||
labels,
|
||||
datasourceType: 'other datasource',
|
||||
rowId: '1',
|
||||
}
|
||||
);
|
||||
|
||||
// Show labels and links
|
||||
expect(screen.getByText('label1')).toBeInTheDocument();
|
||||
expect(screen.getByText('value1')).toBeInTheDocument();
|
||||
expect(screen.getByText('label2')).toBeInTheDocument();
|
||||
expect(screen.getByText('value2')).toBeInTheDocument();
|
||||
expect(screen.getByText('label3')).toBeInTheDocument();
|
||||
expect(screen.getByText('value3')).toBeInTheDocument();
|
||||
expect(screen.queryByText('I')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('S')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('P')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,9 +15,18 @@ import {
|
||||
LogRowModel,
|
||||
} from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { ClipboardButton, DataLinkButton, IconButton, PopoverContent, Themeable2, withTheme2 } from '@grafana/ui';
|
||||
import {
|
||||
ClipboardButton,
|
||||
DataLinkButton,
|
||||
IconButton,
|
||||
PopoverContent,
|
||||
Themeable2,
|
||||
Tooltip,
|
||||
withTheme2,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { logRowToSingleRowDataFrame } from '../logsModel';
|
||||
import { getLabelTypeFromRow } from '../utils';
|
||||
|
||||
import { LogLabelStats } from './LogLabelStats';
|
||||
import { getLogRowStyles } from './getLogRowStyles';
|
||||
@@ -50,6 +59,19 @@ interface State {
|
||||
|
||||
const getStyles = memoizeOne((theme: GrafanaTheme2) => {
|
||||
return {
|
||||
labelType: css({
|
||||
border: `solid 1px ${theme.colors.text.secondary}`,
|
||||
color: theme.colors.text.secondary,
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
fontSize: theme.spacing(1),
|
||||
lineHeight: theme.spacing(1.25),
|
||||
height: theme.spacing(1.5),
|
||||
width: theme.spacing(1.5),
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
verticalAlign: 'middle',
|
||||
marginLeft: theme.spacing(1),
|
||||
}),
|
||||
wordBreakAll: css({
|
||||
label: 'wordBreakAll',
|
||||
wordBreak: 'break-all',
|
||||
@@ -275,6 +297,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
const singleVal = parsedValues == null ? false : parsedValues.length === 1;
|
||||
const hasFilteringFunctionality = !disableActions && onClickFilterLabel && onClickFilterOutLabel;
|
||||
const refIdTooltip = app === CoreApp.Explore && row.dataFrame?.refId ? ` in query ${row.dataFrame?.refId}` : '';
|
||||
const labelType = singleKey ? getLabelTypeFromRow(parsedKeys[0], row) : null;
|
||||
|
||||
const isMultiParsedValueWithNoContent =
|
||||
!singleVal && parsedValues != null && !parsedValues.every((val) => val === '');
|
||||
@@ -321,6 +344,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>{labelType && <LabelTypeBadge type={labelType} styles={styles} />}</td>
|
||||
{/* Key - value columns */}
|
||||
<td className={rowStyles.logDetailsLabel}>{singleKey ? parsedKeys[0] : this.generateMultiVal(parsedKeys)}</td>
|
||||
<td className={cx(styles.wordBreakAll, wrapLogMessage && styles.wrapLine)}>
|
||||
@@ -360,7 +384,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
</tr>
|
||||
{showFieldsStats && singleKey && singleVal && (
|
||||
<tr>
|
||||
<td>
|
||||
<td colSpan={2}>
|
||||
<IconButton
|
||||
variant={showFieldsStats ? 'primary' : 'secondary'}
|
||||
name="signal"
|
||||
@@ -386,6 +410,16 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
function LabelTypeBadge({ type, styles }: { type: string; styles: ReturnType<typeof getStyles> }) {
|
||||
return (
|
||||
<Tooltip content={type}>
|
||||
<div className={styles.labelType}>
|
||||
<span>{type.substring(0, 1)}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
interface AsyncIconButtonProps extends Pick<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> {
|
||||
name: IconName;
|
||||
isActive(): Promise<boolean>;
|
||||
|
||||
@@ -343,3 +343,48 @@ export function createLogRowsMap() {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function getLabelTypeFromFrame(labelKey: string, frame: DataFrame, index: number): null | string {
|
||||
const typeField = frame.fields.find((field) => field.name === 'labelTypes')?.values[index];
|
||||
if (!typeField) {
|
||||
return null;
|
||||
}
|
||||
return typeField[labelKey] ?? null;
|
||||
}
|
||||
|
||||
export function getLabelTypeFromRow(label: string, row: LogRowModel) {
|
||||
if (!row.datasourceType) {
|
||||
return null;
|
||||
}
|
||||
const idField = row.dataFrame.fields.find((field) => field.name === 'id');
|
||||
if (!idField) {
|
||||
return null;
|
||||
}
|
||||
const rowIndex = idField.values.findIndex((id) => id === row.rowId);
|
||||
if (rowIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
const labelType = getLabelTypeFromFrame(label, row.dataFrame, rowIndex);
|
||||
if (!labelType) {
|
||||
return null;
|
||||
}
|
||||
return getDataSourceLabelType(labelType, row.datasourceType);
|
||||
}
|
||||
|
||||
function getDataSourceLabelType(labelType: string, datasourceType: string) {
|
||||
switch (datasourceType) {
|
||||
case 'loki':
|
||||
switch (labelType) {
|
||||
case 'I':
|
||||
return 'Indexed label';
|
||||
case 'S':
|
||||
return 'Structured metadata';
|
||||
case 'P':
|
||||
return 'Parsed label';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user