Skip to content

Commit 5d4c661

Browse files
authored
fix: View sections in config details page (#2766)
1 parent 36b68bf commit 5d4c661

File tree

7 files changed

+286
-229
lines changed

7 files changed

+286
-229
lines changed

src/pages/audit-report/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ export interface ColumnFilterOptions {
417417
}
418418

419419
export interface ViewRef {
420-
namespace: string;
420+
namespace?: string;
421421
name: string;
422422
}
423423

src/pages/config/details/ConfigDetailsViewPage.tsx

Lines changed: 18 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,52 @@
11
import { ConfigDetailsTabs } from "@flanksource-ui/components/Configs/ConfigDetailsTabs";
2-
import {
3-
getViewDataById,
4-
getViewDisplayPluginVariables
5-
} from "@flanksource-ui/api/services/views";
6-
import { useQuery, useQueryClient } from "@tanstack/react-query";
72
import { useParams } from "react-router-dom";
83
import { Loading } from "@flanksource-ui/ui/Loading";
9-
import View from "@flanksource-ui/pages/audit-report/components/View/View";
10-
import { useRef } from "react";
11-
import { toastError } from "@flanksource-ui/components/Toast/toast";
4+
import { useViewData } from "@flanksource-ui/pages/views/hooks/useViewData";
5+
import ViewWithSections from "@flanksource-ui/pages/views/components/ViewWithSections";
6+
import { ErrorViewer } from "@flanksource-ui/components/ErrorViewer";
127

13-
// Displays the view as a tab in the config details page
148
export function ConfigDetailsViewPage() {
15-
const queryClient = useQueryClient();
16-
const forceRefreshRef = useRef(false);
17-
189
const { id: configId, viewId } = useParams<{
1910
id: string;
2011
viewId: string;
2112
}>();
2213

23-
// Fetch display plugin variables from the API
24-
const { data: variables } = useQuery({
25-
queryKey: ["viewDisplayPluginVariables", viewId, configId],
26-
queryFn: () => getViewDisplayPluginVariables(viewId!, configId!),
27-
enabled: !!viewId && !!configId
28-
});
29-
3014
const {
31-
data: viewResult,
15+
viewResult,
3216
isLoading,
3317
error,
34-
refetch
35-
} = useQuery({
36-
queryKey: ["viewDataById", viewId, configId, variables],
37-
queryFn: () => {
38-
if (!viewId) {
39-
throw new Error("View ID is required");
40-
}
41-
const headers = forceRefreshRef.current
42-
? { "cache-control": "max-age=1" }
43-
: undefined;
44-
45-
return getViewDataById(viewId, variables, headers);
46-
},
47-
enabled: !!viewId && !!configId && !!variables
18+
aggregatedVariables,
19+
currentVariables,
20+
handleForceRefresh
21+
} = useViewData({
22+
viewId: viewId!,
23+
configId
4824
});
4925

50-
const handleRefresh = async () => {
51-
forceRefreshRef.current = true;
52-
const result = await refetch();
53-
forceRefreshRef.current = false;
54-
55-
if (result.isError) {
56-
toastError(
57-
result.error instanceof Error
58-
? result.error.message
59-
: "Failed to refresh view"
60-
);
61-
} else if (result.data?.namespace && result.data?.name) {
62-
await queryClient.invalidateQueries({
63-
queryKey: ["view-table", result.data.namespace, result.data.name]
64-
});
65-
}
66-
};
67-
6826
const displayName = viewResult?.title || viewResult?.name || "";
6927

7028
return (
7129
<ConfigDetailsTabs
7230
pageTitlePrefix={`Config View - ${displayName}`}
7331
isLoading={isLoading}
7432
activeTabName={displayName}
75-
refetch={handleRefresh}
33+
refetch={handleForceRefresh}
7634
>
77-
<div className="flex h-full flex-1 flex-col overflow-auto p-4">
35+
<div className="">
7836
{isLoading ? (
7937
<div className="flex flex-1 flex-col items-center justify-center">
8038
<Loading />
8139
</div>
8240
) : error ? (
8341
<div className="flex flex-1 flex-col items-center justify-center">
84-
<div className="text-center">
85-
<div className="mb-4 text-xl text-red-500">Error</div>
86-
<p className="text-gray-600">
87-
{error instanceof Error
88-
? error.message
89-
: "Failed to load view data"}
90-
</p>
91-
</div>
42+
<ErrorViewer error={error} className="max-w-3xl" />
9243
</div>
9344
) : viewResult ? (
94-
<View
95-
title=""
96-
namespace={viewResult.namespace}
97-
name={viewResult.name}
98-
panels={viewResult.panels}
99-
columns={viewResult.columns}
100-
card={viewResult.card}
101-
table={viewResult.table}
102-
requestFingerprint={viewResult.requestFingerprint}
103-
columnOptions={viewResult.columnOptions}
45+
<ViewWithSections
46+
viewResult={viewResult}
47+
aggregatedVariables={aggregatedVariables}
48+
currentVariables={currentVariables}
49+
hideVariables
10450
/>
10551
) : (
10652
<div className="flex flex-1 flex-col items-center justify-center">

src/pages/views/components/SingleView.tsx

Lines changed: 15 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,24 @@
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";
52
import Age from "../../../ui/Age/Age";
6-
import { toastError } from "../../../components/Toast/toast";
73
import 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";
136
import { ErrorViewer } from "@flanksource-ui/components/ErrorViewer";
147

158
interface SingleViewProps {
169
id: string;
1710
}
1811

1912
const 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
};

src/pages/views/components/ViewSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const ViewSection: React.FC<ViewSectionProps> = ({
3535
} = useQuery({
3636
queryKey: ["view-result", namespace, name, currentViewVariables],
3737
queryFn: () =>
38-
getViewDataByNamespace(namespace, name, currentViewVariables),
38+
getViewDataByNamespace(namespace || "", name, currentViewVariables),
3939
enabled: !!namespace && !!name,
4040
staleTime: 5 * 60 * 1000
4141
});

0 commit comments

Comments
 (0)