Skip to content

Commit 774b60f

Browse files
committed
feat: implement query settings management for team members table with filtering and pagination
1 parent d79c780 commit 774b60f

File tree

6 files changed

+155
-35
lines changed

6 files changed

+155
-35
lines changed

src/authz-module/data/api.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ import { LibraryMetadata, TeamMember } from '@src/types';
33
import { camelCaseObject } from '@edx/frontend-platform';
44
import { getApiUrl, getStudioApiUrl } from '@src/data/utils';
55

6+
export interface QuerySettings {
7+
roles: string | null;
8+
search: string | null;
9+
ordering: string | null;
10+
pageSize: number;
11+
pageIndex: number;
12+
}
13+
614
export interface GetTeamMembersResponse {
715
members: TeamMember[];
816
totalCount: number;
@@ -15,8 +23,22 @@ export type PermissionsByRole = {
1523
};
1624

1725
// TODO: replece api path once is created
18-
export const getTeamMembers = async (object: string): Promise<TeamMember[]> => {
19-
const { data } = await getAuthenticatedHttpClient().get(getApiUrl(`/api/authz/v1/roles/users?scope=${object}`));
26+
export const getTeamMembers = async (object: string, querySettings: QuerySettings): Promise<TeamMember[]> => {
27+
const url = new URL(getApiUrl(`/api/authz/v1/roles/users?scope=${object}`));
28+
29+
if (querySettings.roles) {
30+
url.searchParams.set('roles', querySettings.roles);
31+
}
32+
if (querySettings.search) {
33+
url.searchParams.set('search', querySettings.search);
34+
}
35+
if (querySettings.ordering) {
36+
url.searchParams.set('ordering', querySettings.ordering);
37+
}
38+
url.searchParams.set('page_size', querySettings.pageSize.toString());
39+
url.searchParams.set('page', (querySettings.pageIndex + 1).toString());
40+
41+
const { data } = await getAuthenticatedHttpClient().get(url);
2042
return camelCaseObject(data.results);
2143
};
2244

src/authz-module/data/hooks.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
22
import { appId } from '@src/constants';
33
import { LibraryMetadata, TeamMember } from '@src/types';
44
import {
5-
getLibrary, getPermissionsByRole, getTeamMembers, PermissionsByRole,
5+
getLibrary, getPermissionsByRole, getTeamMembers, PermissionsByRole, QuerySettings,
66
} from './api';
77

88
const authzQueryKeys = {
99
all: [appId, 'authz'] as const,
10-
teamMembers: (object: string) => [...authzQueryKeys.all, 'teamMembers', object] as const,
10+
teamMembers: (object: string, querySettings: QuerySettings) => [
11+
...authzQueryKeys.all,
12+
'teamMembers',
13+
object,
14+
querySettings.roles,
15+
querySettings.search,
16+
querySettings.ordering,
17+
querySettings.pageSize,
18+
querySettings.pageIndex,
19+
] as const,
1120
permissionsByRole: (scope: string) => [...authzQueryKeys.all, 'permissionsByRole', scope] as const,
1221
library: (libraryId: string) => [...authzQueryKeys.all, 'library', libraryId] as const,
1322
};
@@ -17,17 +26,24 @@ const authzQueryKeys = {
1726
* It retrieves the full list of members who have access to the given scope.
1827
*
1928
* @param object - The unique identifier of the object/scope
29+
* @param querySettings - Optional query parameters for filtering, sorting, and pagination
2030
*
2131
* @example
2232
* ```tsx
23-
* const { data: teamMembers, isLoading, isError } = useTeamMembers('lib:123');
33+
* const { data: teamMembers, isLoading, isError } = useTeamMembers('lib:123', querySettings);
2434
* ```
2535
*/
26-
export const useTeamMembers = (object: string) => useQuery<TeamMember[], Error>({
27-
queryKey: authzQueryKeys.teamMembers(object),
28-
queryFn: () => getTeamMembers(object),
29-
staleTime: 1000 * 60 * 30, // refetch after 30 minutes
30-
});
36+
export const useTeamMembers = (object: string, querySettings: QuerySettings) => {
37+
const queryKey = authzQueryKeys.teamMembers(object, querySettings);
38+
39+
return useQuery<TeamMember[], Error>({
40+
queryKey,
41+
queryFn: () => getTeamMembers(object, querySettings),
42+
staleTime: 1000 * 60 * 5,
43+
enabled: !!object,
44+
refetchOnWindowFocus: false,
45+
});
46+
};
3147

3248
/**
3349
* React Query hook to fetch all the roles for the specific object/scope.

src/authz-module/libraries-manager/components/MultipleChoiceFilter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const MultipleChoiceFilter: FC<MultipleChoiceFilterProps> = ({
4747
<Form.Checkbox
4848
className="m-2"
4949
key={name}
50-
value={name}
50+
value={value}
5151
onChange={() => changeCheckbox(value)}
5252
aria-label={name}
5353
>

src/authz-module/libraries-manager/components/TeamTable.tsx

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { Edit } from '@openedx/paragon/icons';
1010
import { TableCellValue, TeamMember } from '@src/types';
1111
import { ROUTES } from '@src/authz-module/constants';
1212
import { useTeamMembers } from '@src/authz-module/data/hooks';
13+
import {
14+
useMemo,
15+
} from 'react';
1316
import { useLibraryAuthZ } from '../context';
17+
import { useQuerySettings } from '../hooks';
1418
import messages from './messages';
1519
import TableControlBar from './TableControlBar';
1620

@@ -58,37 +62,30 @@ const RolesCell = ({ row }: CellProps) => (row.original.username === SKELETON_RO
5862

5963
const TeamTable = () => {
6064
const intl = useIntl();
61-
const { libraryId, canManageTeam, username } = useLibraryAuthZ();
65+
66+
const { querySettings, handleTableFetch } = useQuerySettings();
67+
68+
const {
69+
libraryId, canManageTeam, username, roles,
70+
} = useLibraryAuthZ();
6271

6372
// TODO: Display error in the notification system
6473
const {
6574
data: teamMembers, isLoading, isError,
66-
} = useTeamMembers(libraryId);
75+
} = useTeamMembers(libraryId, querySettings);
6776

6877
const rows = isError ? [] : (teamMembers || SKELETON_ROWS);
6978

7079
const navigate = useNavigate();
7180

72-
const reducedChoices = teamMembers?.reduce((acc, currentObject) => {
73-
const { roles } = currentObject;
74-
roles.forEach((role) => {
75-
if (role in acc) {
76-
acc[role].number += 1;
77-
} else {
78-
acc[role] = {
79-
name: role,
80-
number: 1,
81-
value: role,
82-
};
83-
}
84-
});
85-
return acc;
86-
}, {}) ?? {};
87-
88-
const handleFetchData = (querySettings) => {
89-
console.log('Filters', querySettings.filters);
90-
console.log('Sorting', querySettings.sortBy);
91-
};
81+
const adaptedFilterChoices = useMemo(
82+
() => roles.map((role) => ({
83+
name: role.name,
84+
number: role.userCount,
85+
value: role.role,
86+
})),
87+
[roles],
88+
);
9289

9390
return (
9491
<DataTable
@@ -100,7 +97,7 @@ const TeamTable = () => {
10097
manualPagination
10198
isSortable
10299
manualSortBy
103-
fetchData={debounce(handleFetchData, 1000)}
100+
fetchData={debounce(handleTableFetch, 1000)}
104101
data={rows}
105102
itemCount={rows?.length}
106103
additionalColumns={[
@@ -147,7 +144,7 @@ const TeamTable = () => {
147144
Cell: RolesCell,
148145
Filter: CheckboxFilter,
149146
filter: 'includesValue',
150-
filterChoices: Object.values(reducedChoices),
147+
filterChoices: Object.values(adaptedFilterChoices),
151148
disableSortBy: true,
152149
},
153150
{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useQuerySettings } from './useQuerySettings';
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useCallback, useState } from 'react';
2+
import { QuerySettings } from '@src/authz-module/data/api';
3+
4+
interface DataTableFilters {
5+
pageSize: number;
6+
pageIndex: number;
7+
sortBy: Array<{ id: string; desc: boolean }>;
8+
filters: Array<{ id: string; value: any }>;
9+
}
10+
11+
interface UseQuerySettingsReturn {
12+
querySettings: QuerySettings;
13+
handleTableFetch: (tableFilters: DataTableFilters) => void;
14+
}
15+
16+
/**
17+
* Custom hook to manage query settings for table data fetching
18+
* Converts DataTable filter/sort/pagination settings to API query parameters
19+
* and manages URL synchronization
20+
*
21+
* @param initialQuerySettings - Initial query settings
22+
* @returns Object containing querySettings and handleTableFetch function
23+
*/
24+
export const useQuerySettings = (
25+
initialQuerySettings: QuerySettings = {
26+
roles: null,
27+
search: null,
28+
pageSize: 10,
29+
pageIndex: 0,
30+
ordering: null,
31+
},
32+
): UseQuerySettingsReturn => {
33+
const [querySettings, setQuerySettings] = useState<QuerySettings>(initialQuerySettings);
34+
35+
const handleTableFetch = useCallback((tableFilters: DataTableFilters) => {
36+
setQuerySettings((prevSettings) => {
37+
// Extract filters
38+
const rolesFilter = tableFilters.filters.find((filter) => filter.id === 'roles')?.value?.join(',') ?? '';
39+
const searchFilter = tableFilters.filters.find((filter) => filter.id === 'username')?.value ?? '';
40+
41+
// Extract pagination
42+
const { pageSize = 10, pageIndex = 0 } = tableFilters;
43+
44+
// Extract and convert sorting
45+
let ordering = '';
46+
if (tableFilters.sortBy.length) {
47+
const snakeCaseId = tableFilters.sortBy[0].id.replace(/([A-Z])/g, '_$1').toLowerCase();
48+
49+
if (tableFilters.sortBy[0].desc) {
50+
ordering = `-${snakeCaseId}`;
51+
} else {
52+
ordering = snakeCaseId;
53+
}
54+
}
55+
56+
const newQuerySettings: QuerySettings = {
57+
roles: rolesFilter || null,
58+
search: searchFilter || null,
59+
ordering: ordering || null,
60+
pageSize,
61+
pageIndex,
62+
};
63+
64+
const hasChanged = (
65+
prevSettings.roles !== newQuerySettings.roles
66+
|| prevSettings.search !== newQuerySettings.search
67+
|| prevSettings.pageSize !== newQuerySettings.pageSize
68+
|| prevSettings.pageIndex !== newQuerySettings.pageIndex
69+
|| prevSettings.ordering !== newQuerySettings.ordering
70+
);
71+
72+
if (!hasChanged) {
73+
return prevSettings; // No change, prevent unnecessary update
74+
}
75+
76+
return newQuerySettings;
77+
});
78+
}, []);
79+
80+
return {
81+
querySettings,
82+
handleTableFetch,
83+
};
84+
};

0 commit comments

Comments
 (0)