Skip to content

Commit c6f846d

Browse files
committed
fix: view error handling
1 parent 5accfb5 commit c6f846d

File tree

3 files changed

+85
-41
lines changed

3 files changed

+85
-41
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@ export interface ViewResult {
433433
namespace?: string;
434434
name: string;
435435

436+
refreshStatus?: "cache" | "fresh" | "error";
437+
refreshError?: string;
438+
responseSource?: "cache" | "fresh";
439+
436440
lastRefreshedAt?: string;
437441
columns?: ViewColumnDef[];
438442
rows?: ViewRow[];

src/pages/views/components/SingleView.tsx

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import React from "react";
1+
import React, { useEffect, useState } from "react";
22
import Age from "../../../ui/Age/Age";
33
import ViewLayout from "./ViewLayout";
44
import ViewWithSections from "./ViewWithSections";
55
import { useViewData } from "../hooks/useViewData";
66
import { ErrorViewer } from "@flanksource-ui/components/ErrorViewer";
7+
import {
8+
Dialog,
9+
DialogContent,
10+
DialogDescription,
11+
DialogHeader,
12+
DialogTitle
13+
} from "@flanksource-ui/components/ui/dialog";
714

815
interface SingleViewProps {
916
id: string;
@@ -19,32 +26,43 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
1926
currentVariables,
2027
handleForceRefresh
2128
} = useViewData({ viewId: id });
29+
const [refreshErrorOpen, setRefreshErrorOpen] = useState(false);
2230

23-
if (isLoading && !viewResult) {
31+
const refreshError =
32+
viewResult?.refreshStatus === "error" ? viewResult.refreshError : undefined;
33+
const isCachedResponse = viewResult?.responseSource === "cache";
34+
35+
useEffect(() => {
36+
if (refreshError && isCachedResponse) {
37+
setRefreshErrorOpen(true);
38+
}
39+
}, [refreshError, isCachedResponse, viewResult?.requestFingerprint]);
40+
41+
if (error && !viewResult) {
2442
return (
2543
<ViewLayout
26-
title="View"
44+
title="View Error"
2745
icon="workflow"
2846
onRefresh={handleForceRefresh}
2947
centered
3048
>
31-
<div className="text-center">
32-
<div className="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-blue-600"></div>
33-
<p className="text-gray-600">Loading view results...</p>
34-
</div>
49+
<ErrorViewer error={error} className="mx-auto max-w-3xl" />
3550
</ViewLayout>
3651
);
3752
}
3853

39-
if (error) {
54+
if (isLoading && !viewResult) {
4055
return (
4156
<ViewLayout
42-
title="View Error"
57+
title="View"
4358
icon="workflow"
4459
onRefresh={handleForceRefresh}
4560
centered
4661
>
47-
<ErrorViewer error={error} className="mx-auto max-w-3xl" />
62+
<div className="text-center">
63+
<div className="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-blue-600"></div>
64+
<p className="text-gray-600">Loading view results...</p>
65+
</div>
4866
</ViewLayout>
4967
);
5068
}
@@ -68,27 +86,43 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
6886
const { icon, title, name } = viewResult;
6987

7088
return (
71-
<ViewLayout
72-
title={title || name}
73-
icon={icon || "workflow"}
74-
onRefresh={handleForceRefresh}
75-
loading={isFetching}
76-
extra={
77-
viewResult.lastRefreshedAt && (
78-
<p className="text-sm text-gray-500">
79-
Last refreshed:{" "}
80-
<Age from={viewResult.lastRefreshedAt} format="full" />
81-
</p>
82-
)
83-
}
84-
>
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-
/>
91-
</ViewLayout>
89+
<>
90+
<Dialog open={refreshErrorOpen} onOpenChange={setRefreshErrorOpen}>
91+
<DialogContent className="max-w-xl">
92+
<DialogHeader>
93+
<DialogTitle>View refresh failed</DialogTitle>
94+
<DialogDescription>
95+
The view failed to refresh. You are seeing cached data from the
96+
last successful refresh.
97+
</DialogDescription>
98+
</DialogHeader>
99+
{refreshError ? (
100+
<ErrorViewer error={refreshError} className="mt-4" />
101+
) : null}
102+
</DialogContent>
103+
</Dialog>
104+
<ViewLayout
105+
title={title || name}
106+
icon={icon || "workflow"}
107+
onRefresh={handleForceRefresh}
108+
loading={isFetching}
109+
extra={
110+
viewResult.lastRefreshedAt && (
111+
<p className="text-sm text-gray-500">
112+
Last refreshed:{" "}
113+
<Age from={viewResult.lastRefreshedAt} format="full" />
114+
</p>
115+
)
116+
}
117+
>
118+
<ViewWithSections
119+
className="flex h-full w-full flex-1 flex-col overflow-y-auto px-6"
120+
viewResult={viewResult}
121+
aggregatedVariables={aggregatedVariables}
122+
currentVariables={currentVariables}
123+
/>
124+
</ViewLayout>
125+
</>
92126
);
93127
};
94128

src/pages/views/hooks/useViewData.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,18 @@ export function useViewData({
4949

5050
const variables = isDisplayPluginMode ? displayPluginVariables : undefined;
5151

52+
const viewQueryKey = isDisplayPluginMode
53+
? ["viewDataById", viewId, configId, variables]
54+
: ["view-result", viewId];
55+
5256
const {
5357
data: viewResult,
5458
isLoading: isLoadingViewResult,
5559
isFetching: isFetchingViewResult,
5660
error: viewResultError,
5761
refetch
5862
} = useQuery({
59-
queryKey: isDisplayPluginMode
60-
? ["viewDataById", viewId, configId, variables]
61-
: ["view-result", viewId],
63+
queryKey: viewQueryKey,
6264
queryFn: () => {
6365
const headers = forceRefreshRef.current
6466
? { "cache-control": "max-age=1" }
@@ -120,21 +122,25 @@ export function useViewData({
120122
: result.data?.name
121123
? [{ namespace: result.data.namespace ?? "", name: result.data.name }]
122124
: [];
125+
const currentNamespace = result.data?.namespace ?? viewResult?.namespace;
126+
const currentName = result.data?.name ?? viewResult?.name;
127+
const refsToInvalidate = sectionsToRefresh.filter(
128+
(section) =>
129+
!(
130+
currentName &&
131+
section.name === currentName &&
132+
section.namespace === (currentNamespace ?? "")
133+
)
134+
);
123135

124136
if (isDisplayPluginMode) {
125137
await queryClient.invalidateQueries({
126138
queryKey: ["viewDisplayPluginVariables", viewId, configId]
127139
});
128140
}
129141

130-
await queryClient.invalidateQueries({
131-
queryKey: isDisplayPluginMode
132-
? ["viewDataById", viewId, configId, variables]
133-
: ["view-result", viewId]
134-
});
135-
136142
await Promise.all(
137-
sectionsToRefresh.flatMap((section) => [
143+
refsToInvalidate.flatMap((section) => [
138144
queryClient.invalidateQueries({
139145
queryKey: ["view-result", section.namespace, section.name]
140146
}),

0 commit comments

Comments
 (0)