1import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react'; 2import { css } from '@emotion/css'; 3import { LogRows, CustomScrollbar, LogLabels, useStyles2, usePanelContext } from '@grafana/ui'; 4import { 5 PanelProps, 6 Field, 7 Labels, 8 GrafanaTheme2, 9 LogsSortOrder, 10 LogRowModel, 11 DataHoverClearEvent, 12 DataHoverEvent, 13} from '@grafana/data'; 14import { Options } from './types'; 15import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model'; 16import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; 17import { COMMON_LABELS } from '../../../core/logs_model'; 18 19interface LogsPanelProps extends PanelProps<Options> {} 20 21export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({ 22 data, 23 timeZone, 24 options: { 25 showLabels, 26 showTime, 27 wrapLogMessage, 28 showCommonLabels, 29 prettifyLogMessage, 30 sortOrder, 31 dedupStrategy, 32 enableLogDetails, 33 }, 34 title, 35}) => { 36 const isAscending = sortOrder === LogsSortOrder.Ascending; 37 const style = useStyles2(getStyles(title, isAscending)); 38 const [scrollTop, setScrollTop] = useState(0); 39 const logsContainerRef = useRef<HTMLDivElement>(null); 40 41 const { eventBus } = usePanelContext(); 42 const onLogRowHover = useCallback( 43 (row?: LogRowModel) => { 44 if (!row) { 45 eventBus.publish(new DataHoverClearEvent()); 46 } else { 47 eventBus.publish( 48 new DataHoverEvent({ 49 point: { 50 time: row.timeEpochMs, 51 }, 52 }) 53 ); 54 } 55 }, 56 [eventBus] 57 ); 58 59 // Important to memoize stuff here, as panel rerenders a lot for example when resizing. 60 const [logRows, deduplicatedRows, commonLabels] = useMemo(() => { 61 const newResults = data ? dataFrameToLogsModel(data.series, data.request?.intervalMs) : null; 62 const logRows = newResults?.rows || []; 63 const commonLabels = newResults?.meta?.find((m) => m.label === COMMON_LABELS); 64 const deduplicatedRows = dedupLogRows(logRows, dedupStrategy); 65 return [logRows, deduplicatedRows, commonLabels]; 66 }, [data, dedupStrategy]); 67 68 useLayoutEffect(() => { 69 if (isAscending && logsContainerRef.current) { 70 setScrollTop(logsContainerRef.current.offsetHeight); 71 } else { 72 setScrollTop(0); 73 } 74 }, [isAscending, logRows]); 75 76 const getFieldLinks = useCallback( 77 (field: Field, rowIndex: number) => { 78 return getFieldLinksForExplore({ field, rowIndex, range: data.timeRange }); 79 }, 80 [data] 81 ); 82 83 if (!data) { 84 return ( 85 <div className="panel-empty"> 86 <p>No data found in response</p> 87 </div> 88 ); 89 } 90 91 const renderCommonLabels = () => ( 92 <div className={style.labelContainer}> 93 <span className={style.label}>Common labels:</span> 94 <LogLabels labels={commonLabels ? (commonLabels.value as Labels) : { labels: '(no common labels)' }} /> 95 </div> 96 ); 97 98 return ( 99 <CustomScrollbar autoHide scrollTop={scrollTop}> 100 <div className={style.container} ref={logsContainerRef}> 101 {showCommonLabels && !isAscending && renderCommonLabels()} 102 <LogRows 103 logRows={logRows} 104 deduplicatedRows={deduplicatedRows} 105 dedupStrategy={dedupStrategy} 106 showLabels={showLabels} 107 showTime={showTime} 108 wrapLogMessage={wrapLogMessage} 109 prettifyLogMessage={prettifyLogMessage} 110 timeZone={timeZone} 111 getFieldLinks={getFieldLinks} 112 logsSortOrder={sortOrder} 113 enableLogDetails={enableLogDetails} 114 previewLimit={isAscending ? logRows.length : undefined} 115 onLogRowHover={onLogRowHover} 116 /> 117 {showCommonLabels && isAscending && renderCommonLabels()} 118 </div> 119 </CustomScrollbar> 120 ); 121}; 122 123const getStyles = (title: string, isAscending: boolean) => (theme: GrafanaTheme2) => ({ 124 container: css` 125 margin-bottom: ${theme.spacing(1.5)}; 126 //We can remove this hot-fix when we fix panel menu with no title overflowing top of all panels 127 margin-top: ${theme.spacing(!title ? 2.5 : 0)}; 128 `, 129 labelContainer: css` 130 margin: ${isAscending ? theme.spacing(0.5, 0, 0.5, 0) : theme.spacing(0, 0, 0.5, 0.5)}; 131 display: flex; 132 align-items: center; 133 `, 134 label: css` 135 margin-right: ${theme.spacing(0.5)}; 136 font-size: ${theme.typography.bodySmall.fontSize}; 137 font-weight: ${theme.typography.fontWeightMedium}; 138 `, 139}); 140