github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/ui/dashboard/src/components/dashboards/layout/PanelDetail/PanelDetailLog.tsx (about) 1 import DateTime from "../../../DateTime"; 2 import Icon from "../../../Icon"; 3 import Panel from "../Panel"; 4 import sortBy from "lodash/sortBy"; 5 import { classNames } from "../../../../utils/styles"; 6 import { 7 DashboardRunState, 8 PanelDefinition, 9 PanelLog, 10 PanelsLog, 11 PanelsMap, 12 } from "../../../../types"; 13 import { Disclosure } from "@headlessui/react"; 14 import { getNodeAndEdgeDataFormat } from "../../common/useNodeAndEdgeData"; 15 import { NodeAndEdgeProperties } from "../../common/types"; 16 import { PanelDetailProps } from "./index"; 17 import { useDashboard } from "../../../../hooks/useDashboard"; 18 import { usePanel } from "../../../../hooks/usePanel"; 19 20 type PanelLogRowProps = { 21 log: PanelLog; 22 }; 23 24 type PanelLogIconProps = { 25 status: DashboardRunState; 26 }; 27 28 type PanelLogStatusProps = { 29 status: DashboardRunState; 30 }; 31 32 const PanelLogIcon = ({ status }: PanelLogIconProps) => { 33 switch (status) { 34 case "initialized": 35 return ( 36 <Icon 37 className="text-skip w-4.5 h-4.5" 38 icon="materialsymbols-outline:pending" 39 /> 40 ); 41 case "blocked": 42 return ( 43 <Icon 44 className="text-skip w-4.5 h-4.5" 45 icon="materialsymbols-solid:block" 46 /> 47 ); 48 case "running": 49 return ( 50 <Icon 51 className="text-skip w-4.5 h-4.5" 52 icon="materialsymbols-solid:run_circle" 53 /> 54 ); 55 case "cancelled": 56 return ( 57 <Icon 58 className="text-skip w-4.5 h-4.5" 59 icon="materialsymbols-outline:cancel" 60 /> 61 ); 62 case "error": 63 return ( 64 <Icon 65 className="text-alert w-4.5 h-4.5" 66 icon="materialsymbols-solid:error" 67 /> 68 ); 69 case "complete": 70 return ( 71 <Icon 72 className="text-ok w-4.5 h-4.5" 73 icon="materialsymbols-solid:check_circle" 74 /> 75 ); 76 } 77 }; 78 79 const PanelLogStatus = ({ status }: PanelLogStatusProps) => { 80 const baseClassname = "inline-block tabular-nums whitespace-nowrap"; 81 switch (status) { 82 case "initialized": 83 return <pre className={baseClassname}>Initialized</pre>; 84 case "blocked": 85 return ( 86 <pre className={baseClassname}>Blocked </pre> 87 ); 88 case "running": 89 return ( 90 <pre className={baseClassname}>Running </pre> 91 ); 92 case "cancelled": 93 return <pre className={baseClassname}>Cancelled </pre>; 94 case "error": 95 return ( 96 <pre className={baseClassname}> 97 Error 98 </pre> 99 ); 100 case "complete": 101 return <pre className={baseClassname}>Complete </pre>; 102 } 103 }; 104 105 const PanelLogMessage = ({ log }: PanelLogRowProps) => ( 106 <div className="flex space-x-2"> 107 <PanelLogStatus status={log.status} /> 108 {log.prefix && ( 109 <pre className="text-foreground-lighter tabular-nums">{log.prefix}:</pre> 110 )} 111 {log.isDependency && <span className="">{log.title}</span>} 112 {log.executionTime !== undefined && ( 113 <span className="text-foreground-lighter tabular-nums"> 114 {log.executionTime.toLocaleString()}ms 115 </span> 116 )} 117 </div> 118 ); 119 120 const PanelLogRow = ({ log }: PanelLogRowProps) => { 121 return ( 122 <Disclosure> 123 {({ open }) => { 124 return ( 125 <> 126 <Disclosure.Button 127 className={classNames( 128 "w-full px-2 py-1 flex justify-between items-center hover:bg-black-scale-2", 129 log.error ? "cursor-pointer" : "cursor-default" 130 )} 131 > 132 <div className="flex items-center space-x-3"> 133 <PanelLogIcon status={log.status} /> 134 <DateTime 135 date={log.timestamp} 136 dateClassName="hidden" 137 timeFormat="HH:mm:ss.SSS" 138 /> 139 <PanelLogMessage log={log} /> 140 </div> 141 {log.error ? ( 142 <div> 143 <Icon 144 className="w-4.5 h-4.5 text-foreground-light" 145 icon={ 146 open 147 ? "materialsymbols-outline:expand_less" 148 : "materialsymbols-outline:expand_more" 149 } 150 /> 151 </div> 152 ) : null} 153 </Disclosure.Button> 154 {log.error ? ( 155 <Disclosure.Panel className="px-2 py-1"> 156 {log.error} 157 </Disclosure.Panel> 158 ) : null} 159 </> 160 ); 161 }} 162 </Disclosure> 163 ); 164 }; 165 166 const addDependencyLogs = ( 167 panel: PanelDefinition, 168 panelsLog: PanelsLog, 169 panelsMap: PanelsMap, 170 dependencyLogs: PanelLog[], 171 dependentPanelRecord 172 ) => { 173 for (const dependency of panel.dependencies || []) { 174 if (dependentPanelRecord[dependency]) { 175 continue; 176 } 177 dependentPanelRecord[dependency] = true; 178 const dependencyPanel = panelsMap[dependency]; 179 if (!dependencyPanel) { 180 continue; 181 } 182 addDependencyLogs( 183 dependencyPanel, 184 panelsLog, 185 panelsMap, 186 dependencyLogs, 187 dependentPanelRecord 188 ); 189 } 190 const dependencyPanelLog = panelsLog[panel.name]; 191 dependencyLogs.push( 192 ...dependencyPanelLog.map((l) => ({ 193 ...l, 194 isDependency: true, 195 prefix: panel.panel_type, 196 })) 197 ); 198 }; 199 200 const getDependencyLogs = ( 201 panel: PanelDefinition, 202 panelsLog: PanelsLog, 203 panelsMap: PanelsMap 204 ) => { 205 const dependencyLogs: PanelLog[] = []; 206 const dependentPanelRecord = {}; 207 208 addDependencyLogs( 209 panel, 210 panelsLog, 211 panelsMap, 212 dependencyLogs, 213 dependentPanelRecord 214 ); 215 216 if ( 217 (panel.panel_type === "flow" || 218 panel.panel_type === "graph" || 219 panel.panel_type === "hierarchy") && 220 panel.properties && 221 getNodeAndEdgeDataFormat(panel.properties) === "NODE_AND_EDGE" 222 ) { 223 const nodeAndEdgeProperties = panel.properties as NodeAndEdgeProperties; 224 for (const node of nodeAndEdgeProperties.nodes || []) { 225 const nodePanel = panelsMap[node]; 226 if (!nodePanel) { 227 continue; 228 } 229 addDependencyLogs( 230 nodePanel, 231 panelsLog, 232 panelsMap, 233 dependencyLogs, 234 dependentPanelRecord 235 ); 236 } 237 for (const edge of nodeAndEdgeProperties.edges || []) { 238 const edgePanel = panelsMap[edge]; 239 if (!edgePanel) { 240 continue; 241 } 242 addDependencyLogs( 243 edgePanel, 244 panelsLog, 245 panelsMap, 246 dependencyLogs, 247 dependentPanelRecord 248 ); 249 } 250 } 251 return dependencyLogs; 252 }; 253 254 const PanelLogs = () => { 255 const { panelsLog, panelsMap } = useDashboard(); 256 const { definition } = usePanel(); 257 const panelLog = panelsLog[definition.name]; 258 const dependencyPanelLogs = getDependencyLogs( 259 definition as PanelDefinition, 260 panelsLog, 261 panelsMap 262 ); 263 const allLogs = sortBy([...dependencyPanelLogs, ...panelLog], "timestamp"); 264 return ( 265 <div className="border border-black-scale-2 divide-y divide-divide"> 266 {allLogs.map((log, idx) => ( 267 <PanelLogRow 268 key={`${log.status}:${log.timestamp}:${log.prefix}:${log.title}-${idx}`} 269 log={log} 270 /> 271 ))} 272 </div> 273 ); 274 }; 275 276 const PanelDetailLog = ({ definition }: PanelDetailProps) => ( 277 <Panel 278 definition={{ 279 ...definition, 280 title: `${definition.title ? `${definition.title} Log` : "Log"}`, 281 }} 282 parentType="dashboard" 283 showControls={false} 284 showPanelError={false} 285 forceBackground={true} 286 > 287 <PanelLogs /> 288 </Panel> 289 ); 290 291 export default PanelDetailLog;