| 
									
										
										
										
											2023-09-12 22:57:14 +08:00
										 |  |  | import { cx } from '@emotion/css'; | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | import memoizeOne from 'memoize-one'; | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  | import React, { PureComponent, MouseEvent, createRef } from 'react'; | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 22:33:17 +08:00
										 |  |  | import { | 
					
						
							|  |  |  |   TimeZone, | 
					
						
							|  |  |  |   LogsDedupStrategy, | 
					
						
							|  |  |  |   LogRowModel, | 
					
						
							|  |  |  |   Field, | 
					
						
							|  |  |  |   LinkModel, | 
					
						
							|  |  |  |   LogsSortOrder, | 
					
						
							|  |  |  |   CoreApp, | 
					
						
							|  |  |  |   DataFrame, | 
					
						
							| 
									
										
										
										
											2023-12-21 05:10:29 +08:00
										 |  |  |   LogRowContextOptions, | 
					
						
							| 
									
										
										
										
											2022-11-10 22:33:17 +08:00
										 |  |  | } from '@grafana/data'; | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  | import { config } from '@grafana/runtime'; | 
					
						
							| 
									
										
										
										
											2023-12-21 05:10:29 +08:00
										 |  |  | import { DataQuery } from '@grafana/schema'; | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | import { withTheme2, Themeable2 } from '@grafana/ui'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-28 21:21:37 +08:00
										 |  |  | import { PopoverMenu } from '../../explore/Logs/PopoverMenu'; | 
					
						
							| 
									
										
										
										
											2023-07-13 14:01:55 +08:00
										 |  |  | import { UniqueKeyMaker } from '../UniqueKeyMaker'; | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  | import { sortLogRows, targetIsElement } from '../utils'; | 
					
						
							| 
									
										
										
										
											2022-09-30 18:16:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | //Components
 | 
					
						
							|  |  |  | import { LogRow } from './LogRow'; | 
					
						
							|  |  |  | import { getLogRowStyles } from './getLogRowStyles'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const PREVIEW_LIMIT = 100; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface Props extends Themeable2 { | 
					
						
							|  |  |  |   logRows?: LogRowModel[]; | 
					
						
							|  |  |  |   deduplicatedRows?: LogRowModel[]; | 
					
						
							|  |  |  |   dedupStrategy: LogsDedupStrategy; | 
					
						
							|  |  |  |   showLabels: boolean; | 
					
						
							|  |  |  |   showTime: boolean; | 
					
						
							|  |  |  |   wrapLogMessage: boolean; | 
					
						
							|  |  |  |   prettifyLogMessage: boolean; | 
					
						
							|  |  |  |   timeZone: TimeZone; | 
					
						
							|  |  |  |   enableLogDetails: boolean; | 
					
						
							|  |  |  |   logsSortOrder?: LogsSortOrder | null; | 
					
						
							|  |  |  |   previewLimit?: number; | 
					
						
							|  |  |  |   forceEscape?: boolean; | 
					
						
							| 
									
										
										
										
											2023-01-12 02:20:11 +08:00
										 |  |  |   displayedFields?: string[]; | 
					
						
							| 
									
										
										
										
											2022-09-29 16:00:01 +08:00
										 |  |  |   app?: CoreApp; | 
					
						
							| 
									
										
										
										
											2023-10-31 19:37:44 +08:00
										 |  |  |   showContextToggle?: (row: LogRowModel) => boolean; | 
					
						
							| 
									
										
										
										
											2023-11-27 21:29:00 +08:00
										 |  |  |   onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void; | 
					
						
							|  |  |  |   onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void; | 
					
						
							| 
									
										
										
										
											2022-11-10 22:33:17 +08:00
										 |  |  |   getFieldLinks?: (field: Field, rowIndex: number, dataFrame: DataFrame) => Array<LinkModel<Field>>; | 
					
						
							| 
									
										
										
										
											2023-01-12 02:20:11 +08:00
										 |  |  |   onClickShowField?: (key: string) => void; | 
					
						
							|  |  |  |   onClickHideField?: (key: string) => void; | 
					
						
							| 
									
										
										
										
											2023-06-28 21:22:54 +08:00
										 |  |  |   onPinLine?: (row: LogRowModel) => void; | 
					
						
							|  |  |  |   onUnpinLine?: (row: LogRowModel) => void; | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |   onLogRowHover?: (row?: LogRowModel) => void; | 
					
						
							| 
									
										
										
										
											2023-04-14 23:05:43 +08:00
										 |  |  |   onOpenContext?: (row: LogRowModel, onClose: () => void) => void; | 
					
						
							| 
									
										
										
										
											2023-12-21 05:10:29 +08:00
										 |  |  |   getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQuery | null>; | 
					
						
							| 
									
										
										
										
											2023-06-16 20:07:51 +08:00
										 |  |  |   onPermalinkClick?: (row: LogRowModel) => Promise<void>; | 
					
						
							|  |  |  |   permalinkedRowId?: string; | 
					
						
							|  |  |  |   scrollIntoView?: (element: HTMLElement) => void; | 
					
						
							| 
									
										
										
										
											2023-09-04 22:30:17 +08:00
										 |  |  |   isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>; | 
					
						
							| 
									
										
										
										
											2023-06-28 21:22:54 +08:00
										 |  |  |   pinnedRowId?: string; | 
					
						
							| 
									
										
										
										
											2023-08-18 18:54:08 +08:00
										 |  |  |   containerRendered?: boolean; | 
					
						
							| 
									
										
										
										
											2023-09-12 22:57:14 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * If false or undefined, the `contain:strict` css property will be added to the wrapping `<table>` for performance reasons. | 
					
						
							|  |  |  |    * Any overflowing content will be clipped at the table boundary. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   overflowingContent?: boolean; | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |   onClickFilterValue?: (value: string, refId?: string) => void; | 
					
						
							|  |  |  |   onClickFilterOutValue?: (value: string, refId?: string) => void; | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface State { | 
					
						
							|  |  |  |   renderAll: boolean; | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |   selection: string; | 
					
						
							|  |  |  |   selectedRow: LogRowModel | null; | 
					
						
							|  |  |  |   popoverMenuCoordinates: { x: number; y: number }; | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class UnThemedLogRows extends PureComponent<Props, State> { | 
					
						
							|  |  |  |   renderAllTimer: number | null = null; | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |   logRowsRef = createRef<HTMLDivElement>(); | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   static defaultProps = { | 
					
						
							|  |  |  |     previewLimit: PREVIEW_LIMIT, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   state: State = { | 
					
						
							|  |  |  |     renderAll: false, | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |     selection: '', | 
					
						
							|  |  |  |     selectedRow: null, | 
					
						
							|  |  |  |     popoverMenuCoordinates: { x: 0, y: 0 }, | 
					
						
							| 
									
										
										
										
											2022-09-29 16:00:01 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Toggle the `contextIsOpen` state when a context of one LogRow is opened in order to not show the menu of the other log rows. | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2023-04-14 23:05:43 +08:00
										 |  |  |   openContext = (row: LogRowModel, onClose: () => void): void => { | 
					
						
							|  |  |  |     if (this.props.onOpenContext) { | 
					
						
							|  |  |  |       this.props.onOpenContext(row, onClose); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |   popoverMenuSupported() { | 
					
						
							| 
									
										
										
										
											2023-11-28 21:21:37 +08:00
										 |  |  |     if (!config.featureToggles.logRowsPopoverMenu || this.props.app !== CoreApp.Explore) { | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return Boolean(this.props.onClickFilterOutValue || this.props.onClickFilterValue); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   handleSelection = (e: MouseEvent<HTMLTableRowElement>, row: LogRowModel): boolean => { | 
					
						
							|  |  |  |     if (this.popoverMenuSupported() === false) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const selection = document.getSelection()?.toString(); | 
					
						
							|  |  |  |     if (!selection) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!this.logRowsRef.current) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const parentBounds = this.logRowsRef.current?.getBoundingClientRect(); | 
					
						
							|  |  |  |     this.setState({ | 
					
						
							|  |  |  |       selection, | 
					
						
							|  |  |  |       popoverMenuCoordinates: { x: e.clientX - parentBounds.left, y: e.clientY - parentBounds.top }, | 
					
						
							|  |  |  |       selectedRow: row, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2023-11-23 18:04:23 +08:00
										 |  |  |     document.addEventListener('click', this.handleDeselection); | 
					
						
							| 
									
										
										
										
											2023-11-27 20:36:55 +08:00
										 |  |  |     document.addEventListener('contextmenu', this.handleDeselection); | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |     return true; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   handleDeselection = (e: Event) => { | 
					
						
							|  |  |  |     if (targetIsElement(e.target) && !this.logRowsRef.current?.contains(e.target)) { | 
					
						
							|  |  |  |       // The mouseup event comes from outside the log rows, close the menu.
 | 
					
						
							|  |  |  |       this.closePopoverMenu(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (document.getSelection()?.toString()) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.closePopoverMenu(); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   closePopoverMenu = () => { | 
					
						
							| 
									
										
										
										
											2023-11-23 18:04:23 +08:00
										 |  |  |     document.removeEventListener('click', this.handleDeselection); | 
					
						
							| 
									
										
										
										
											2023-11-27 20:36:55 +08:00
										 |  |  |     document.removeEventListener('contextmenu', this.handleDeselection); | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |     this.setState({ | 
					
						
							|  |  |  |       selection: '', | 
					
						
							|  |  |  |       popoverMenuCoordinates: { x: 0, y: 0 }, | 
					
						
							|  |  |  |       selectedRow: null, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |   componentDidMount() { | 
					
						
							|  |  |  |     // Staged rendering
 | 
					
						
							|  |  |  |     const { logRows, previewLimit } = this.props; | 
					
						
							|  |  |  |     const rowCount = logRows ? logRows.length : 0; | 
					
						
							|  |  |  |     // Render all right away if not too far over the limit
 | 
					
						
							|  |  |  |     const renderAll = rowCount <= previewLimit! * 2; | 
					
						
							|  |  |  |     if (renderAll) { | 
					
						
							|  |  |  |       this.setState({ renderAll }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this.renderAllTimer = window.setTimeout(() => this.setState({ renderAll: true }), 2000); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   componentWillUnmount() { | 
					
						
							| 
									
										
										
										
											2023-11-23 18:04:23 +08:00
										 |  |  |     document.removeEventListener('click', this.handleDeselection); | 
					
						
							| 
									
										
										
										
											2023-11-27 20:36:55 +08:00
										 |  |  |     document.removeEventListener('contextmenu', this.handleDeselection); | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |     if (this.renderAllTimer) { | 
					
						
							|  |  |  |       clearTimeout(this.renderAllTimer); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   makeGetRows = memoizeOne((orderedRows: LogRowModel[]) => { | 
					
						
							|  |  |  |     return () => orderedRows; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sortLogs = memoizeOne((logRows: LogRowModel[], logsSortOrder: LogsSortOrder): LogRowModel[] => | 
					
						
							|  |  |  |     sortLogRows(logRows, logsSortOrder) | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   render() { | 
					
						
							| 
									
										
										
										
											2023-06-28 21:45:22 +08:00
										 |  |  |     const { deduplicatedRows, logRows, dedupStrategy, theme, logsSortOrder, previewLimit, ...rest } = this.props; | 
					
						
							| 
									
										
										
										
											2023-04-14 23:05:43 +08:00
										 |  |  |     const { renderAll } = this.state; | 
					
						
							| 
									
										
										
										
											2023-02-01 22:28:10 +08:00
										 |  |  |     const styles = getLogRowStyles(theme); | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |     const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows; | 
					
						
							|  |  |  |     const hasData = logRows && logRows.length > 0; | 
					
						
							|  |  |  |     const dedupCount = dedupedRows | 
					
						
							|  |  |  |       ? dedupedRows.reduce((sum, row) => (row.duplicates ? sum + row.duplicates : sum), 0) | 
					
						
							|  |  |  |       : 0; | 
					
						
							|  |  |  |     const showDuplicates = dedupStrategy !== LogsDedupStrategy.none && dedupCount > 0; | 
					
						
							|  |  |  |     // Staged rendering
 | 
					
						
							|  |  |  |     const processedRows = dedupedRows ? dedupedRows : []; | 
					
						
							|  |  |  |     const orderedRows = logsSortOrder ? this.sortLogs(processedRows, logsSortOrder) : processedRows; | 
					
						
							|  |  |  |     const firstRows = orderedRows.slice(0, previewLimit!); | 
					
						
							|  |  |  |     const lastRows = orderedRows.slice(previewLimit!, orderedRows.length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
 | 
					
						
							|  |  |  |     const getRows = this.makeGetRows(orderedRows); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-13 14:01:55 +08:00
										 |  |  |     const keyMaker = new UniqueKeyMaker(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2023-11-16 17:48:10 +08:00
										 |  |  |       <div className={styles.logRows} ref={this.logRowsRef}> | 
					
						
							|  |  |  |         {this.state.selection && this.state.selectedRow && ( | 
					
						
							|  |  |  |           <PopoverMenu | 
					
						
							|  |  |  |             close={this.closePopoverMenu} | 
					
						
							|  |  |  |             row={this.state.selectedRow} | 
					
						
							|  |  |  |             selection={this.state.selection} | 
					
						
							|  |  |  |             {...this.state.popoverMenuCoordinates} | 
					
						
							|  |  |  |             onClickFilterValue={rest.onClickFilterValue} | 
					
						
							|  |  |  |             onClickFilterOutValue={rest.onClickFilterOutValue} | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |         <table className={cx(styles.logsRowsTable, this.props.overflowingContent ? '' : styles.logsRowsTableContain)}> | 
					
						
							|  |  |  |           <tbody> | 
					
						
							|  |  |  |             {hasData && | 
					
						
							|  |  |  |               firstRows.map((row) => ( | 
					
						
							|  |  |  |                 <LogRow | 
					
						
							|  |  |  |                   key={keyMaker.getKey(row.uid)} | 
					
						
							|  |  |  |                   getRows={getRows} | 
					
						
							|  |  |  |                   row={row} | 
					
						
							|  |  |  |                   showDuplicates={showDuplicates} | 
					
						
							|  |  |  |                   logsSortOrder={logsSortOrder} | 
					
						
							|  |  |  |                   onOpenContext={this.openContext} | 
					
						
							|  |  |  |                   styles={styles} | 
					
						
							|  |  |  |                   onPermalinkClick={this.props.onPermalinkClick} | 
					
						
							|  |  |  |                   scrollIntoView={this.props.scrollIntoView} | 
					
						
							|  |  |  |                   permalinkedRowId={this.props.permalinkedRowId} | 
					
						
							|  |  |  |                   onPinLine={this.props.onPinLine} | 
					
						
							|  |  |  |                   onUnpinLine={this.props.onUnpinLine} | 
					
						
							|  |  |  |                   pinned={this.props.pinnedRowId === row.uid} | 
					
						
							|  |  |  |                   isFilterLabelActive={this.props.isFilterLabelActive} | 
					
						
							|  |  |  |                   handleTextSelection={this.popoverMenuSupported() ? this.handleSelection : undefined} | 
					
						
							|  |  |  |                   {...rest} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               ))} | 
					
						
							|  |  |  |             {hasData && | 
					
						
							|  |  |  |               renderAll && | 
					
						
							|  |  |  |               lastRows.map((row) => ( | 
					
						
							|  |  |  |                 <LogRow | 
					
						
							|  |  |  |                   key={keyMaker.getKey(row.uid)} | 
					
						
							|  |  |  |                   getRows={getRows} | 
					
						
							|  |  |  |                   row={row} | 
					
						
							|  |  |  |                   showDuplicates={showDuplicates} | 
					
						
							|  |  |  |                   logsSortOrder={logsSortOrder} | 
					
						
							|  |  |  |                   onOpenContext={this.openContext} | 
					
						
							|  |  |  |                   styles={styles} | 
					
						
							|  |  |  |                   onPermalinkClick={this.props.onPermalinkClick} | 
					
						
							|  |  |  |                   scrollIntoView={this.props.scrollIntoView} | 
					
						
							|  |  |  |                   permalinkedRowId={this.props.permalinkedRowId} | 
					
						
							|  |  |  |                   onPinLine={this.props.onPinLine} | 
					
						
							|  |  |  |                   onUnpinLine={this.props.onUnpinLine} | 
					
						
							|  |  |  |                   pinned={this.props.pinnedRowId === row.uid} | 
					
						
							|  |  |  |                   isFilterLabelActive={this.props.isFilterLabelActive} | 
					
						
							|  |  |  |                   handleTextSelection={this.popoverMenuSupported() ? this.handleSelection : undefined} | 
					
						
							|  |  |  |                   {...rest} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               ))} | 
					
						
							|  |  |  |             {hasData && !renderAll && ( | 
					
						
							|  |  |  |               <tr> | 
					
						
							|  |  |  |                 <td colSpan={5}>Rendering {orderedRows.length - previewLimit!} rows...</td> | 
					
						
							|  |  |  |               </tr> | 
					
						
							|  |  |  |             )} | 
					
						
							|  |  |  |           </tbody> | 
					
						
							|  |  |  |         </table> | 
					
						
							|  |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2022-09-19 16:51:46 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const LogRows = withTheme2(UnThemedLogRows); | 
					
						
							|  |  |  | LogRows.displayName = 'LogsRows'; |