refactor activity page to use react query requests

Co-authored-by: Bill Thornton <thornbill@users.noreply.github.com>
This commit is contained in:
bu3alwa 2024-08-20 20:52:53 -04:00
parent 656799cce7
commit 710fe641e2
4 changed files with 98 additions and 63 deletions

View File

@ -92,6 +92,7 @@
- [Venkat Karasani](https://github.com/venkat-karasani) - [Venkat Karasani](https://github.com/venkat-karasani)
- [Connor Smith](https://github.com/ConnorS1110) - [Connor Smith](https://github.com/ConnorS1110)
- [iFraan](https://github.com/iFraan) - [iFraan](https://github.com/iFraan)
- [Ali](https://github.com/bu3alwa)
## Emby Contributors ## Emby Contributors

View File

@ -1,6 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry'; import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import PermMedia from '@mui/icons-material/PermMedia'; import PermMedia from '@mui/icons-material/PermMedia';
@ -14,7 +12,8 @@ import { Link, useSearchParams } from 'react-router-dom';
import Page from 'components/Page'; import Page from 'components/Page';
import UserAvatar from 'components/UserAvatar'; import UserAvatar from 'components/UserAvatar';
import { useApi } from 'hooks/useApi'; import { useLogEntires } from 'hooks/useLogEntries';
import { useUsers } from 'hooks/useUsers';
import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'scripts/datetime'; import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'scripts/datetime';
import globalize from 'lib/globalize'; import globalize from 'lib/globalize';
import { toBoolean } from 'utils/string'; import { toBoolean } from 'utils/string';
@ -41,19 +40,42 @@ const getActivityView = (param: string | null) => {
const getRowId = (row: ActivityLogEntry) => row.Id ?? -1; const getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
const Activity = () => { const Activity = () => {
const { api } = useApi();
const [ searchParams, setSearchParams ] = useSearchParams(); const [ searchParams, setSearchParams ] = useSearchParams();
const [ activityView, setActivityView ] = useState( const [ activityView, setActivityView ] = useState(
getActivityView(searchParams.get(VIEW_PARAM))); getActivityView(searchParams.get(VIEW_PARAM)));
const [ isLoading, setIsLoading ] = useState(true);
const [ paginationModel, setPaginationModel ] = useState({ const [ paginationModel, setPaginationModel ] = useState({
page: 0, page: 0,
pageSize: DEFAULT_PAGE_SIZE pageSize: DEFAULT_PAGE_SIZE
}); });
const [ rowCount, setRowCount ] = useState(0);
const [ rows, setRows ] = useState<ActivityLogEntry[]>([]); const { data: usersData, isLoading: isUsersLoading } = useUsers();
const [ users, setUsers ] = useState<Record<string, UserDto>>({});
type UsersRecords = Record<string, UserDto>;
const users: UsersRecords = useMemo(() => {
if (!usersData) return {};
return usersData.reduce<UsersRecords>((acc, user) => {
const userId = user.Id;
if (!userId) return acc;
return {
...acc,
[userId]: user
};
}, {});
}, [usersData]);
const activityParams = useMemo(() => ({
startIndex: paginationModel.page * paginationModel.pageSize,
limit: paginationModel.pageSize,
hasUserId: activityView !== ActivityView.All ? activityView === ActivityView.User : undefined
}), [activityView, paginationModel.page, paginationModel.pageSize]);
const { data: logEntries, isLoading: isLogEntriesLoading } = useLogEntires(activityParams);
const isLoading = isUsersLoading || isLogEntriesLoading;
const userColDef: GridColDef[] = activityView !== ActivityView.System ? [ const userColDef: GridColDef[] = activityView !== ActivityView.System ? [
{ {
@ -153,58 +175,6 @@ const Activity = () => {
} }
}, []); }, []);
useEffect(() => {
if (api) {
const fetchUsers = async () => {
const { data } = await getUserApi(api).getUsers();
const usersById: Record<string, UserDto> = {};
data.forEach(user => {
if (user.Id) {
usersById[user.Id] = user;
}
});
setUsers(usersById);
};
fetchUsers()
.catch(err => {
console.error('[activity] failed to fetch users', err);
});
}
}, [ api ]);
useEffect(() => {
if (api) {
const fetchActivity = async () => {
const params: {
startIndex: number,
limit: number,
hasUserId?: boolean
} = {
startIndex: paginationModel.page * paginationModel.pageSize,
limit: paginationModel.pageSize
};
if (activityView !== ActivityView.All) {
params.hasUserId = activityView === ActivityView.User;
}
const { data } = await getActivityLogApi(api)
.getLogEntries(params);
setRowCount(data.TotalRecordCount ?? 0);
setRows(data.Items ?? []);
setIsLoading(false);
};
setIsLoading(true);
fetchActivity()
.catch(err => {
console.error('[activity] failed to fetch activity log entries', err);
});
}
}, [ activityView, api, paginationModel ]);
useEffect(() => { useEffect(() => {
const currentViewParam = getActivityView(searchParams.get(VIEW_PARAM)); const currentViewParam = getActivityView(searchParams.get(VIEW_PARAM));
if (currentViewParam !== activityView) { if (currentViewParam !== activityView) {
@ -254,12 +224,12 @@ const Activity = () => {
</Box> </Box>
<DataGrid <DataGrid
columns={columns} columns={columns}
rows={rows} rows={logEntries?.Items || []}
pageSizeOptions={[ 10, 25, 50, 100 ]} pageSizeOptions={[ 10, 25, 50, 100 ]}
paginationMode='server' paginationMode='server'
paginationModel={paginationModel} paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel} onPaginationModelChange={setPaginationModel}
rowCount={rowCount} rowCount={logEntries?.TotalRecordCount || 0}
getRowId={getRowId} getRowId={getRowId}
loading={isLoading} loading={isLoading}
sx={{ sx={{

View File

@ -0,0 +1,33 @@
import type { ActivityLogApiGetLogEntriesRequest } from '@jellyfin/sdk/lib/generated-client';
import type { AxiosRequestConfig } from 'axios';
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
import { useQuery } from '@tanstack/react-query';
import { JellyfinApiContext, useApi } from './useApi';
const fetchGetLogEntries = async (
currentApi: JellyfinApiContext,
requestParams: ActivityLogApiGetLogEntriesRequest,
options?: AxiosRequestConfig
) => {
const { api } = currentApi;
if (!api) return;
const response = await getActivityLogApi(api).getLogEntries(requestParams, {
signal: options?.signal
});
return response.data;
};
export const useLogEntires = (
requestParams: ActivityLogApiGetLogEntriesRequest
) => {
const currentApi = useApi();
return useQuery({
queryKey: ['LogEntries', requestParams],
queryFn: ({ signal }) =>
fetchGetLogEntries(currentApi, requestParams, { signal })
});
};

31
src/hooks/useUsers.tsx Normal file
View File

@ -0,0 +1,31 @@
import type { AxiosRequestConfig } from 'axios';
import type { UserApiGetUsersRequest } from '@jellyfin/sdk/lib/generated-client';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
import { useQuery } from '@tanstack/react-query';
import { type JellyfinApiContext, useApi } from './useApi';
export const fetchGetUsers = async (
currentApi: JellyfinApiContext,
requestParams?: UserApiGetUsersRequest,
options?: AxiosRequestConfig
) => {
const { api } = currentApi;
if (!api) return;
const response = await getUserApi(api).getUsers(requestParams, {
signal: options?.signal
});
return response.data;
};
export const useUsers = (requestParams?: UserApiGetUsersRequest) => {
const currentApi = useApi();
return useQuery({
queryKey: ['Users'],
queryFn: ({ signal }) =>
fetchGetUsers(currentApi, requestParams, { signal })
});
};