Skip to content

Commit 24f6e51

Browse files
committed
Add request cancellation and loading state to billing hook
1 parent 5d97f04 commit 24f6e51

File tree

1 file changed

+31
-19
lines changed

1 file changed

+31
-19
lines changed

src/slurmcostmanager.js

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const PLUGIN_BASE =
1212
window.cockpit.manifest.path) ||
1313
'/usr/share/cockpit/slurmcostmanager';
1414

15+
const HAS_COCKPIT =
16+
typeof window !== 'undefined' && window.cockpit && window.cockpit.spawn;
17+
1518
function getBillingPeriod(ref = new Date()) {
1619
const today = new Date();
1720
let year, month, end;
@@ -50,19 +53,19 @@ function getYearPeriod(year = new Date().getFullYear()) {
5053
function useBillingData(period) {
5154
const [data, setData] = useState(null);
5255
const [error, setError] = useState(null);
53-
const requestIdRef = useRef(0);
56+
const [loading, setLoading] = useState(false);
57+
const abortRef = useRef(null);
5458

5559
const load = useCallback(async () => {
56-
const id = ++requestIdRef.current;
57-
// Clear out existing data while loading a new period so that views
58-
// such as "Detailed Transactions" don't momentarily display data
59-
// from the previously selected period (e.g. the fiscal year) when
60-
// navigating directly between views.
61-
setData(null);
60+
if (abortRef.current) {
61+
if (abortRef.current.abort) abortRef.current.abort();
62+
if (abortRef.current.close) abortRef.current.close();
63+
}
64+
setLoading(true);
6265
setError(null);
6366
try {
6467
let json;
65-
if (window.cockpit && window.cockpit.spawn) {
68+
if (HAS_COCKPIT) {
6669
let start, end;
6770
if (typeof period === 'string') {
6871
({ start, end } = getBillingPeriod(period));
@@ -81,30 +84,39 @@ function useBillingData(period) {
8184
'--output',
8285
'-',
8386
];
84-
const output = await window.cockpit.spawn(args, { err: 'message' });
87+
const proc = window.cockpit.spawn(args, { err: 'message' });
88+
abortRef.current = proc;
89+
const output = await proc;
8590
json = JSON.parse(output);
8691
} else {
87-
const resp = await fetch('billing.json');
92+
const controller = new AbortController();
93+
abortRef.current = controller;
94+
const resp = await fetch('billing.json', { signal: controller.signal });
8895
if (!resp.ok) throw new Error('Failed to fetch billing data');
8996
json = await resp.json();
9097
}
91-
if (requestIdRef.current === id) {
92-
setData(json);
93-
setError(null);
94-
}
98+
setData(json);
9599
} catch (e) {
96-
console.error(e);
97-
if (requestIdRef.current === id) {
100+
if (e.name !== 'AbortError') {
101+
console.error(e);
98102
setError(e.message || String(e));
99103
}
104+
} finally {
105+
setLoading(false);
100106
}
101107
}, [period]);
102108

103109
useEffect(() => {
104110
load();
111+
return () => {
112+
if (abortRef.current) {
113+
if (abortRef.current.abort) abortRef.current.abort();
114+
if (abortRef.current.close) abortRef.current.close();
115+
}
116+
};
105117
}, [load]);
106118

107-
return { data, error, reload: load };
119+
return { data, error, loading, reload: load };
108120
}
109121

110122
function aggregateAccountDetails(details = []) {
@@ -1928,7 +1940,7 @@ function App() {
19281940
const [month, setMonth] = useState(defaultMonth);
19291941
const yearPeriod = useMemo(() => getYearPeriod(currentYear), [currentYear]);
19301942
const period = view === 'year' ? yearPeriod : month;
1931-
const { data, error, reload } = useBillingData(period);
1943+
const { data, error, loading, reload } = useBillingData(period);
19321944
const details = useMemo(() => {
19331945
if (!data) return [];
19341946
return view === 'year'
@@ -1985,7 +1997,7 @@ function App() {
19851997
)
19861998
)
19871999
),
1988-
view !== 'settings' && !data && !error && React.createElement('p', null, 'Loading...'),
2000+
view !== 'settings' && loading && React.createElement('p', null, 'Loading...'),
19892001
view !== 'settings' &&
19902002
error &&
19912003
React.createElement(

0 commit comments

Comments
 (0)