diff --git a/.betterer.results b/.betterer.results index e861dbe1415..4018a113db2 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3276,8 +3276,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/explore/Logs/LogsMetaRow.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Styles should be written using objects.", "0"] ], "public/app/features/explore/Logs/LogsNavigation.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -6019,9 +6018,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "6"], [0, 0, 0, "Styles should be written using objects.", "7"] ], - "public/app/plugins/panel/logs/LogsPanel.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/panel/logs/types.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"] ], diff --git a/public/app/features/explore/Logs/LogsMetaRow.tsx b/public/app/features/explore/Logs/LogsMetaRow.tsx index 55f5bdacc02..b6608f69b20 100644 --- a/public/app/features/explore/Logs/LogsMetaRow.tsx +++ b/public/app/features/explore/Logs/LogsMetaRow.tsx @@ -13,13 +13,14 @@ import { transformDataFrame, DataTransformerConfig, CustomTransformOperator, + Labels, } from '@grafana/data'; import { DataFrame } from '@grafana/data/'; import { reportInteraction } from '@grafana/runtime'; import { Button, Dropdown, Menu, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui'; import { downloadDataFrameAsCsv, downloadLogsModelAsTxt } from '../../inspector/utils/download'; -import { LogLabels } from '../../logs/components/LogLabels'; +import { LogLabels, LogLabelsList } from '../../logs/components/LogLabels'; import { MAX_CHARACTERS } from '../../logs/components/LogRowMessage'; import { logRowsToReadableJson } from '../../logs/utils'; import { MetaInfoText, MetaItemProps } from '../MetaInfoText'; @@ -133,7 +134,7 @@ export const LogsMetaRow = React.memo( logsMetaItem.push( { label: 'Showing only selected fields', - value: renderMetaItem(displayedFields, LogsMetaKind.LabelsMap), + value: , }, { label: '', @@ -195,11 +196,16 @@ export const LogsMetaRow = React.memo( LogsMetaRow.displayName = 'LogsMetaRow'; -function renderMetaItem(value: any, kind: LogsMetaKind) { +function renderMetaItem(value: string | number | Labels, kind: LogsMetaKind) { + if (typeof value === 'string' || typeof value === 'number') { + return <>{value}; + } if (kind === LogsMetaKind.LabelsMap) { return ; - } else if (kind === LogsMetaKind.Error) { - return {value}; } - return value; + if (kind === LogsMetaKind.Error) { + return {value.toString()}; + } + console.error(`Meta type ${typeof value} ${value} not recognized.`); + return <>; } diff --git a/public/app/features/logs/components/LogLabels.test.tsx b/public/app/features/logs/components/LogLabels.test.tsx index e76459f0b65..9bfd070d9a7 100644 --- a/public/app/features/logs/components/LogLabels.test.tsx +++ b/public/app/features/logs/components/LogLabels.test.tsx @@ -1,27 +1,35 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { LogLabels } from './LogLabels'; +import { LogLabels, LogLabelsList } from './LogLabels'; describe('', () => { it('renders notice when no labels are found', () => { - render(); + render(); expect(screen.queryByText('(no unique labels)')).toBeInTheDocument(); }); it('renders labels', () => { render(); - expect(screen.queryByText('bar')).toBeInTheDocument(); - expect(screen.queryByText('42')).toBeInTheDocument(); + expect(screen.queryByText('foo=bar')).toBeInTheDocument(); + expect(screen.queryByText('baz=42')).toBeInTheDocument(); }); it('excludes labels with certain names or labels starting with underscore', () => { render(); - expect(screen.queryByText('bar')).toBeInTheDocument(); - expect(screen.queryByText('42')).not.toBeInTheDocument(); + expect(screen.queryByText('foo=bar')).toBeInTheDocument(); + expect(screen.queryByText('level=42')).not.toBeInTheDocument(); expect(screen.queryByText('13')).not.toBeInTheDocument(); }); it('excludes labels with empty string values', () => { render(); - expect(screen.queryByText('bar')).toBeInTheDocument(); - expect(screen.queryByText('baz')).not.toBeInTheDocument(); + expect(screen.queryByText('foo=bar')).toBeInTheDocument(); + expect(screen.queryByText(/baz/)).not.toBeInTheDocument(); + }); +}); + +describe('', () => { + it('renders labels', () => { + render(); + expect(screen.queryByText('bar')).toBeInTheDocument(); + expect(screen.queryByText('42')).toBeInTheDocument(); }); }); diff --git a/public/app/features/logs/components/LogLabels.tsx b/public/app/features/logs/components/LogLabels.tsx index 3f76fda46dd..2305ad7e866 100644 --- a/public/app/features/logs/components/LogLabels.tsx +++ b/public/app/features/logs/components/LogLabels.tsx @@ -1,47 +1,90 @@ import { css, cx } from '@emotion/css'; -import React from 'react'; +import React, { useMemo } from 'react'; import { GrafanaTheme2, Labels } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; +import { Tooltip, useStyles2 } from '@grafana/ui'; // Levels are already encoded in color, filename is a Loki-ism const HIDDEN_LABELS = ['level', 'lvl', 'filename']; interface Props { labels: Labels; + emptyMessage?: string; } -export const LogLabels = ({ labels }: Props) => { +export const LogLabels = React.memo(({ labels, emptyMessage }: Props) => { const styles = useStyles2(getStyles); - const displayLabels = Object.keys(labels).filter((label) => !label.startsWith('_') && !HIDDEN_LABELS.includes(label)); + const displayLabels = useMemo( + () => + Object.keys(labels) + .filter((label) => !label.startsWith('_') && !HIDDEN_LABELS.includes(label)) + .sort(), + [labels] + ); - if (displayLabels.length === 0) { + if (displayLabels.length === 0 && emptyMessage) { return ( - (no unique labels) + {emptyMessage} ); } return ( - {displayLabels.sort().map((label) => { + {displayLabels.map((label) => { const value = labels[label]; if (!value) { return; } - const tooltip = `${label}: ${value}`; + const labelValue = `${label}=${value}`; return ( - - - {value} - - + + {labelValue} + ); })} ); -}; +}); +LogLabels.displayName = 'LogLabels'; + +interface LogLabelsArrayProps { + labels: string[]; +} + +export const LogLabelsList = React.memo(({ labels }: LogLabelsArrayProps) => { + const styles = useStyles2(getStyles); + return ( + + {labels.map((label) => ( + + {label} + + ))} + + ); +}); +LogLabelsList.displayName = 'LogLabelsList'; + +interface LogLabelProps { + styles: Record; + tooltip?: string; + children: JSX.Element | string; +} + +const LogLabel = React.forwardRef( + ({ styles, tooltip, children }: LogLabelProps, ref) => { + return ( + + + {children} + + + ); + } +); +LogLabel.displayName = 'LogLabel'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/public/app/plugins/panel/logs/LogsPanel.test.tsx b/public/app/plugins/panel/logs/LogsPanel.test.tsx index 7c3fdbf0726..2230f70dae1 100644 --- a/public/app/plugins/panel/logs/LogsPanel.test.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.test.tsx @@ -87,7 +87,7 @@ describe('LogsPanel', () => { options: { showCommonLabels: true, sortOrder: LogsSortOrder.Descending }, }); expect(await screen.findByText(/common labels:/i)).toBeInTheDocument(); - expect(container.firstChild?.childNodes[0].textContent).toMatch(/^Common labels:common_appcommon_job/); + expect(container.firstChild?.childNodes[0].textContent).toMatch(/^Common labels:app=common_appjob=common_job/); }); it('shows common labels on bottom when ascending sort order', async () => { const { container } = setup({ @@ -95,7 +95,7 @@ describe('LogsPanel', () => { options: { showCommonLabels: true, sortOrder: LogsSortOrder.Ascending }, }); expect(await screen.findByText(/common labels:/i)).toBeInTheDocument(); - expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:common_appcommon_job$/); + expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:app=common_appjob=common_job$/); }); it('does not show common labels when showCommonLabels is set to false', async () => { setup({ data: { series: seriesWithCommonLabels }, options: { showCommonLabels: false } }); diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 8069088e533..40aa8dab3f0 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -39,6 +39,8 @@ interface LogsPermalinkUrlState { }; } +const noCommonLabels: Labels = {}; + export const LogsPanel = ({ data, timeZone, @@ -225,7 +227,10 @@ export const LogsPanel = ({ const renderCommonLabels = () => (
Common labels: - +
);