From 7efa10e7e06cb0a99c9288909f49f231cf745d70 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 31 Mar 2022 18:10:34 -0500 Subject: [PATCH] Convert reducers/admin to Typescript --- app/soapbox/reducers/{admin.js => admin.ts} | 77 +++++++++++++++------ app/soapbox/selectors/index.ts | 9 +-- app/soapbox/utils/config_db.ts | 4 +- 3 files changed, 61 insertions(+), 29 deletions(-) rename app/soapbox/reducers/{admin.js => admin.ts} (54%) diff --git a/app/soapbox/reducers/admin.js b/app/soapbox/reducers/admin.ts similarity index 54% rename from app/soapbox/reducers/admin.js rename to app/soapbox/reducers/admin.ts index 6a75e2c5b..90cb10a4f 100644 --- a/app/soapbox/reducers/admin.js +++ b/app/soapbox/reducers/admin.ts @@ -21,33 +21,54 @@ import { ADMIN_USERS_APPROVE_SUCCESS, } from '../actions/admin'; +import type { AnyAction } from 'redux'; +import type { Config } from 'soapbox/utils/config_db'; + const ReducerRecord = ImmutableRecord({ - reports: ImmutableMap(), - openReports: ImmutableOrderedSet(), - users: ImmutableMap(), - latestUsers: ImmutableOrderedSet(), - awaitingApproval: ImmutableOrderedSet(), - configs: ImmutableList(), + reports: ImmutableMap(), + openReports: ImmutableOrderedSet(), + users: ImmutableMap(), + latestUsers: ImmutableOrderedSet(), + awaitingApproval: ImmutableOrderedSet(), + configs: ImmutableList(), needsReboot: false, }); -const FILTER_UNAPPROVED = ['local', 'need_approval']; -const FILTER_LATEST = ['local', 'active']; +type State = ReturnType; -const filtersMatch = (f1, f2) => is(ImmutableSet(f1), ImmutableSet(f2)); -const toIds = items => items.map(item => item.id); +// Umm... based? +// https://itnext.io/typescript-extract-unpack-a-type-from-a-generic-baca7af14e51 +type InnerRecord = R extends ImmutableRecord ? TProps : never; -const mergeSet = (state, key, users) => { +type InnerState = InnerRecord; + +// Lol https://javascript.plainenglish.io/typescript-essentials-conditionally-filter-types-488705bfbf56 +type FilterConditionally = Pick; + +type SetKeys = keyof FilterConditionally>; + +type APIReport = { id: string, state: string, statuses: any[] }; +type APIUser = { id: string, email: string, nickname: string, registration_reason: string }; + +type Filter = 'local' | 'need_approval' | 'active'; + +const FILTER_UNAPPROVED: Filter[] = ['local', 'need_approval']; +const FILTER_LATEST: Filter[] = ['local', 'active']; + +const filtersMatch = (f1: string[], f2: string[]) => is(ImmutableSet(f1), ImmutableSet(f2)); +const toIds = (items: any[]) => items.map(item => item.id); + +const mergeSet = (state: State, key: SetKeys, users: APIUser[]): State => { const newIds = toIds(users); - return state.update(key, ImmutableOrderedSet(), ids => ids.union(newIds)); + return state.update(key, (ids: ImmutableOrderedSet) => ids.union(newIds)); }; -const replaceSet = (state, key, users) => { +const replaceSet = (state: State, key: SetKeys, users: APIUser[]): State => { const newIds = toIds(users); return state.set(key, ImmutableOrderedSet(newIds)); }; -const maybeImportUnapproved = (state, users, filters) => { +const maybeImportUnapproved = (state: State, users: APIUser[], filters: Filter[]): State => { if (filtersMatch(FILTER_UNAPPROVED, filters)) { return mergeSet(state, 'awaitingApproval', users); } else { @@ -55,7 +76,7 @@ const maybeImportUnapproved = (state, users, filters) => { } }; -const maybeImportLatest = (state, users, filters, page) => { +const maybeImportLatest = (state: State, users: APIUser[], filters: Filter[], page: number): State => { if (page === 1 && filtersMatch(FILTER_LATEST, filters)) { return replaceSet(state, 'latestUsers', users); } else { @@ -63,14 +84,14 @@ const maybeImportLatest = (state, users, filters, page) => { } }; -const importUser = (state, user) => ( +const importUser = (state: State, user: APIUser): State => ( state.setIn(['users', user.id], ImmutableMap({ email: user.email, registration_reason: user.registration_reason, })) ); -function importUsers(state, users, filters, page) { +function importUsers(state: State, users: APIUser[], filters: Filter[], page: number): State { return state.withMutations(state => { maybeImportUnapproved(state, users, filters); maybeImportLatest(state, users, filters, page); @@ -81,7 +102,7 @@ function importUsers(state, users, filters, page) { }); } -function deleteUsers(state, accountIds) { +function deleteUsers(state: State, accountIds: string[]): State { return state.withMutations(state => { accountIds.forEach(id => { state.update('awaitingApproval', orderedSet => orderedSet.delete(id)); @@ -90,7 +111,7 @@ function deleteUsers(state, accountIds) { }); } -function approveUsers(state, users) { +function approveUsers(state: State, users: APIUser[]): State { return state.withMutations(state => { users.forEach(user => { state.update('awaitingApproval', orderedSet => orderedSet.delete(user.nickname)); @@ -99,7 +120,7 @@ function approveUsers(state, users) { }); } -function importReports(state, reports) { +function importReports(state: State, reports: APIReport[]): State { return state.withMutations(state => { reports.forEach(report => { report.statuses = report.statuses.map(status => status.id); @@ -111,7 +132,7 @@ function importReports(state, reports) { }); } -function handleReportDiffs(state, reports) { +function handleReportDiffs(state: State, reports: APIReport[]) { // Note: the reports here aren't full report objects // hence the need for a new function. return state.withMutations(state => { @@ -127,11 +148,21 @@ function handleReportDiffs(state, reports) { }); } -export default function admin(state = ReducerRecord(), action) { +const normalizeConfig = (config: any): Config => ImmutableMap(fromJS(config)); + +const normalizeConfigs = (configs: any): ImmutableList => { + return ImmutableList(fromJS(configs)).map(normalizeConfig); +}; + +const importConfigs = (state: State, configs: any): State => { + return state.set('configs', normalizeConfigs(configs)); +}; + +export default function admin(state: State = ReducerRecord(), action: AnyAction): State { switch(action.type) { case ADMIN_CONFIG_FETCH_SUCCESS: case ADMIN_CONFIG_UPDATE_SUCCESS: - return state.set('configs', fromJS(action.configs)); + return importConfigs(state, action.configs); case ADMIN_REPORTS_FETCH_SUCCESS: return importReports(state, action.reports); case ADMIN_REPORTS_PATCH_REQUEST: diff --git a/app/soapbox/selectors/index.ts b/app/soapbox/selectors/index.ts index 2eb21744e..ee5f526c4 100644 --- a/app/soapbox/selectors/index.ts +++ b/app/soapbox/selectors/index.ts @@ -2,6 +2,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, + fromJS, } from 'immutable'; import { createSelector } from 'reselect'; @@ -261,9 +262,9 @@ export const makeGetReport = () => { return createSelector( [ - (state: RootState, id: string) => state.admin.getIn(['reports', id]), - (state: RootState, id: string) => state.admin.getIn(['reports', id, 'statuses']).map( - (statusId: string) => state.statuses.get(statusId)) + (state: RootState, id: string) => state.admin.reports.get(id), + (state: RootState, id: string) => ImmutableList(fromJS(state.admin.reports.getIn([id, 'statuses']))).map( + statusId => state.statuses.get(normalizeId(statusId))) .filter((s: any) => s) .map((s: any) => getStatus(state, s.toJS())), ], @@ -306,7 +307,7 @@ export const makeGetOtherAccounts = () => { const getSimplePolicy = createSelector([ (state: RootState) => state.admin.configs, - (state: RootState) => state.instance.pleroma.getIn(['metadata', 'federation', 'mrf_simple'], ImmutableMap()), + (state: RootState) => state.instance.pleroma.getIn(['metadata', 'federation', 'mrf_simple'], ImmutableMap()) as ImmutableMap, ], (configs, instancePolicy: ImmutableMap) => { return instancePolicy.merge(ConfigDB.toSimplePolicy(configs)); }); diff --git a/app/soapbox/utils/config_db.ts b/app/soapbox/utils/config_db.ts index deb5d048a..48f3118b5 100644 --- a/app/soapbox/utils/config_db.ts +++ b/app/soapbox/utils/config_db.ts @@ -6,8 +6,8 @@ import { } from 'immutable'; import { trimStart } from 'lodash'; -type Config = ImmutableMap; -type Policy = ImmutableMap; +export type Config = ImmutableMap; +export type Policy = ImmutableMap; const find = ( configs: ImmutableList,