From a40aef0822c27fa9d145e8410f3b7979feb8446b Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 18 May 2021 11:16:29 +0200 Subject: [PATCH] Logs panel: Support details view (#34125) * Show log details in Logs panel * Add hide log details as panel option * Refactor tests to use testing library * Change hideLogDetails to enableLogsDetails * Add enableLogDetails to test file --- .../components/Logs/LogDetailsRow.test.tsx | 73 +++++++++++++------ .../src/components/Logs/LogDetailsRow.tsx | 30 ++++++-- .../src/components/Logs/LogLabelStats.tsx | 2 +- .../grafana-ui/src/components/Logs/LogRow.tsx | 8 +- .../src/components/Logs/LogRows.test.tsx | 6 ++ .../src/components/Logs/LogRows.tsx | 8 +- public/app/features/explore/Logs.tsx | 1 + public/app/plugins/panel/logs/LogsPanel.tsx | 12 ++- public/app/plugins/panel/logs/module.tsx | 6 ++ public/app/plugins/panel/logs/types.ts | 1 + 10 files changed, 104 insertions(+), 43 deletions(-) diff --git a/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx b/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx index 3509c4a6093..4c1243f4161 100644 --- a/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx @@ -1,12 +1,11 @@ -import React from 'react'; -import { LogDetailsRow, Props } from './LogDetailsRow'; -import { GrafanaTheme } from '@grafana/data'; -import { mount } from 'enzyme'; -import { LogLabelStats } from './LogLabelStats'; +import React, { ComponentProps } from 'react'; +import { screen, render, fireEvent } from '@testing-library/react'; +import { LogDetailsRow } from './LogDetailsRow'; + +type Props = ComponentProps; const setup = (propOverrides?: Partial) => { const props: Props = { - theme: {} as GrafanaTheme, parsedValue: '', parsedKey: '', isLabel: true, @@ -14,40 +13,67 @@ const setup = (propOverrides?: Partial) => { getStats: () => null, onClickFilterLabel: () => {}, onClickFilterOutLabel: () => {}, + onClickShowDetectedField: () => {}, + onClickHideDetectedField: () => {}, + showDetectedFields: [], }; Object.assign(props, propOverrides); - const wrapper = mount(); - return wrapper; + return render(); }; describe('LogDetailsRow', () => { it('should render parsed key', () => { - const wrapper = setup({ parsedKey: 'test key' }); - expect(wrapper.text().includes('test key')).toBe(true); + setup({ parsedKey: 'test key' }); + expect(screen.getByText('test key')).toBeInTheDocument(); }); it('should render parsed value', () => { - const wrapper = setup({ parsedValue: 'test value' }); - expect(wrapper.text().includes('test value')).toBe(true); + setup({ parsedValue: 'test value' }); + expect(screen.getByText('test value')).toBeInTheDocument(); }); + it('should render metrics button', () => { - const wrapper = setup(); - expect(wrapper.find({ title: 'Ad-hoc statistics' }).hostNodes()).toHaveLength(1); + setup(); + expect(screen.getAllByTitle('Ad-hoc statistics')).toHaveLength(1); }); + describe('if props is a label', () => { it('should render filter label button', () => { - const wrapper = setup(); - expect(wrapper.find({ title: 'Filter for value' }).hostNodes()).toHaveLength(1); + setup(); + expect(screen.getAllByTitle('Filter for value')).toHaveLength(1); }); it('should render filter out label button', () => { - const wrapper = setup(); - expect(wrapper.find({ title: 'Filter out value' }).hostNodes()).toHaveLength(1); + setup(); + expect(screen.getAllByTitle('Filter out value')).toHaveLength(1); + }); + it('should not render filtering buttons if no filtering functions provided', () => { + setup({ onClickFilterLabel: undefined, onClickFilterOutLabel: undefined }); + expect(screen.queryByTitle('Filter out value')).not.toBeInTheDocument(); + }); + }); + + describe('if props is not a label', () => { + it('should not render a filter label button', () => { + setup({ isLabel: false }); + expect(screen.queryByTitle('Filter for value')).not.toBeInTheDocument(); + }); + it('should render a show toggleFieldButton button', () => { + setup({ isLabel: false }); + expect(screen.getAllByTitle('Show this field instead of the message')).toHaveLength(1); + }); + it('should not render a show toggleFieldButton button if no detected fields toggling functions provided', () => { + setup({ + isLabel: false, + onClickShowDetectedField: undefined, + onClickHideDetectedField: undefined, + }); + expect(screen.queryByTitle('Show this field instead of the message')).not.toBeInTheDocument(); }); }); it('should render stats when stats icon is clicked', () => { - const wrapper = setup({ + setup({ parsedKey: 'key', parsedValue: 'value', getStats: () => { @@ -66,9 +92,10 @@ describe('LogDetailsRow', () => { }, }); - expect(wrapper.find(LogLabelStats).length).toBe(0); - wrapper.find({ title: 'Ad-hoc statistics' }).hostNodes().simulate('click'); - expect(wrapper.find(LogLabelStats).length).toBe(1); - expect(wrapper.find(LogLabelStats).contains('another value')).toBeTruthy(); + expect(screen.queryByTestId('logLabelStats')).not.toBeInTheDocument(); + const adHocStatsButton = screen.getByTitle('Ad-hoc statistics'); + fireEvent.click(adHocStatsButton); + expect(screen.getByTestId('logLabelStats')).toBeInTheDocument(); + expect(screen.getByTestId('logLabelStats')).toHaveTextContent('another value'); }); }); diff --git a/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx b/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx index a28da1be239..c96c900df47 100644 --- a/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx @@ -112,10 +112,26 @@ class UnThemedLogDetailsRow extends PureComponent { } render() { - const { theme, parsedKey, parsedValue, isLabel, links, showDetectedFields, wrapLogMessage } = this.props; + const { + theme, + parsedKey, + parsedValue, + isLabel, + links, + showDetectedFields, + wrapLogMessage, + onClickShowDetectedField, + onClickHideDetectedField, + onClickFilterLabel, + onClickFilterOutLabel, + } = this.props; const { showFieldsStats, fieldStats, fieldCount } = this.state; const styles = getStyles(theme); const style = getLogRowStyles(theme); + + const hasDetectedFieldsFunctionality = onClickShowDetectedField && onClickHideDetectedField; + const hasFilteringFunctionality = onClickFilterLabel && onClickFilterOutLabel; + const toggleFieldButton = !isLabel && showDetectedFields && showDetectedFields.includes(parsedKey) ? ( @@ -130,7 +146,7 @@ class UnThemedLogDetailsRow extends PureComponent { - {isLabel && ( + {hasFilteringFunctionality && isLabel && ( <> @@ -141,12 +157,10 @@ class UnThemedLogDetailsRow extends PureComponent { )} - {!isLabel && ( - <> - - {toggleFieldButton} - - + {hasDetectedFieldsFunctionality && !isLabel && ( + + {toggleFieldButton} + )} {/* Key - value columns */} diff --git a/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx b/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx index 1de7efc4abc..1130cc766e3 100644 --- a/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx +++ b/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx @@ -74,7 +74,7 @@ class UnThemedLogLabelStats extends PureComponent { const otherProportion = otherCount / total; return ( - +
{label}: {total} of {rowCount} rows have that {isLabel ? 'label' : 'field'} diff --git a/packages/grafana-ui/src/components/Logs/LogRow.tsx b/packages/grafana-ui/src/components/Logs/LogRow.tsx index dd019cd53d6..6cc46795fe6 100644 --- a/packages/grafana-ui/src/components/Logs/LogRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRow.tsx @@ -41,7 +41,7 @@ interface Props extends Themeable { showTime: boolean; wrapLogMessage: boolean; timeZone: TimeZone; - allowDetails?: boolean; + enableLogDetails: boolean; logsSortOrder?: LogsSortOrder | null; forceEscape?: boolean; showDetectedFields?: string[]; @@ -102,7 +102,7 @@ class UnThemedLogRow extends PureComponent { }; toggleDetails = () => { - if (this.props.allowDetails) { + if (!this.props.enableLogDetails) { return; } this.setState((state) => { @@ -131,7 +131,7 @@ class UnThemedLogRow extends PureComponent { onClickShowDetectedField, onClickHideDetectedField, highlighterExpressions, - allowDetails, + enableLogDetails, row, showDuplicates, showContextToggle, @@ -171,7 +171,7 @@ class UnThemedLogRow extends PureComponent { )} - {!allowDetails && ( + {enableLogDetails && ( diff --git a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx index 95301d20c0d..e473f057a67 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx @@ -17,6 +17,7 @@ describe('LogRows', () => { showTime={false} wrapLogMessage={true} timeZone={'utc'} + enableLogDetails={true} /> ); @@ -39,6 +40,7 @@ describe('LogRows', () => { wrapLogMessage={true} timeZone={'utc'} previewLimit={1} + enableLogDetails={true} /> ); @@ -68,6 +70,7 @@ describe('LogRows', () => { showTime={false} wrapLogMessage={true} timeZone={'utc'} + enableLogDetails={true} /> ); @@ -88,6 +91,7 @@ describe('LogRows', () => { showTime={false} wrapLogMessage={true} timeZone={'utc'} + enableLogDetails={true} /> ); @@ -110,6 +114,7 @@ describe('LogRows', () => { wrapLogMessage={true} timeZone={'utc'} logsSortOrder={LogsSortOrder.Ascending} + enableLogDetails={true} /> ); @@ -133,6 +138,7 @@ describe('LogRows', () => { wrapLogMessage={true} timeZone={'utc'} logsSortOrder={LogsSortOrder.Descending} + enableLogDetails={true} /> ); diff --git a/packages/grafana-ui/src/components/Logs/LogRows.tsx b/packages/grafana-ui/src/components/Logs/LogRows.tsx index bba14cc56ee..2d2946769af 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.tsx @@ -21,8 +21,8 @@ export interface Props extends Themeable { showTime: boolean; wrapLogMessage: boolean; timeZone: TimeZone; + enableLogDetails: boolean; logsSortOrder?: LogsSortOrder | null; - allowDetails?: boolean; previewLimit?: number; forceEscape?: boolean; showDetectedFields?: string[]; @@ -91,7 +91,7 @@ class UnThemedLogRows extends PureComponent { onClickFilterLabel, onClickFilterOutLabel, theme, - allowDetails, + enableLogDetails, previewLimit, getFieldLinks, logsSortOrder, @@ -136,7 +136,7 @@ class UnThemedLogRows extends PureComponent { showDetectedFields={showDetectedFields} wrapLogMessage={wrapLogMessage} timeZone={timeZone} - allowDetails={allowDetails} + enableLogDetails={enableLogDetails} onClickFilterLabel={onClickFilterLabel} onClickFilterOutLabel={onClickFilterOutLabel} onClickShowDetectedField={onClickShowDetectedField} @@ -161,7 +161,7 @@ class UnThemedLogRows extends PureComponent { showDetectedFields={showDetectedFields} wrapLogMessage={wrapLogMessage} timeZone={timeZone} - allowDetails={allowDetails} + enableLogDetails={enableLogDetails} onClickFilterLabel={onClickFilterLabel} onClickFilterOutLabel={onClickFilterOutLabel} onClickShowDetectedField={onClickShowDetectedField} diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 0b538c1ff0d..e598af19e6b 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -341,6 +341,7 @@ export class UnthemedLogs extends PureComponent { showContextToggle={showContextToggle} showLabels={showLabels} showTime={showTime} + enableLogDetails={true} forceEscape={forceEscape} wrapLogMessage={wrapLogMessage} timeZone={timeZone} diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index e263b8829b5..8956fbbd22f 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -1,15 +1,16 @@ import React from 'react'; import { LogRows, CustomScrollbar } from '@grafana/ui'; -import { PanelProps } from '@grafana/data'; +import { PanelProps, Field } from '@grafana/data'; import { Options } from './types'; import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model'; +import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; interface LogsPanelProps extends PanelProps {} export const LogsPanel: React.FunctionComponent = ({ data, timeZone, - options: { showLabels, showTime, wrapLogMessage, sortOrder, dedupStrategy }, + options: { showLabels, showTime, wrapLogMessage, sortOrder, dedupStrategy, enableLogDetails }, }) => { if (!data) { return ( @@ -23,6 +24,10 @@ export const LogsPanel: React.FunctionComponent = ({ const logRows = newResults?.rows || []; const deduplicatedRows = dedupLogRows(logRows, dedupStrategy); + const getFieldLinks = (field: Field, rowIndex: number) => { + return getFieldLinksForExplore({ field, rowIndex, range: data.timeRange }); + }; + return ( = ({ showTime={showTime} wrapLogMessage={wrapLogMessage} timeZone={timeZone} - allowDetails={true} + getFieldLinks={getFieldLinks} logsSortOrder={sortOrder} + enableLogDetails={enableLogDetails} /> ); diff --git a/public/app/plugins/panel/logs/module.tsx b/public/app/plugins/panel/logs/module.tsx index 7c44ad7b674..e0233faf775 100644 --- a/public/app/plugins/panel/logs/module.tsx +++ b/public/app/plugins/panel/logs/module.tsx @@ -22,6 +22,12 @@ export const plugin = new PanelPlugin(LogsPanel).setPanelOptions((build description: '', defaultValue: false, }) + .addBooleanSwitch({ + path: 'enableLogDetails', + name: 'Enable log details', + description: '', + defaultValue: true, + }) .addRadio({ path: 'dedupStrategy', name: 'Deduplication', diff --git a/public/app/plugins/panel/logs/types.ts b/public/app/plugins/panel/logs/types.ts index c910730a51c..d1513ca7942 100644 --- a/public/app/plugins/panel/logs/types.ts +++ b/public/app/plugins/panel/logs/types.ts @@ -4,6 +4,7 @@ export interface Options { showLabels: boolean; showTime: boolean; wrapLogMessage: boolean; + enableLogDetails: boolean; sortOrder: LogsSortOrder; dedupStrategy: LogsDedupStrategy; }