1- import React , { useMemo , useRef } from "react" ;
2- import { useQuery , useQueryClient } from "@tanstack/react-query" ;
3- import { getViewDataById } from "../../../api/services/views" ;
4- import ViewSection from "./ViewSection" ;
1+ import React from "react" ;
52import Age from "../../../ui/Age/Age" ;
6- import { toastError } from "../../../components/Toast/toast" ;
73import ViewLayout from "./ViewLayout" ;
8- import { useAggregatedViewVariables } from "../hooks/useAggregatedViewVariables" ;
9- import GlobalFiltersForm from "../../audit-report/components/View/GlobalFiltersForm" ;
10- import GlobalFilters from "../../audit-report/components/View/GlobalFilters" ;
11- import { VIEW_VAR_PREFIX } from "../constants" ;
12- import type { ViewRef } from "../../audit-report/types" ;
4+ import ViewWithSections from "./ViewWithSections" ;
5+ import { useViewData } from "../hooks/useViewData" ;
136import { ErrorViewer } from "@flanksource-ui/components/ErrorViewer" ;
147
158interface SingleViewProps {
169 id : string ;
1710}
1811
1912const SingleView : React . FC < SingleViewProps > = ( { id } ) => {
20- const queryClient = useQueryClient ( ) ;
21- const forceRefreshRef = useRef ( false ) ;
22-
23- // Fetch view metadata only. Each section (including the main view) will fetch
24- // its own data with its own variables using its unique prefix.
2513 const {
26- data : viewResult ,
14+ viewResult,
2715 isLoading,
2816 isFetching,
2917 error,
30- refetch
31- } = useQuery ( {
32- queryKey : [ "view-result" , id ] ,
33- queryFn : ( ) => {
34- const headers = forceRefreshRef . current
35- ? { "cache-control" : "max-age=1" }
36- : undefined ;
37- return getViewDataById ( id , undefined , headers ) ;
38- } ,
39- enabled : ! ! id ,
40- staleTime : 5 * 60 * 1000 ,
41- keepPreviousData : true
42- } ) ;
43-
44- // Collect all section refs (main view + additional sections)
45- // Must be called before early returns to satisfy React hooks rules
46- const allSectionRefs = useMemo < ViewRef [ ] > ( ( ) => {
47- if ( ! viewResult ?. namespace || ! viewResult ?. name ) {
48- return [ ] ;
49- }
50- const refs = [ { namespace : viewResult . namespace , name : viewResult . name } ] ;
51- if ( viewResult ?. sections ) {
52- viewResult . sections . forEach ( ( section ) => {
53- refs . push ( {
54- namespace : section . viewRef . namespace ,
55- name : section . viewRef . name
56- } ) ;
57- } ) ;
58- }
59- return refs ;
60- } , [ viewResult ?. namespace , viewResult ?. name , viewResult ?. sections ] ) ;
61-
62- // Fetch and aggregate variables from all sections
63- const { variables : aggregatedVariables , currentVariables } =
64- useAggregatedViewVariables ( allSectionRefs ) ;
65-
66- const handleForceRefresh = async ( ) => {
67- forceRefreshRef . current = true ;
68- const result = await refetch ( ) ;
69- forceRefreshRef . current = false ;
70-
71- if ( result . isError ) {
72- const err = result . error as any ;
73- toastError (
74- err ?. message ||
75- err ?. error ||
76- err ?. detail ||
77- err ?. msg ||
78- "Failed to refresh view"
79- ) ;
80- return ;
81- }
82-
83- // Invalidate all section data (view results and tables) so they refetch
84- const sectionsToRefresh =
85- allSectionRefs . length > 0 &&
86- allSectionRefs [ 0 ] . namespace &&
87- allSectionRefs [ 0 ] . name
88- ? allSectionRefs
89- : result . data ?. namespace && result . data . name
90- ? [ { namespace : result . data . namespace , name : result . data . name } ]
91- : [ ] ;
92-
93- // Also clear the main view query by id so the metadata refetches
94- await queryClient . invalidateQueries ( { queryKey : [ "view-result" , id ] } ) ;
95-
96- await Promise . all (
97- sectionsToRefresh . flatMap ( ( section ) => [
98- queryClient . invalidateQueries ( {
99- queryKey : [ "view-result" , section . namespace , section . name ]
100- } ) ,
101- queryClient . invalidateQueries ( {
102- queryKey : [ "view-table" , section . namespace , section . name ]
103- } ) ,
104- queryClient . invalidateQueries ( {
105- queryKey : [ "view-variables" , section . namespace , section . name ]
106- } )
107- ] )
108- ) ;
109- } ;
18+ aggregatedVariables,
19+ currentVariables,
20+ handleForceRefresh
21+ } = useViewData ( { viewId : id } ) ;
11022
11123 if ( isLoading && ! viewResult ) {
11224 return (
@@ -153,24 +65,7 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
15365 ) ;
15466 }
15567
156- const { icon, title, namespace, name } = viewResult ;
157-
158- // Render the main view through ViewSection to reuse its spacing/scroll styling;
159- // rendering the raw View here caused padding/overflow glitches alongside sections.
160- const primaryViewSection = {
161- title : title || name ,
162- viewRef : {
163- namespace : namespace || "" ,
164- name : name
165- }
166- } ;
167-
168- // Check if this view only aggregates other views (has sections but no content of its own)
169- const isAggregatorView =
170- viewResult . sections &&
171- viewResult . sections . length > 0 &&
172- ! viewResult . panels &&
173- ! viewResult . columns ;
68+ const { icon, title, name } = viewResult ;
17469
17570 return (
17671 < ViewLayout
@@ -187,46 +82,12 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
18782 )
18883 }
18984 >
190- < div className = "flex h-full w-full flex-1 flex-col overflow-y-auto px-6" >
191- { /* Render aggregated variables once at the top */ }
192- { aggregatedVariables && aggregatedVariables . length > 0 && (
193- < GlobalFiltersForm
194- variables = { aggregatedVariables }
195- globalVarPrefix = { VIEW_VAR_PREFIX }
196- currentVariables = { currentVariables }
197- >
198- < GlobalFilters variables = { aggregatedVariables } />
199- </ GlobalFiltersForm >
200- ) }
201-
202- { aggregatedVariables && aggregatedVariables . length > 0 && (
203- < hr className = "my-4 border-gray-200" />
204- ) }
205-
206- { /* Only show the primary ViewSection if this view has its own content */ }
207- { ! isAggregatorView && (
208- < div className = "mt-2" >
209- < ViewSection
210- key = { `${ namespace || "default" } :${ name } ` }
211- section = { primaryViewSection }
212- hideVariables
213- />
214- </ div >
215- ) }
216-
217- { viewResult ?. sections && viewResult . sections . length > 0 && (
218- < >
219- { viewResult . sections . map ( ( section ) => (
220- < div
221- key = { `${ section . viewRef . namespace } :${ section . viewRef . name } ` }
222- className = "mt-4"
223- >
224- < ViewSection section = { section } hideVariables />
225- </ div >
226- ) ) }
227- </ >
228- ) }
229- </ div >
85+ < ViewWithSections
86+ className = "flex h-full w-full flex-1 flex-col overflow-y-auto px-6"
87+ viewResult = { viewResult }
88+ aggregatedVariables = { aggregatedVariables }
89+ currentVariables = { currentVariables }
90+ />
23091 </ ViewLayout >
23192 ) ;
23293} ;
0 commit comments