From 771c9643dc13440ebd94c8f129922876454fca5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 2 Apr 2024 23:09:41 +0200 Subject: [PATCH] Switch admin log to react-query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- src/actions/admin.ts | 21 --------- src/api/hooks/admin/index.ts | 1 + src/api/hooks/admin/useModerationLog.ts | 42 ++++++++++++++++++ src/features/admin/moderation-log.tsx | 49 +++++++-------------- src/reducers/admin-log.ts | 58 ------------------------- src/reducers/index.ts | 2 - src/schemas/index.ts | 1 + src/schemas/moderation-log-entry.ts | 12 +++++ src/types/entities.ts | 3 -- 9 files changed, 71 insertions(+), 118 deletions(-) create mode 100644 src/api/hooks/admin/useModerationLog.ts delete mode 100644 src/reducers/admin-log.ts create mode 100644 src/schemas/moderation-log-entry.ts diff --git a/src/actions/admin.ts b/src/actions/admin.ts index 433cd1e00..c5eb1150a 100644 --- a/src/actions/admin.ts +++ b/src/actions/admin.ts @@ -55,10 +55,6 @@ const ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST = 'ADMIN_STATUS_TOGGLE_SENSITIVITY const ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS'; const ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL'; -const ADMIN_LOG_FETCH_REQUEST = 'ADMIN_LOG_FETCH_REQUEST'; -const ADMIN_LOG_FETCH_SUCCESS = 'ADMIN_LOG_FETCH_SUCCESS'; -const ADMIN_LOG_FETCH_FAIL = 'ADMIN_LOG_FETCH_FAIL'; - const ADMIN_USERS_TAG_REQUEST = 'ADMIN_USERS_TAG_REQUEST'; const ADMIN_USERS_TAG_SUCCESS = 'ADMIN_USERS_TAG_SUCCESS'; const ADMIN_USERS_TAG_FAIL = 'ADMIN_USERS_TAG_FAIL'; @@ -423,19 +419,6 @@ const toggleStatusSensitivity = (id: string, sensitive: boolean) => }); }; -const fetchModerationLog = (params?: Record) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: ADMIN_LOG_FETCH_REQUEST }); - return api(getState) - .get('/api/v1/pleroma/admin/moderation_log', { params }) - .then(({ data }) => { - dispatch({ type: ADMIN_LOG_FETCH_SUCCESS, items: data.items, total: data.total }); - return data; - }).catch(error => { - dispatch({ type: ADMIN_LOG_FETCH_FAIL, error }); - }); - }; - const tagUsers = (accountIds: string[], tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); @@ -707,9 +690,6 @@ export { ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL, - ADMIN_LOG_FETCH_REQUEST, - ADMIN_LOG_FETCH_SUCCESS, - ADMIN_LOG_FETCH_FAIL, ADMIN_USERS_TAG_REQUEST, ADMIN_USERS_TAG_SUCCESS, ADMIN_USERS_TAG_FAIL, @@ -757,7 +737,6 @@ export { approveUsers, deleteStatus, toggleStatusSensitivity, - fetchModerationLog, tagUsers, untagUsers, setTags, diff --git a/src/api/hooks/admin/index.ts b/src/api/hooks/admin/index.ts index 49e22cf86..3a4c8050b 100644 --- a/src/api/hooks/admin/index.ts +++ b/src/api/hooks/admin/index.ts @@ -3,6 +3,7 @@ export { useCreateRelay } from './useCreateRelay'; export { useDeleteDomain } from './useDeleteDomain'; export { useDeleteRelay } from './useDeleteRelay'; export { useDomains } from './useDomains'; +export { useModerationLog } from './useModerationLog'; export { useRelays } from './useRelays'; export { useSuggest } from './useSuggest'; export { useUpdateDomain } from './useUpdateDomain'; diff --git a/src/api/hooks/admin/useModerationLog.ts b/src/api/hooks/admin/useModerationLog.ts new file mode 100644 index 000000000..8284c9b52 --- /dev/null +++ b/src/api/hooks/admin/useModerationLog.ts @@ -0,0 +1,42 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { useApi } from 'soapbox/hooks'; +import { moderationLogEntrySchema, type ModerationLogEntry } from 'soapbox/schemas'; + +interface ModerationLogResult { + items: ModerationLogEntry[]; + total: number; +} + +const flattenPages = (pages?: ModerationLogResult[]): ModerationLogEntry[] => (pages || []).map(({ items }) => items).flat(); + +const useModerationLog = () => { + const api = useApi(); + + const getModerationLog = async (page: number): Promise => { + const { data } = await api.get('/api/v1/pleroma/admin/moderation_log', { params: { page } }); + + const normalizedData = data.items.map((domain) => moderationLogEntrySchema.parse(domain)); + + return { + items: normalizedData, + total: data.total, + }; + }; + + const queryInfo = useInfiniteQuery({ + queryKey: ['moderation_log'], + queryFn: ({ pageParam }) => getModerationLog(pageParam), + initialPageParam: 1, + getNextPageParam: (page, allPages) => flattenPages(allPages)!.length >= page.total ? undefined : allPages.length + 1, + }); + + const data = flattenPages(queryInfo.data?.pages); + + return { + ...queryInfo, + data, + }; +}; + +export { useModerationLog }; diff --git a/src/features/admin/moderation-log.tsx b/src/features/admin/moderation-log.tsx index 004d87997..57a80f83f 100644 --- a/src/features/admin/moderation-log.tsx +++ b/src/features/admin/moderation-log.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { defineMessages, FormattedDate, useIntl } from 'react-intl'; -import { fetchModerationLog } from 'soapbox/actions/admin'; +import { useModerationLog } from 'soapbox/api/hooks/admin'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import { AdminLog } from 'soapbox/types/entities'; + +import type { ModerationLogEntry } from 'soapbox/schemas'; const messages = defineMessages({ heading: { id: 'column.admin.moderation_log', defaultMessage: 'Moderation Log' }, @@ -14,37 +14,18 @@ const messages = defineMessages({ const ModerationLog = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); - - const items = useAppSelector((state) => { - return state.admin_log.index.map((i) => state.admin_log.items.get(String(i))); - }); - - const hasMore = useAppSelector((state) => state.admin_log.total - state.admin_log.index.count() > 0); - const [isLoading, setIsLoading] = useState(true); - const [lastPage, setLastPage] = useState(0); + const { + data, + hasNextPage, + isLoading, + fetchNextPage, + } = useModerationLog(); - const showLoading = isLoading && items.count() === 0; - - useEffect(() => { - dispatch(fetchModerationLog()) - .then(() => { - setIsLoading(false); - setLastPage(1); - }) - .catch(() => { }); - }, []); + const showLoading = isLoading && data.length === 0; const handleLoadMore = () => { - const page = lastPage + 1; - - setIsLoading(true); - dispatch(fetchModerationLog({ page })) - .then(() => { - setIsLoading(false); - setLastPage(page); - }).catch(() => { }); + fetchNextPage(); }; return ( @@ -54,11 +35,11 @@ const ModerationLog = () => { showLoading={showLoading} scrollKey='moderation-log' emptyMessage={intl.formatMessage(messages.emptyMessage)} - hasMore={hasMore} + hasMore={hasNextPage} onLoadMore={handleLoadMore} listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800' > - {items.map(item => item && ( + {data.map(item => item && ( ))} @@ -67,7 +48,7 @@ const ModerationLog = () => { }; interface ILogItem { - log: AdminLog; + log: ModerationLogEntry; } const LogItem: React.FC = ({ log }) => { diff --git a/src/reducers/admin-log.ts b/src/reducers/admin-log.ts deleted file mode 100644 index 1ffe4438f..000000000 --- a/src/reducers/admin-log.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - Map as ImmutableMap, - Record as ImmutableRecord, - OrderedSet as ImmutableOrderedSet, -} from 'immutable'; - -import { ADMIN_LOG_FETCH_SUCCESS } from 'soapbox/actions/admin'; - -import type { AnyAction } from 'redux'; -import type { APIEntity } from 'soapbox/types/entities'; - -export const LogEntryRecord = ImmutableRecord({ - data: ImmutableMap(), - id: 0, - message: '', - time: 0, -}); - -const ReducerRecord = ImmutableRecord({ - items: ImmutableMap(), - index: ImmutableOrderedSet(), - total: 0, -}); - -type LogEntry = ReturnType; -type State = ReturnType; -type APIEntities = Array; - -const parseItems = (items: APIEntities) => { - const ids: Array = []; - const map: Record = {}; - - items.forEach(item => { - ids.push(item.id); - map[item.id] = LogEntryRecord(item); - }); - - return { ids: ids, map: map }; -}; - -const importItems = (state: State, items: APIEntities, total: number) => { - const { ids, map } = parseItems(items); - - return state.withMutations(state => { - state.update('index', v => v.union(ids)); - state.update('items', v => v.merge(map)); - state.set('total', total); - }); -}; - -export default function admin_log(state = ReducerRecord(), action: AnyAction) { - switch (action.type) { - case ADMIN_LOG_FETCH_SUCCESS: - return importItems(state, action.items, action.total); - default: - return state; - } -} diff --git a/src/reducers/index.ts b/src/reducers/index.ts index a30369561..c8ea0e81a 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -8,7 +8,6 @@ import entities from 'soapbox/entity-store/reducer'; import accounts_meta from './accounts-meta'; import admin from './admin'; import admin_announcements from './admin-announcements'; -import admin_log from './admin-log'; import admin_user_index from './admin-user-index'; import aliases from './aliases'; import announcements from './announcements'; @@ -69,7 +68,6 @@ const reducers = { accounts_meta, admin, admin_announcements, - admin_log, admin_user_index, aliases, announcements, diff --git a/src/schemas/index.ts b/src/schemas/index.ts index a541ac092..dcc4e74e2 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -12,6 +12,7 @@ export { groupRelationshipSchema, type GroupRelationship } from './group-relatio export { groupTagSchema, type GroupTag } from './group-tag'; export { instanceSchema, type Instance } from './instance'; export { mentionSchema, type Mention } from './mention'; +export { moderationLogEntrySchema, type ModerationLogEntry } from './moderation-log-entry'; export { notificationSchema, type Notification } from './notification'; export { patronUserSchema, type PatronUser } from './patron'; export { pollSchema, type Poll, type PollOption } from './poll'; diff --git a/src/schemas/moderation-log-entry.ts b/src/schemas/moderation-log-entry.ts new file mode 100644 index 000000000..d3e553381 --- /dev/null +++ b/src/schemas/moderation-log-entry.ts @@ -0,0 +1,12 @@ +import z from 'zod'; + +const moderationLogEntrySchema = z.object({ + id: z.coerce.string(), + data: z.record(z.string(), z.any()).catch({}), + time: z.number().catch(0), + message: z.string().catch(''), +}); + +type ModerationLogEntry = z.infer + +export { moderationLogEntrySchema, type ModerationLogEntry }; diff --git a/src/types/entities.ts b/src/types/entities.ts index 98d36a07c..4f784d7da 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -20,14 +20,12 @@ import { StatusRecord, TagRecord, } from 'soapbox/normalizers'; -import { LogEntryRecord } from 'soapbox/reducers/admin-log'; import { Account as SchemaAccount } from 'soapbox/schemas'; import type { Record as ImmutableRecord } from 'immutable'; import type { LegacyMap } from 'soapbox/utils/legacy'; type AdminAccount = ReturnType; -type AdminLog = ReturnType; type AdminReport = ReturnType; type Announcement = ReturnType; type AnnouncementReaction = ReturnType; @@ -62,7 +60,6 @@ type EmbeddedEntity = null | string | ReturnType