github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/ui/dashboard/src/hooks/usePanel.tsx (about) 1 import usePanelControls from "./usePanelControls"; 2 import { BaseChartProps } from "../components/dashboards/charts/types"; 3 import { CardProps } from "../components/dashboards/Card"; 4 import { 5 createContext, 6 ReactNode, 7 useContext, 8 useEffect, 9 useMemo, 10 useState, 11 } from "react"; 12 import { 13 DashboardInputs, 14 DashboardPanelType, 15 DashboardRunState, 16 PanelDefinition, 17 PanelDependenciesByStatus, 18 PanelsMap, 19 } from "../types"; 20 import { FlowProps } from "../components/dashboards/flows/types"; 21 import { getNodeAndEdgeDataFormat } from "../components/dashboards/common/useNodeAndEdgeData"; 22 import { GraphProps } from "../components/dashboards/graphs/types"; 23 import { HierarchyProps } from "../components/dashboards/hierarchies/types"; 24 import { ImageProps } from "../components/dashboards/Image"; 25 import { 26 InputProperties, 27 InputProps, 28 } from "../components/dashboards/inputs/types"; 29 import { IPanelControl } from "../components/dashboards/layout/Panel/PanelControls"; 30 import { NodeAndEdgeProperties } from "../components/dashboards/common/types"; 31 import { TableProps } from "../components/dashboards/Table"; 32 import { TextProps } from "../components/dashboards/Text"; 33 import { useDashboard } from "./useDashboard"; 34 import { useContainer } from "./useContainer"; 35 36 type IPanelContext = { 37 definition: 38 | BaseChartProps 39 | CardProps 40 | FlowProps 41 | GraphProps 42 | HierarchyProps 43 | ImageProps 44 | InputProps 45 | PanelDefinition 46 | TableProps 47 | TextProps; 48 dependencies: PanelDefinition[]; 49 dependenciesByStatus: PanelDependenciesByStatus; 50 inputPanelsAwaitingValue: PanelDefinition[]; 51 panelControls: IPanelControl[]; 52 panelInformation: ReactNode | null; 53 showPanelControls: boolean; 54 showPanelInformation: boolean; 55 setPanelInformation: (information: ReactNode) => void; 56 setShowPanelControls: (show: boolean) => void; 57 setShowPanelInformation: (show: boolean) => void; 58 }; 59 60 type PanelProviderProps = { 61 children: ReactNode; 62 definition: 63 | BaseChartProps 64 | CardProps 65 | FlowProps 66 | GraphProps 67 | HierarchyProps 68 | ImageProps 69 | InputProps 70 | PanelDefinition 71 | TableProps 72 | TextProps; 73 parentType: DashboardPanelType; 74 showControls?: boolean; 75 }; 76 77 const PanelContext = createContext<IPanelContext | null>(null); 78 79 const recordDependency = ( 80 definition: PanelDefinition, 81 panelsMap: PanelsMap, 82 selectedDashboardInputs: DashboardInputs, 83 dependencies: PanelDefinition[], 84 dependenciesByStatus: PanelDependenciesByStatus, 85 inputPanelsAwaitingValue: PanelDefinition[], 86 recordedInputPanels: {} 87 ) => { 88 // Record this panel as a dependency 89 dependencies.push(definition); 90 91 // Keep track of this panel by its status 92 const statuses = 93 dependenciesByStatus[definition.status as DashboardRunState] || []; 94 statuses.push(definition); 95 dependenciesByStatus[definition.status as DashboardRunState] = statuses; 96 97 // Is this panel an input? If so, does it have a value? 98 const isInput = definition.panel_type === "input"; 99 const inputProperties = isInput 100 ? (definition.properties as InputProperties) 101 : null; 102 const hasInputValue = 103 isInput && 104 inputProperties?.unqualified_name && 105 !!selectedDashboardInputs[inputProperties?.unqualified_name]; 106 if (isInput && !hasInputValue && !recordedInputPanels[definition.name]) { 107 inputPanelsAwaitingValue.push(definition); 108 recordedInputPanels[definition.name] = definition; 109 } 110 111 for (const dependency of definition?.dependencies || []) { 112 const dependencyPanel = panelsMap[dependency]; 113 if (!dependencyPanel || !dependencyPanel.status) { 114 continue; 115 } 116 recordDependency( 117 dependencyPanel, 118 panelsMap, 119 selectedDashboardInputs, 120 dependencies, 121 dependenciesByStatus, 122 inputPanelsAwaitingValue, 123 recordedInputPanels 124 ); 125 } 126 }; 127 128 const PanelProvider = ({ 129 children, 130 definition, 131 parentType, 132 showControls, 133 }: PanelProviderProps) => { 134 const { updateChildStatus } = useContainer(); 135 const { selectedDashboardInputs, panelsMap } = useDashboard(); 136 const [showPanelControls, setShowPanelControls] = useState(false); 137 const [showPanelInformation, setShowPanelInformation] = useState(false); 138 const [panelInformation, setPanelInformation] = useState<ReactNode | null>( 139 null 140 ); 141 const { panelControls } = usePanelControls(definition, showControls); 142 const { dependencies, dependenciesByStatus, inputPanelsAwaitingValue } = 143 useMemo(() => { 144 if (!definition) { 145 return { 146 dependencies: [], 147 dependenciesByStatus: {}, 148 inputPanelsAwaitingValue: [], 149 }; 150 } 151 const dataFormat = getNodeAndEdgeDataFormat( 152 definition.properties as NodeAndEdgeProperties 153 ); 154 if ( 155 dataFormat === "LEGACY" && 156 (!definition.dependencies || definition.dependencies.length === 0) 157 ) { 158 return { 159 dependencies: [], 160 dependenciesByStatus: {}, 161 inputPanelsAwaitingValue: [], 162 }; 163 } 164 const dependencies: PanelDefinition[] = []; 165 const dependenciesByStatus: PanelDependenciesByStatus = {}; 166 const inputPanelsAwaitingValue: PanelDefinition[] = []; 167 const recordedInputPanels = {}; 168 169 if (dataFormat === "NODE_AND_EDGE") { 170 const nodeAndEdgeProperties = 171 definition.properties as NodeAndEdgeProperties; 172 for (const node of nodeAndEdgeProperties.nodes || []) { 173 const nodePanel = panelsMap[node]; 174 if (!nodePanel || !nodePanel.status) { 175 continue; 176 } 177 recordDependency( 178 nodePanel, 179 panelsMap, 180 selectedDashboardInputs, 181 dependencies, 182 dependenciesByStatus, 183 inputPanelsAwaitingValue, 184 recordedInputPanels 185 ); 186 } 187 for (const edge of nodeAndEdgeProperties.edges || []) { 188 const edgePanel = panelsMap[edge]; 189 if (!edgePanel || !edgePanel.status) { 190 continue; 191 } 192 recordDependency( 193 edgePanel, 194 panelsMap, 195 selectedDashboardInputs, 196 dependencies, 197 dependenciesByStatus, 198 inputPanelsAwaitingValue, 199 recordedInputPanels 200 ); 201 } 202 } 203 204 for (const dependency of definition.dependencies || []) { 205 const dependencyPanel = panelsMap[dependency]; 206 if (!dependencyPanel || !dependencyPanel.status) { 207 continue; 208 } 209 recordDependency( 210 dependencyPanel, 211 panelsMap, 212 selectedDashboardInputs, 213 dependencies, 214 dependenciesByStatus, 215 inputPanelsAwaitingValue, 216 recordedInputPanels 217 ); 218 } 219 220 return { dependencies, dependenciesByStatus, inputPanelsAwaitingValue }; 221 }, [definition, panelsMap, selectedDashboardInputs]); 222 223 useEffect(() => { 224 if (parentType !== "container") { 225 return; 226 } 227 updateChildStatus( 228 definition as PanelDefinition, 229 inputPanelsAwaitingValue.length === 0 ? "visible" : "hidden" 230 ); 231 }, [definition, inputPanelsAwaitingValue, parentType, updateChildStatus]); 232 233 return ( 234 <PanelContext.Provider 235 value={{ 236 definition, 237 dependencies, 238 dependenciesByStatus, 239 inputPanelsAwaitingValue, 240 panelControls, 241 panelInformation, 242 showPanelControls, 243 showPanelInformation, 244 setPanelInformation, 245 setShowPanelControls, 246 setShowPanelInformation, 247 }} 248 > 249 {children} 250 </PanelContext.Provider> 251 ); 252 }; 253 254 const usePanel = () => { 255 const context = useContext(PanelContext); 256 if (context === undefined) { 257 throw new Error("usePanel must be used within a PanelContext"); 258 } 259 return context as IPanelContext; 260 }; 261 262 export { PanelContext, PanelProvider, usePanel };