Skip to content

Commit 8936a26

Browse files
committed
perf: replace sequential pagination with single bulk request
Signed-off-by: Vladimir Belousov <vbelouso@redhat.com>
1 parent 05be245 commit 8936a26

File tree

6 files changed

+92
-87
lines changed

6 files changed

+92
-87
lines changed

src/app/Accounts/components/AccountsTable.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { api, AccountResponseApi, ProviderApi } from '@api';
55
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
66
import { TablePagination } from '@app/components/common/TablesPagination';
77
import { searchItems, filterByProvider, sortItems, paginateItems } from '@app/utils/tableFilters';
8-
import { fetchAllPages } from '@app/utils/fetchAllPages';
98

109
export const AccountsTable: React.FunctionComponent<{
1110
searchValue: string;
@@ -20,21 +19,28 @@ export const AccountsTable: React.FunctionComponent<{
2019
const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc'>('asc');
2120

2221
useEffect(() => {
22+
const controller = new AbortController();
23+
2324
const fetchData = async () => {
24-
setLoading(true);
2525
try {
26-
const allItems = await fetchAllPages(async (page, pageSize) => {
27-
const { data } = await api.accounts.accountsList({ page, page_size: pageSize });
28-
return { items: data.items || [], count: data.count || 0 };
29-
});
30-
setAllAccounts(allItems);
26+
setLoading(true);
27+
const { data } = await api.accounts.accountsList({ page: 1, page_size: 10000 }, { signal: controller.signal });
28+
if (!controller.signal.aborted) {
29+
setAllAccounts(data.items || []);
30+
}
3131
} catch (error) {
32-
console.error('Error fetching accounts:', error);
32+
if (!controller.signal.aborted) {
33+
console.error('Error fetching accounts:', error);
34+
}
3335
} finally {
34-
setLoading(false);
36+
if (!controller.signal.aborted) {
37+
setLoading(false);
38+
}
3539
}
3640
};
41+
3742
fetchData();
43+
return () => controller.abort();
3844
}, []);
3945

4046
let filtered = allAccounts;

src/app/Actions/Scheduler/components/ActionsTable.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { renderActionTypeLabel, renderOperationLabel, renderActionStatusLabel } from '@app/utils/renderUtils';
22
import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
33
import { Label } from '@patternfly/react-core';
4-
import React, { useState, useEffect } from 'react';
4+
import React, { useState, useEffect, useRef, useCallback } from 'react';
55
import { ActionStatus, ActionOperations, ActionTypes } from '@app/types/types';
66
import { Link } from 'react-router-dom';
77
import { api, ActionResponseApi } from '@api';
88
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
99
import { TablePagination } from '@app/components/common/TablesPagination';
1010
import { paginateItems } from '@app/utils/tableFilters';
11-
import { fetchAllPages } from '@app/utils/fetchAllPages';
1211
import { ActionsColumn } from '@patternfly/react-table';
1312
import { rowActions } from './ActionsKebabMenu';
1413

@@ -26,29 +25,38 @@ export const ScheduleActionsTable: React.FunctionComponent<{
2625
const [filteredActions, setFilteredActions] = useState<ActionResponseApi[]>([]);
2726
const [filteredCount, setFilteredCount] = useState(0);
2827
const [loading, setLoading] = useState(true);
28+
const controllerRef = useRef<AbortController | null>(null);
29+
30+
const reloadActions = useCallback(async () => {
31+
if (controllerRef.current) {
32+
controllerRef.current.abort();
33+
}
34+
controllerRef.current = new AbortController();
2935

30-
// useEffect logic abstracted in this function to be reused in the kebab menu
31-
const reloadActions = async () => {
3236
setLoading(true);
3337
try {
34-
const allItems = await fetchAllPages(async (page, pageSize) => {
35-
const { data } = await api.schedule.scheduleList({
36-
page,
37-
page_size: pageSize,
38-
});
39-
return { items: data.items || [], count: data.count || 0 };
40-
});
41-
setAllActions(allItems);
38+
const { data } = await api.schedule.scheduleList(
39+
{ page: 1, page_size: 10000 },
40+
{ signal: controllerRef.current.signal }
41+
);
42+
if (!controllerRef.current.signal.aborted) {
43+
setAllActions(data.items || []);
44+
}
4245
} catch (error) {
43-
console.error('Error fetching ScheduleActions:', error);
46+
if (!controllerRef.current?.signal.aborted) {
47+
console.error('Error fetching ScheduleActions:', error);
48+
}
4449
} finally {
45-
setLoading(false);
50+
if (!controllerRef.current?.signal.aborted) {
51+
setLoading(false);
52+
}
4653
}
47-
};
54+
}, []);
4855

4956
useEffect(() => {
5057
reloadActions();
51-
}, [reloadFlag]);
58+
return () => controllerRef.current?.abort();
59+
}, [reloadFlag, reloadActions]);
5260

5361
// Apply filters when data or filter props change
5462
useEffect(() => {

src/app/Actions/Scheduler/components/ModalPowerManagement.tsx

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { ActionStatus } from '@app/types/types';
2020
import { useUser } from '@app/Contexts/UserContext.tsx';
2121
import { debug } from '@app/utils/debugLogs';
2222
import { api, startCluster, stopCluster, AccountResponseApi, ClusterResponseApi, ActionRequestApi } from '@api';
23-
import { fetchAllPages } from '@app/utils/fetchAllPages';
2423
import cronValidate from 'cron-validate';
2524

2625
interface ModalPowerManagementProps {
@@ -85,53 +84,53 @@ export const ModalPowerManagement: React.FunctionComponent<ModalPowerManagementP
8584
React.useEffect(() => {
8685
if (!isOpen) return;
8786

87+
const controller = new AbortController();
88+
8889
const fetchAccounts = async () => {
89-
//setLoading(true);
9090
try {
91-
const accountFullList = await fetchAllPages(async (page, pageSize) => {
92-
const { data } = await api.accounts.accountsList({ page, page_size: pageSize });
93-
return { items: data.items || [], count: data.count || 0 };
94-
});
95-
setAllAccounts(accountFullList);
91+
const { data } = await api.accounts.accountsList({ page: 1, page_size: 10000 }, { signal: controller.signal });
92+
if (!controller.signal.aborted) {
93+
setAllAccounts(data.items || []);
94+
}
9695
} catch (error) {
97-
console.error('Error fetching accounts:', error);
98-
setAllAccounts([]);
99-
} finally {
100-
//setLoading(false);
96+
if (!controller.signal.aborted) {
97+
console.error('Error fetching accounts:', error);
98+
setAllAccounts([]);
99+
}
101100
}
102101
};
103102

104103
fetchAccounts();
104+
return () => controller.abort();
105105
}, [isOpen]);
106106

107-
// Reload clusters every time the selected account changes.
108107
React.useEffect(() => {
109108
if (!isOpen) return;
110109

111-
// Reset dependent state when account changes / clears
112110
setSelectedCluster(null);
113111
setAllClusters([]);
114112

115113
const accountId = selectedAccount?.accountId;
116114
if (!accountId) return;
117115

116+
const controller = new AbortController();
117+
118118
const fetchClusters = async () => {
119-
//setLoading(true);
120119
try {
121-
const clusterFullList = await fetchAllPages(async (page, pageSize) => {
122-
const { data } = await api.accounts.clustersList(accountId, { page, page_size: pageSize });
123-
return { items: data.items || [], count: data.count || 0 };
124-
});
125-
setAllClusters(clusterFullList);
120+
const { data } = await api.accounts.clustersList(accountId, { signal: controller.signal });
121+
if (!controller.signal.aborted) {
122+
setAllClusters(data.items || []);
123+
}
126124
} catch (error) {
127-
console.error('Error fetching clusters:', error);
128-
setAllClusters([]);
129-
} finally {
130-
//setLoading(false);
125+
if (!controller.signal.aborted) {
126+
console.error('Error fetching clusters:', error);
127+
setAllClusters([]);
128+
}
131129
}
132130
};
133131

134132
fetchClusters();
133+
return () => controller.abort();
135134
}, [isOpen, selectedAccount?.accountId]);
136135

137136
// Reset modal state when closing to avoid leaking previous selections.

src/app/Clusters/components/ClustersTable.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { ClustersTableProps } from '../types';
77
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
88
import { TablePagination } from '@app/components/common/TablesPagination';
99
import { searchItems, filterByStatus, filterByProvider, sortItems, paginateItems } from '@app/utils/tableFilters';
10-
import { fetchAllPages } from '@app/utils/fetchAllPages';
1110
import { EmptyState, EmptyStateVariant, EmptyStateBody, Title } from '@patternfly/react-core';
1211
import { CubesIcon } from '@patternfly/react-icons';
1312

@@ -27,21 +26,28 @@ export const ClustersTable: React.FunctionComponent<ClustersTableProps> = ({
2726
const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc'>('asc');
2827

2928
useEffect(() => {
29+
const controller = new AbortController();
30+
3031
const fetchData = async () => {
3132
try {
3233
setLoading(true);
33-
const allItems = await fetchAllPages(async (page, pageSize) => {
34-
const { data } = await api.clusters.clustersList({ page, page_size: pageSize });
35-
return { items: data.items || [], count: data.count || 0 };
36-
});
37-
setAllClusters(allItems);
34+
const { data } = await api.clusters.clustersList({ page: 1, page_size: 10000 }, { signal: controller.signal });
35+
if (!controller.signal.aborted) {
36+
setAllClusters(data.items || []);
37+
}
3838
} catch (error) {
39-
console.error('Error fetching clusters:', error);
39+
if (!controller.signal.aborted) {
40+
console.error('Error fetching clusters:', error);
41+
}
4042
} finally {
41-
setLoading(false);
43+
if (!controller.signal.aborted) {
44+
setLoading(false);
45+
}
4246
}
4347
};
48+
4449
fetchData();
50+
return () => controller.abort();
4551
}, []);
4652

4753
let processed = allClusters;

src/app/Servers/components/ServersTable.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { ServersTableProps } from '../types';
77
import { api, InstanceResponseApi } from '@api';
88
import { TablePagination } from '@app/components/common/TablesPagination';
99
import { searchItems, filterByStatus, filterByProvider, sortItems, paginateItems } from '@app/utils/tableFilters';
10-
import { fetchAllPages } from '@app/utils/fetchAllPages';
1110
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
1211
import { ServerIcon } from '@patternfly/react-icons';
1312

@@ -26,21 +25,31 @@ export const ServersTable: React.FunctionComponent<ServersTableProps> = ({
2625
const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc'>('asc');
2726

2827
useEffect(() => {
28+
const controller = new AbortController();
29+
2930
const fetchData = async () => {
3031
try {
3132
setLoading(true);
32-
const allItems = await fetchAllPages(async (page, pageSize) => {
33-
const { data } = await api.instances.instancesList({ page, page_size: pageSize });
34-
return { items: data.items || [], count: data.count || 0 };
35-
});
36-
setAllInstances(allItems);
33+
const { data } = await api.instances.instancesList(
34+
{ page: 1, page_size: 10000 },
35+
{ signal: controller.signal }
36+
);
37+
if (!controller.signal.aborted) {
38+
setAllInstances(data.items || []);
39+
}
3740
} catch (error) {
38-
console.error('Error fetching instances:', error);
41+
if (!controller.signal.aborted) {
42+
console.error('Error fetching instances:', error);
43+
}
3944
} finally {
40-
setLoading(false);
45+
if (!controller.signal.aborted) {
46+
setLoading(false);
47+
}
4148
}
4249
};
50+
4351
fetchData();
52+
return () => controller.abort();
4453
}, []);
4554

4655
let filtered = allInstances;

src/app/utils/fetchAllPages.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)