1import React, { useCallback, useMemo } from 'react'; 2import { DataFrame, FieldType, PanelProps } from '@grafana/data'; 3import { TooltipPlugin, useTheme2, ZoomPlugin, usePanelContext } from '@grafana/ui'; 4import { TimelineMode, TimelineOptions } from './types'; 5import { TimelineChart } from './TimelineChart'; 6import { prepareTimelineFields, prepareTimelineLegendItems } from './utils'; 7import { StateTimelineTooltip } from './StateTimelineTooltip'; 8import { getLastStreamingDataFramePacket } from '@grafana/data/src/dataframe/StreamingDataFrame'; 9 10interface TimelinePanelProps extends PanelProps<TimelineOptions> {} 11 12/** 13 * @alpha 14 */ 15export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({ 16 data, 17 timeRange, 18 timeZone, 19 options, 20 width, 21 height, 22 onChangeTimeRange, 23}) => { 24 const theme = useTheme2(); 25 const { sync } = usePanelContext(); 26 27 const { frames, warn } = useMemo(() => prepareTimelineFields(data?.series, options.mergeValues ?? true, theme), [ 28 data, 29 options.mergeValues, 30 theme, 31 ]); 32 33 const legendItems = useMemo(() => prepareTimelineLegendItems(frames, options.legend, theme), [ 34 frames, 35 options.legend, 36 theme, 37 ]); 38 39 const renderCustomTooltip = useCallback( 40 (alignedData: DataFrame, seriesIdx: number | null, datapointIdx: number | null) => { 41 const data = frames ?? []; 42 // Count value fields in the state-timeline-ready frame 43 const valueFieldsCount = data.reduce( 44 (acc, frame) => acc + frame.fields.filter((field) => field.type !== FieldType.time).length, 45 0 46 ); 47 48 // Not caring about multi mode in StateTimeline 49 if (seriesIdx === null || datapointIdx === null) { 50 return null; 51 } 52 53 /** 54 * There could be a case when the tooltip shows a data from one of a multiple query and the other query finishes first 55 * from refreshing. This causes data to be out of sync. alignedData - 1 because Time field doesn't count. 56 * Render nothing in this case to prevent error. 57 * See https://github.com/grafana/support-escalations/issues/932 58 */ 59 if ( 60 (!alignedData.meta?.transformations?.length && alignedData.fields.length - 1 !== valueFieldsCount) || 61 !alignedData.fields[seriesIdx] 62 ) { 63 return null; 64 } 65 66 return ( 67 <StateTimelineTooltip 68 data={data} 69 alignedData={alignedData} 70 seriesIdx={seriesIdx} 71 datapointIdx={datapointIdx} 72 timeZone={timeZone} 73 /> 74 ); 75 }, 76 [timeZone, frames] 77 ); 78 79 if (!frames || warn) { 80 return ( 81 <div className="panel-empty"> 82 <p>{warn ?? 'No data found in response'}</p> 83 </div> 84 ); 85 } 86 87 if (frames.length === 1) { 88 const packet = getLastStreamingDataFramePacket(frames[0]); 89 if (packet) { 90 // console.log('STREAM Packet', packet); 91 } 92 } 93 94 return ( 95 <TimelineChart 96 theme={theme} 97 frames={frames} 98 structureRev={data.structureRev} 99 timeRange={timeRange} 100 timeZone={timeZone} 101 width={width} 102 height={height} 103 legendItems={legendItems} 104 {...options} 105 mode={TimelineMode.Changes} 106 > 107 {(config, alignedFrame) => { 108 return ( 109 <> 110 <ZoomPlugin config={config} onZoom={onChangeTimeRange} /> 111 <TooltipPlugin 112 data={alignedFrame} 113 sync={sync} 114 config={config} 115 mode={options.tooltip.mode} 116 timeZone={timeZone} 117 renderTooltip={renderCustomTooltip} 118 /> 119 </> 120 ); 121 }} 122 </TimelineChart> 123 ); 124}; 125