From f836a8f6c7c0c93deca6a91f952fa57b6f5b571c Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 19 Dec 2023 15:21:30 +0100 Subject: [PATCH 01/11] Modernize redux: userSlice Switching to redux toolkit for getting users for the user table. --- app/src/actions/userActions.ts | 35 ----------- app/src/components/shared/MainNav.tsx | 7 +-- app/src/components/users/Acls.tsx | 8 +-- app/src/components/users/Groups.tsx | 7 +-- app/src/components/users/Users.tsx | 82 ++++++------------------ app/src/reducers/userReducers.ts | 71 --------------------- app/src/slices/userSlice.ts | 90 +++++++++++++++++++++++++++ app/src/store.ts | 2 +- app/src/thunks/tableThunks.ts | 7 +-- app/src/thunks/userThunks.ts | 28 +-------- 10 files changed, 123 insertions(+), 214 deletions(-) delete mode 100644 app/src/actions/userActions.ts delete mode 100644 app/src/reducers/userReducers.ts create mode 100644 app/src/slices/userSlice.ts diff --git a/app/src/actions/userActions.ts b/app/src/actions/userActions.ts deleted file mode 100644 index 6209a0e939..0000000000 --- a/app/src/actions/userActions.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * This file contains all redux actions that can be executed on users - */ - -// Constants of action types for fetching users from server -export const LOAD_USERS_IN_PROGRESS = "LOAD_USERS_IN_PROGRESS"; -export const LOAD_USERS_SUCCESS = "LOAD_USERS_SUCCESS"; -export const LOAD_USERS_FAILURE = "LOAD_USERS_FAILURE"; - -// Constants of action types affecting UI -export const SET_USER_COLUMNS = "SET_USER_COLUMNS"; - -// Actions affecting fetching users from server - -export const loadUsersInProgress = () => ({ - type: LOAD_USERS_IN_PROGRESS, -}); - -// @ts-expect-error TS(7006): Parameter 'users' implicitly has an 'any' type. -export const loadUsersSuccess = (users) => ({ - type: LOAD_USERS_SUCCESS, - payload: { users }, -}); - -export const loadUsersFailure = () => ({ - type: LOAD_USERS_FAILURE, -}); - -// Action affecting UI - -// @ts-expect-error TS(7006): Parameter 'updatedColumns' implicitly has an 'any'... Remove this comment to see the full error message -export const setUserColumns = (updatedColumns) => ({ - type: SET_USER_COLUMNS, - payload: { updatedColumns }, -}); diff --git a/app/src/components/shared/MainNav.tsx b/app/src/components/shared/MainNav.tsx index e75a72381b..f22edcf610 100644 --- a/app/src/components/shared/MainNav.tsx +++ b/app/src/components/shared/MainNav.tsx @@ -17,7 +17,6 @@ import { import { fetchEvents } from "../../thunks/eventThunks"; import { fetchRecordings } from "../../thunks/recordingThunks"; import { fetchJobs } from "../../thunks/jobThunks"; -import { fetchUsers } from "../../thunks/userThunks"; import { fetchThemes } from "../../thunks/themeThunks"; import { fetchFilters, fetchStats } from "../../thunks/tableFilterThunks"; import { setOffset } from "../../actions/tableActions"; @@ -31,6 +30,7 @@ import { GlobalHotKeys } from "react-hotkeys"; import { availableHotkeys } from "../../configs/hotkeysConfig"; import { fetchAcls } from "../../slices/aclSlice"; import { useAppDispatch } from "../../store"; +import { fetchUsers } from "../../slices/userSlice"; /** * This component renders the main navigation that opens when the burger button is clicked @@ -66,8 +66,6 @@ const MainNav = ({ loadingServices, // @ts-expect-error TS(7031): Binding element 'loadingServicesIntoTable' implici... Remove this comment to see the full error message loadingServicesIntoTable, -// @ts-expect-error TS(7031): Binding element 'loadingUsers' implicitly has an '... Remove this comment to see the full error message - loadingUsers, // @ts-expect-error TS(7031): Binding element 'loadingUsersIntoTable' implicitly... Remove this comment to see the full error message loadingUsersIntoTable, // @ts-expect-error TS(7031): Binding element 'loadingGroups' implicitly has an ... Remove this comment to see the full error message @@ -179,7 +177,7 @@ const MainNav = ({ resetOffset(); // Fetching users from server - loadingUsers(); + dispatch(fetchUsers()); // Load users into table loadingUsersIntoTable(); @@ -357,7 +355,6 @@ const mapDispatchToProps = (dispatch) => ({ loadingServersIntoTable: () => dispatch(loadServersIntoTable()), loadingServices: () => dispatch(fetchServices()), loadingServicesIntoTable: () => dispatch(loadServicesIntoTable()), - loadingUsers: () => dispatch(fetchUsers()), loadingUsersIntoTable: () => dispatch(loadUsersIntoTable()), loadingGroups: () => dispatch(fetchGroups()), loadingGroupsIntoTable: () => dispatch(loadGroupsIntoTable()), diff --git a/app/src/components/users/Acls.tsx b/app/src/components/users/Acls.tsx index f3819ef65a..2c1a9dea6a 100644 --- a/app/src/components/users/Acls.tsx +++ b/app/src/components/users/Acls.tsx @@ -9,7 +9,6 @@ import Notifications from "../shared/Notifications"; import NewResourceModal from "../shared/NewResourceModal"; import { aclsTemplateMap } from "../../configs/tableConfigs/aclsTableConfig"; import { fetchFilters } from "../../thunks/tableFilterThunks"; -import { fetchUsers } from "../../thunks/userThunks"; import { loadAclsIntoTable, loadGroupsIntoTable, @@ -27,6 +26,7 @@ import { getUserInformation } from "../../selectors/userInfoSelectors"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; import { useAppDispatch, useAppSelector } from "../../store"; import { fetchAcls } from "../../slices/aclSlice"; +import { fetchUsers } from "../../slices/userSlice"; /** * This component renders the table view of acls @@ -36,10 +36,10 @@ const Acls: React.FC = () => { const [displayNavigation, setNavigation] = useState(false); const [displayNewAclModal, setNewAclModal] = useState(false); - const dispatch = useAppDispatch(); - const acls = useAppSelector(state => getTotalAcls(state)); + const dispatch = useAppDispatch(); + const acls = useAppSelector(state => getTotalAcls(state)); const user = useAppSelector(state => getUserInformation(state)); - const currentFilterType = useAppSelector(state => getCurrentFilterResource(state)); + const currentFilterType = useAppSelector(state => getCurrentFilterResource(state)); const loadAcls = async () => { // Fetching acls from server diff --git a/app/src/components/users/Groups.tsx b/app/src/components/users/Groups.tsx index 0a6e18d8e1..cf16ac846a 100644 --- a/app/src/components/users/Groups.tsx +++ b/app/src/components/users/Groups.tsx @@ -11,7 +11,6 @@ import NewResourceModal from "../shared/NewResourceModal"; import { getTotalGroups } from "../../selectors/groupSelectors"; import { groupsTemplateMap } from "../../configs/tableConfigs/groupsTableConfig"; import { fetchFilters } from "../../thunks/tableFilterThunks"; -import { fetchUsers } from "../../thunks/userThunks"; import { loadAclsIntoTable, loadGroupsIntoTable, @@ -28,6 +27,7 @@ import { hasAccess } from "../../utils/utils"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; import { fetchAcls } from "../../slices/aclSlice"; import { useAppDispatch } from "../../store"; +import { fetchUsers } from "../../slices/userSlice"; /** * This component renders the table view of groups @@ -41,8 +41,6 @@ const Groups = ({ groups, // @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message loadingFilters, -// @ts-expect-error TS(7031): Binding element 'loadingUsers' implicitly has an '... Remove this comment to see the full error message - loadingUsers, // @ts-expect-error TS(7031): Binding element 'loadingUsersIntoTable' implicitly... Remove this comment to see the full error message loadingUsersIntoTable, // @ts-expect-error TS(7031): Binding element 'loadingAclsIntoTable' implicitly ... Remove this comment to see the full error message @@ -74,7 +72,7 @@ const Groups = ({ resetOffset(); // Fetching users from server - loadingUsers(); + dispatch(fetchUsers()); // Load users into table loadingUsersIntoTable(); @@ -215,7 +213,6 @@ const mapDispatchToProps = (dispatch) => ({ loadingFilters: (resource) => dispatch(fetchFilters(resource)), loadingGroups: () => dispatch(fetchGroups()), loadingGroupsIntoTable: () => dispatch(loadGroupsIntoTable()), - loadingUsers: () => dispatch(fetchUsers()), loadingUsersIntoTable: () => dispatch(loadUsersIntoTable()), loadingAclsIntoTable: () => dispatch(loadAclsIntoTable()), resetTextFilter: () => dispatch(editTextFilter("")), diff --git a/app/src/components/users/Users.tsx b/app/src/components/users/Users.tsx index a5d0d0a53d..c582e04b2a 100644 --- a/app/src/components/users/Users.tsx +++ b/app/src/components/users/Users.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import cn from "classnames"; -import { connect } from "react-redux"; import MainNav from "../shared/MainNav"; import TableFilters from "../shared/TableFilters"; import Table from "../shared/Table"; @@ -10,7 +9,6 @@ import Notifications from "../shared/Notifications"; import NewResourceModal from "../shared/NewResourceModal"; import { usersTemplateMap } from "../../configs/tableConfigs/usersTableConfig"; import { getTotalUsers } from "../../selectors/userSelectors"; -import { fetchUsers } from "../../thunks/userThunks"; import { loadAclsIntoTable, loadGroupsIntoTable, @@ -27,76 +25,58 @@ import { getUserInformation } from "../../selectors/userInfoSelectors"; import { hasAccess } from "../../utils/utils"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; import { fetchAcls } from "../../slices/aclSlice"; -import { useAppDispatch } from "../../store"; +import { useAppDispatch, useAppSelector } from "../../store"; +import { fetchUsers } from "../../slices/userSlice"; /** * This component renders the table view of users */ -const Users = ({ -// @ts-expect-error TS(7031): Binding element 'loadingUsers' implicitly has an '... Remove this comment to see the full error message - loadingUsers, -// @ts-expect-error TS(7031): Binding element 'loadingUsersIntoTable' implicitly... Remove this comment to see the full error message - loadingUsersIntoTable, -// @ts-expect-error TS(7031): Binding element 'users' implicitly has an 'any' ty... Remove this comment to see the full error message - users, -// @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message - loadingFilters, -// @ts-expect-error TS(7031): Binding element 'loadingGroups' implicitly has an ... Remove this comment to see the full error message - loadingGroups, -// @ts-expect-error TS(7031): Binding element 'loadingGroupsIntoTable' implicitl... Remove this comment to see the full error message - loadingGroupsIntoTable, -// @ts-expect-error TS(7031): Binding element 'loadingAclsIntoTable' implicitly ... Remove this comment to see the full error message - loadingAclsIntoTable, -// @ts-expect-error TS(7031): Binding element 'resetTextFilter' implicitly has a... Remove this comment to see the full error message - resetTextFilter, -// @ts-expect-error TS(7031): Binding element 'resetOffset' implicitly has an 'a... Remove this comment to see the full error message - resetOffset, -// @ts-expect-error TS(7031): Binding element 'user' implicitly has an 'any' typ... Remove this comment to see the full error message - user, -// @ts-expect-error TS(7031): Binding element 'currentFilterType' implicitly has... Remove this comment to see the full error message - currentFilterType, -}) => { +const Users: React.FC = () => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); + const dispatch = useAppDispatch(); const [displayNavigation, setNavigation] = useState(false); const [displayNewUserModal, setNewUserModal] = useState(false); + const users = useAppSelector(state => getTotalUsers(state)); + const user = useAppSelector(state => getUserInformation(state)); + const currentFilterType = useAppSelector(state => getCurrentFilterResource(state)); + const loadUsers = async () => { // Fetching users from server - await loadingUsers(); + await dispatch(fetchUsers()); // Load users into table - loadingUsersIntoTable(); + dispatch(loadUsersIntoTable()); }; const loadGroups = () => { // Reset the current page to first page - resetOffset(); + dispatch(setOffset(0)); // Fetching groups from server - loadingGroups(); + dispatch(fetchGroups()); // Load groups into table - loadingGroupsIntoTable(); + dispatch(loadGroupsIntoTable()); }; const loadAcls = () => { // Reset the current page to first page - resetOffset(); + dispatch(setOffset(0)); // Fetching acls from server dispatch(fetchAcls()); // Load acls into table - loadingAclsIntoTable(); + dispatch(loadAclsIntoTable()); }; useEffect(() => { if ("users" !== currentFilterType) { - loadingFilters("users"); + dispatch(fetchFilters("users")); } - resetTextFilter(); + dispatch(editTextFilter("")); // Load users on mount loadUsers().then((r) => console.info(r)); @@ -185,8 +165,8 @@ const Users = ({
{/* Include filters component */}

{t("USERS.USERS.TABLE.CAPTION")}

@@ -200,26 +180,4 @@ const Users = ({ ); }; -// Getting state data out of redux store -// @ts-expect-error TS(7006): Parameter 'state' implicitly has an 'any' type. -const mapStateToProps = (state) => ({ - users: getTotalUsers(state), - user: getUserInformation(state), - currentFilterType: getCurrentFilterResource(state), -}); - -// Mapping actions to dispatch -// @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. -const mapDispatchToProps = (dispatch) => ({ -// @ts-expect-error TS(7006): Parameter 'resource' implicitly has an 'any' type. - loadingFilters: (resource) => dispatch(fetchFilters(resource)), - loadingUsers: () => dispatch(fetchUsers()), - loadingUsersIntoTable: () => dispatch(loadUsersIntoTable()), - loadingGroups: () => dispatch(fetchGroups()), - loadingGroupsIntoTable: () => dispatch(loadGroupsIntoTable()), - loadingAclsIntoTable: () => dispatch(loadAclsIntoTable()), - resetTextFilter: () => dispatch(editTextFilter("")), - resetOffset: () => dispatch(setOffset(0)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Users); +export default Users; diff --git a/app/src/reducers/userReducers.ts b/app/src/reducers/userReducers.ts deleted file mode 100644 index be71881bc7..0000000000 --- a/app/src/reducers/userReducers.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { usersTableConfig } from "../configs/tableConfigs/usersTableConfig"; -import { - LOAD_USERS_FAILURE, - LOAD_USERS_IN_PROGRESS, - LOAD_USERS_SUCCESS, - SET_USER_COLUMNS, -} from "../actions/userActions"; - -/** - * This file contains redux reducer for actions affecting the state of users - */ - -// Fill columns initially with columns defined in usersTableConfig -const initialColumns = usersTableConfig.columns.map((column) => ({ - ...column, - deactivated: false, -})); - -// Initial state of users in redux store -const initialState = { - isLoading: false, - results: [], - columns: initialColumns, - total: 0, - count: 0, - offset: 0, - limit: 0, -}; - -// Reducer for users -// @ts-expect-error TS(7006): Parameter 'action' implicitly has an 'any' type. -const users = (state = initialState, action) => { - const { type, payload } = action; - switch (type) { - case LOAD_USERS_IN_PROGRESS: { - return { - ...state, - isLoading: true, - }; - } - case LOAD_USERS_SUCCESS: { - const { users } = payload; - return { - ...state, - isLoading: false, - total: users.total, - count: users.count, - limit: users.limit, - offset: users.offset, - results: users.results, - }; - } - case LOAD_USERS_FAILURE: { - return { - ...state, - isLoading: false, - }; - } - case SET_USER_COLUMNS: { - const { updatedColumns } = payload; - return { - ...state, - columns: updatedColumns, - }; - } - default: - return state; - } -}; - -export default users; diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts new file mode 100644 index 0000000000..da695bd8ee --- /dev/null +++ b/app/src/slices/userSlice.ts @@ -0,0 +1,90 @@ +import { PayloadAction, SerializedError, createAsyncThunk, createSlice } from '@reduxjs/toolkit' +import { usersTableConfig } from "../configs/tableConfigs/usersTableConfig"; +import axios from 'axios'; +import { getURLParams } from "../utils/resourceUtils"; + +/** + * This file contains redux reducer for actions affecting the state of users + */ +type UsersState = { + status: 'uninitialized' | 'loading' | 'succeeded' | 'failed', + error: SerializedError | null, + results: any[], // TODO: proper typing + columns: any, // TODO: proper typing, derive from `initialColumns` + total: number, + count: number, + offset: number, + limit: number, +}; + +// Fill columns initially with columns defined in usersTableConfig +const initialColumns = usersTableConfig.columns.map((column) => ({ + ...column, + deactivated: false, +})); + +// Initial state of users in redux store +const initialState: UsersState = { + status: 'uninitialized', + error: null, + results: [], + columns: initialColumns, + total: 0, + count: 0, + offset: 0, + limit: 0, +}; + +export const fetchUsers = createAsyncThunk('users/fetchUsers', async (_, { getState }) => { + const state = getState(); + let params = getURLParams(state); + // Just make the async request here, and return the response. + // This will automatically dispatch a `pending` action first, + // and then `fulfilled` or `rejected` actions based on the promise. + const res = await axios.get("/admin-ng/users/users.json", { params: params }); + return res.data; +}); + +const usersSlice = createSlice({ + name: 'users', + initialState, + reducers: { + setUserColumns(state, action: PayloadAction<{ + updatedColumns: UsersState["columns"], + }>) { + state.columns = action.payload.updatedColumns; + }, + }, + // These are used for thunks + extraReducers: builder => { + builder + .addCase(fetchUsers.pending, (state) => { + state.status = 'loading'; + }) + .addCase(fetchUsers.fulfilled, (state, action: PayloadAction<{ + total: UsersState["total"], + count: UsersState["count"], + limit: UsersState["limit"], + offset: UsersState["offset"], + results: UsersState["results"], + }>) => { + state.status = 'succeeded'; + const users = action.payload; + state.total = users.total; + state.count = users.count; + state.limit = users.limit; + state.offset = users.offset; + state.results = users.results; + }) + .addCase(fetchUsers.rejected, (state, action) => { + state.status = 'failed'; + state.results = []; + state.error = action.error; + }); + } +}); + +export const { setUserColumns } = usersSlice.actions; + +// Export the slice reducer as the default export +export default usersSlice.reducer; diff --git a/app/src/store.ts b/app/src/store.ts index 790807712c..badcc104a6 100644 --- a/app/src/store.ts +++ b/app/src/store.ts @@ -12,7 +12,7 @@ import recordings from "./reducers/recordingReducer"; import jobs from "./reducers/jobReducer"; import servers from "./reducers/serverReducer"; import services from "./reducers/serviceReducer"; -import users from "./reducers/userReducers"; +import users from "./slices/userSlice"; import groups from "./reducers/groupReducers"; import acls from "./slices/aclSlice"; import themes from "./reducers/themeReducers"; diff --git a/app/src/thunks/tableThunks.ts b/app/src/thunks/tableThunks.ts index 3220b05dde..07e3acb00e 100644 --- a/app/src/thunks/tableThunks.ts +++ b/app/src/thunks/tableThunks.ts @@ -38,18 +38,17 @@ import { fetchRecordings } from "./recordingThunks"; import { fetchJobs } from "./jobThunks"; import { fetchServers } from "./serverThunks"; import { fetchServices } from "./serviceThunks"; -import { fetchUsers } from "./userThunks"; import { fetchGroups } from "./groupThunks"; import { fetchThemes } from "./themeThunks"; import { setRecordingsColumns } from "../actions/recordingActions"; import { setJobColumns } from "../actions/jobActions"; import { setServerColumns } from "../actions/serverActions"; -import { setUserColumns } from "../actions/userActions"; import { setGroupColumns } from "../actions/groupActions"; import { fetchAcls, setAclColumns } from "../slices/aclSlice"; import { setThemeColumns } from "../actions/themeActions"; import { setServicesColumns } from "../actions/serviceActions"; import { useAppDispatch } from "../store"; +import { fetchUsers, setUserColumns } from "../slices/userSlice"; /** * This file contains methods/thunks used to manage the table in the main view and its state changes @@ -452,7 +451,7 @@ export const goToPage = (pageNumber) => async (dispatch, getState) => { break; } case "users": { - await dispatch(fetchUsers()); + await appDispatch(fetchUsers()); dispatch(loadUsersIntoTable()); break; } @@ -524,7 +523,7 @@ export const updatePages = () => async (dispatch, getState) => { break; } case "users": { - await dispatch(fetchUsers()); + await appDispatch(fetchUsers()); dispatch(loadUsersIntoTable()); break; } diff --git a/app/src/thunks/userThunks.ts b/app/src/thunks/userThunks.ts index 77ee5e5402..897da9be0f 100644 --- a/app/src/thunks/userThunks.ts +++ b/app/src/thunks/userThunks.ts @@ -1,34 +1,8 @@ import axios from "axios"; -import { - loadUsersFailure, - loadUsersInProgress, - loadUsersSuccess, -} from "../actions/userActions"; -import { buildUserBody, getURLParams } from "../utils/resourceUtils"; +import { buildUserBody } from "../utils/resourceUtils"; import { transformToIdValueArray } from "../utils/utils"; import { addNotification } from "./notificationThunks"; -// fetch users from server -// @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. -export const fetchUsers = () => async (dispatch, getState) => { - try { - dispatch(loadUsersInProgress()); - - const state = getState(); - let params = getURLParams(state); - - // /users.json?limit=0&offset=0&filter={filter}&sort={sort} - let data = await axios.get("/admin-ng/users/users.json", { - params: params, - }); - - const users = await data.data; - dispatch(loadUsersSuccess(users)); - } catch (e) { - dispatch(loadUsersFailure()); - } -}; - // get users and their user names export const fetchUsersAndUsernames = async () => { let data = await axios.get( From 55461f245e760f37f88608bf1ec7a798e2c35568 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 19 Dec 2023 16:19:23 +0100 Subject: [PATCH 02/11] Fix addNotification([...]) typing Turns out even more of the parameters for the addNotification function in the notificationThunks are perfectly optional. --- app/src/thunks/aclDetailsThunks.ts | 2 -- app/src/thunks/aclThunks.ts | 4 ---- app/src/thunks/eventDetailsThunks.ts | 2 -- app/src/thunks/eventThunks.ts | 17 ----------------- app/src/thunks/groupDetailsThunks.ts | 3 --- app/src/thunks/groupThunks.ts | 5 ----- app/src/thunks/notificationThunks.ts | 2 +- app/src/thunks/recordingThunks.ts | 3 --- app/src/thunks/seriesThunks.ts | 6 ------ app/src/thunks/taskThunks.ts | 2 -- app/src/thunks/themeDetailsThunks.ts | 2 -- app/src/thunks/themeThunks.ts | 4 ---- app/src/thunks/userDetailsThunks.ts | 2 -- app/src/thunks/userInfoThunks.ts | 1 - app/src/thunks/userThunks.ts | 4 ---- 15 files changed, 1 insertion(+), 58 deletions(-) diff --git a/app/src/thunks/aclDetailsThunks.ts b/app/src/thunks/aclDetailsThunks.ts index 75025f78b6..f5642605a2 100644 --- a/app/src/thunks/aclDetailsThunks.ts +++ b/app/src/thunks/aclDetailsThunks.ts @@ -121,12 +121,10 @@ export const updateAclDetails = (values, aclId) => async (dispatch) => { .put(`/admin-ng/acl/${aclId}`, data) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "ACL_UPDATED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "ACL_NOT_SAVED")); }); }; diff --git a/app/src/thunks/aclThunks.ts b/app/src/thunks/aclThunks.ts index 4a505a0bba..c8126be65e 100644 --- a/app/src/thunks/aclThunks.ts +++ b/app/src/thunks/aclThunks.ts @@ -69,12 +69,10 @@ export const postNewAcl = (values) => async (dispatch) => { }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "ACL_ADDED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "ACL_NOT_SAVED")); }); }; @@ -86,13 +84,11 @@ export const deleteAcl = (id) => async (dispatch) => { .then((res) => { console.info(res); //add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "ACL_DELETED")); }) .catch((res) => { console.error(res); // add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "ACL_NOT_DELETED")); }); }; diff --git a/app/src/thunks/eventDetailsThunks.ts b/app/src/thunks/eventDetailsThunks.ts index ac3239cb39..e1de916d59 100644 --- a/app/src/thunks/eventDetailsThunks.ts +++ b/app/src/thunks/eventDetailsThunks.ts @@ -578,14 +578,12 @@ export const updateAssets = (values, eventId) => async (dispatch, getState) => { .then((response) => { console.info(response); dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 4. addNotification("success", "EVENTS_UPDATED", null, NOTIFICATION_CONTEXT) ); }) .catch((response) => { console.error(response); dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 4. addNotification( "error", "EVENTS_NOT_UPDATED", diff --git a/app/src/thunks/eventThunks.ts b/app/src/thunks/eventThunks.ts index e9623e2e72..23b853d392 100644 --- a/app/src/thunks/eventThunks.ts +++ b/app/src/thunks/eventThunks.ts @@ -184,7 +184,6 @@ export const updateBulkMetadata = (metadataFields, values) => async ( .then((res) => { console.info(res); dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification("success", "BULK_METADATA_UPDATE.ALL_EVENTS_UPDATED") ); }) @@ -196,19 +195,16 @@ export const updateBulkMetadata = (metadataFields, values) => async ( // if this error data is undefined then an unexpected error occurred if (!err.data) { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification("error", "BULK_METADATA_UPDATE.UNEXPECTED_ERROR") ); } else { if (err.data.updated && err.data.updated.length === 0) { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification("error", "BULK_METADATA_UPDATE.NO_EVENTS_UPDATED") ); } if (err.data.updateFailures && err.data.updateFailures.length > 0) { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification( "warning", "BULK_METADATA_UPDATE.SOME_EVENTS_NOT_UPDATED" @@ -217,7 +213,6 @@ export const updateBulkMetadata = (metadataFields, values) => async ( } if (err.data.notFound && err.data.notFound.length > 0) { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification( "warning", "BULK_ACTIONS.EDIT_EVENTS_METADATA.REQUEST_ERRORS.NOT_FOUND" @@ -227,7 +222,6 @@ export const updateBulkMetadata = (metadataFields, values) => async ( } } else { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification("error", "BULK_METADATA_UPDATE.UNEXPECTED_ERROR") ); } @@ -485,12 +479,10 @@ export const postNewEvent = (values, metadataInfo, extendedMetadata) => async ( .post("/admin-ng/event/new", formData, config) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "EVENTS_CREATED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "EVENTS_NOT_CREATED")); }); }; @@ -504,20 +496,16 @@ export const deleteEvent = (id) => async (dispatch) => { .then((res) => { // add success notification depending on status code if (res.status === 200) { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "EVENT_DELETED")); } else { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "EVENT_WILL_BE_DELETED")); } }) .catch((res) => { // add error notification depending on status code if (res.status === 401) { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "EVENTS_NOT_DELETED_NOT_AUTHORIZED")); } else { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "EVENTS_NOT_DELETED")); } }); @@ -539,13 +527,11 @@ export const deleteMultipleEvent = (events) => async (dispatch) => { .then((res) => { console.info(res); //add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "EVENTS_DELETED")); }) .catch((res) => { console.error(res); //add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "EVENTS_NOT_DELETED")); }); }; @@ -706,7 +692,6 @@ export const updateScheduledEventsBulk = (values) => async (dispatch) => { if (!eventChanges || !originalEvent) { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 4. addNotification( "error", "EVENTS_NOT_UPDATED_ID", @@ -772,12 +757,10 @@ export const updateScheduledEventsBulk = (values) => async (dispatch) => { .put("/admin-ng/event/bulk/update", formData) .then((res) => { console.info(res); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "EVENTS_UPDATED_ALL")); }) .catch((res) => { console.error(res); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "EVENTS_NOT_UPDATED_ALL")); }); }; diff --git a/app/src/thunks/groupDetailsThunks.ts b/app/src/thunks/groupDetailsThunks.ts index acb54b1b0e..db772da1dc 100644 --- a/app/src/thunks/groupDetailsThunks.ts +++ b/app/src/thunks/groupDetailsThunks.ts @@ -55,16 +55,13 @@ export const updateGroupDetails = (values, groupId) => async (dispatch) => { .put(`/admin-ng/groups/${groupId}`, data) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "GROUP_UPDATED")); }) .catch((response) => { console.error(response); if (response.status === 409) { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "GROUP_CONFLICT")); } else { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "GROUP_NOT_SAVED")); } }); diff --git a/app/src/thunks/groupThunks.ts b/app/src/thunks/groupThunks.ts index be9a9e88c2..c42f9ec97e 100644 --- a/app/src/thunks/groupThunks.ts +++ b/app/src/thunks/groupThunks.ts @@ -43,16 +43,13 @@ export const postNewGroup = (values) => async (dispatch) => { }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "GROUP_ADDED")); }) .catch((response) => { console.error(response); if (response.status === 409) { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "GROUP_CONFLICT")); } else { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "GROUP_NOT_SAVED")); } }); @@ -66,13 +63,11 @@ export const deleteGroup = (id) => async (dispatch) => { .then((res) => { console.info(res); // add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "GROUP_DELETED")); }) .catch((res) => { console.error(res); // add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "GROUP_NOT_DELETED")); }); }; diff --git a/app/src/thunks/notificationThunks.ts b/app/src/thunks/notificationThunks.ts index 2267a9e601..96cbe65d91 100644 --- a/app/src/thunks/notificationThunks.ts +++ b/app/src/thunks/notificationThunks.ts @@ -11,7 +11,7 @@ import { } from "../configs/generalConfig"; // @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. -export const addNotification = (type, key, duration, parameter, context, id?) => ( +export const addNotification = (type, key, duration?, parameter?, context?, id?) => ( // @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. dispatch, // @ts-expect-error TS(7006): Parameter 'getState' implicitly has an 'any' type. diff --git a/app/src/thunks/recordingThunks.ts b/app/src/thunks/recordingThunks.ts index f08584ecd8..cd1d7ddc37 100644 --- a/app/src/thunks/recordingThunks.ts +++ b/app/src/thunks/recordingThunks.ts @@ -67,7 +67,6 @@ export const deleteRecording = (id) => async (dispatch) => { .then((res) => { console.info(res); // add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "LOCATION_DELETED")); }) .catch((res) => { @@ -75,11 +74,9 @@ export const deleteRecording = (id) => async (dispatch) => { // add error notification depending on status code if (res.status === 401) { dispatch( -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. addNotification("error", "LOCATION_NOT_DELETED_NOT_AUTHORIZED") ); } else { -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "LOCATION_NOT_DELETED")); } }); diff --git a/app/src/thunks/seriesThunks.ts b/app/src/thunks/seriesThunks.ts index 6065ef99a8..304c215767 100644 --- a/app/src/thunks/seriesThunks.ts +++ b/app/src/thunks/seriesThunks.ts @@ -155,12 +155,10 @@ export const postNewSeries = (values, metadataInfo, extendedMetadata) => async ( }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "SERIES_ADDED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "SERIES_NOT_SAVED")); }); }; @@ -195,13 +193,11 @@ export const deleteSeries = (id) => async (dispatch) => { .then((res) => { console.info(res); // add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "SERIES_DELETED")); }) .catch((res) => { console.error(res); // add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "SERIES_NOT_DELETED")); }); }; @@ -222,13 +218,11 @@ export const deleteMultipleSeries = (series) => async (dispatch) => { .then((res) => { console.info(res); //add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "SERIES_DELETED")); }) .catch((res) => { console.error(res); //add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "SERIES_NOT_DELETED")); }); }; diff --git a/app/src/thunks/taskThunks.ts b/app/src/thunks/taskThunks.ts index fc7b0bf58c..a2e76fc79d 100644 --- a/app/src/thunks/taskThunks.ts +++ b/app/src/thunks/taskThunks.ts @@ -33,12 +33,10 @@ export const postTasks = (values: any) => async (dispatch: any) => { }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "TASK_CREATED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "TASK_NOT_CREATED")); }); }; diff --git a/app/src/thunks/themeDetailsThunks.ts b/app/src/thunks/themeDetailsThunks.ts index 878f7cedec..3a67d0a8ca 100644 --- a/app/src/thunks/themeDetailsThunks.ts +++ b/app/src/thunks/themeDetailsThunks.ts @@ -56,12 +56,10 @@ export const updateThemeDetails = (id, values) => async (dispatch) => { }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "THEME_CREATED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "THEME_NOT_CREATED")); }); }; diff --git a/app/src/thunks/themeThunks.ts b/app/src/thunks/themeThunks.ts index 177f97bd91..c7288ebc48 100644 --- a/app/src/thunks/themeThunks.ts +++ b/app/src/thunks/themeThunks.ts @@ -42,12 +42,10 @@ export const postNewTheme = (values) => async (dispatch) => { }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "THEME_CREATED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "THEME_NOT_CREATED")); }); }; @@ -59,13 +57,11 @@ export const deleteTheme = (id) => async (dispatch) => { .then((res) => { console.info(res); // add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "THEME_DELETED")); }) .catch((res) => { console.error(res); // add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "THEME_NOT_DELETED")); }); }; diff --git a/app/src/thunks/userDetailsThunks.ts b/app/src/thunks/userDetailsThunks.ts index 77318c3eba..8d6d44928b 100644 --- a/app/src/thunks/userDetailsThunks.ts +++ b/app/src/thunks/userDetailsThunks.ts @@ -40,12 +40,10 @@ export const updateUserDetails = (values, username) => async (dispatch) => { .put(`/admin-ng/users/${username}.json`, data) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "USER_UPDATED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "USER_NOT_SAVED")); }); }; diff --git a/app/src/thunks/userInfoThunks.ts b/app/src/thunks/userInfoThunks.ts index 3511319b59..be4ceadca7 100644 --- a/app/src/thunks/userInfoThunks.ts +++ b/app/src/thunks/userInfoThunks.ts @@ -29,7 +29,6 @@ export const fetchUserInfo = () => async (dispatch) => { } catch (e) { console.error(e); dispatch(loadUserInfoFailure()); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "PROBLEM_ON_START")); } }; diff --git a/app/src/thunks/userThunks.ts b/app/src/thunks/userThunks.ts index 897da9be0f..0fb6cfe4a0 100644 --- a/app/src/thunks/userThunks.ts +++ b/app/src/thunks/userThunks.ts @@ -29,12 +29,10 @@ export const postNewUser = (values) => async (dispatch) => { }) .then((response) => { console.info(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "USER_ADDED")); }) .catch((response) => { console.error(response); -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "USER_NOT_SAVED")); }); }; @@ -48,13 +46,11 @@ export const deleteUser = (id) => async (dispatch) => { .then((res) => { console.info(res); // add success notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("success", "USER_DELETED")); }) .catch((res) => { console.error(res); // add error notification -// @ts-expect-error TS(2554): Expected 5 arguments, but got 2. dispatch(addNotification("error", "USER_NOT_DELETED")); }); }; From 27d81eb7d474943cad2064198c66f8d20630401f Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 19 Dec 2023 16:24:34 +0100 Subject: [PATCH 03/11] Move postNewUser to userSlice Moving code to where I think it belongs --- .../users/partials/wizard/NewUserWizard.tsx | 9 +++--- app/src/slices/userSlice.ts | 29 ++++++++++++++++++- app/src/thunks/userThunks.ts | 24 --------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/src/components/users/partials/wizard/NewUserWizard.tsx b/app/src/components/users/partials/wizard/NewUserWizard.tsx index 2d97ddd3e0..40096f6cff 100644 --- a/app/src/components/users/partials/wizard/NewUserWizard.tsx +++ b/app/src/components/users/partials/wizard/NewUserWizard.tsx @@ -7,8 +7,9 @@ import NewUserGeneralTab from "./NewUserGeneralTab"; import UserRolesTab from "./UserRolesTab"; import { initialFormValuesNewUser } from "../../../../configs/modalConfig"; import { getUsernames } from "../../../../selectors/userSelectors"; -import { postNewUser } from "../../../../thunks/userThunks"; import { NewUserSchema } from "../../../../utils/validate"; +import { postNewUser } from "../../../../slices/userSlice"; +import { useAppDispatch } from "../../../../store"; /** * This component renders the new user wizard @@ -16,9 +17,9 @@ import { NewUserSchema } from "../../../../utils/validate"; const NewUserWizard = ({ close, usernames, - postNewUser }: any) => { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const navStyle = { left: "0px", @@ -35,7 +36,7 @@ const NewUserWizard = ({ // @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. const handleSubmit = (values) => { - const response = postNewUser(values); + const response = dispatch(postNewUser(values)); console.info(response); close(); }; @@ -111,8 +112,6 @@ const mapStateToProps = (state) => ({ // Mapping actions to dispatch // @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. const mapDispatchToProps = (dispatch) => ({ -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - postNewUser: (values) => dispatch(postNewUser(values)), }); export default connect(mapStateToProps, mapDispatchToProps)(NewUserWizard); diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts index da695bd8ee..12892891d5 100644 --- a/app/src/slices/userSlice.ts +++ b/app/src/slices/userSlice.ts @@ -1,7 +1,8 @@ import { PayloadAction, SerializedError, createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { usersTableConfig } from "../configs/tableConfigs/usersTableConfig"; import axios from 'axios'; -import { getURLParams } from "../utils/resourceUtils"; +import { buildUserBody, getURLParams } from "../utils/resourceUtils"; +import { addNotification } from '../thunks/notificationThunks'; /** * This file contains redux reducer for actions affecting the state of users @@ -45,6 +46,31 @@ export const fetchUsers = createAsyncThunk('users/fetchUsers', async (_, { getSt return res.data; }); +export const postNewUser = createAsyncThunk('users/postNewUser', async (values: any, {dispatch}) => { + // get URL params used for post request + let data = buildUserBody(values); + + axios + .post("/admin-ng/users", data, { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + // Usually we would extraReducers for responses, but reducers are not allowed to dispatch + // (they need to be free of side effects) + // Since we want to dispatch, we have to handle responses in our thunk + .then((response) => { + console.info(response); + dispatch(addNotification("success", "USER_ADDED")); + return response.data; + }) + .catch((response) => { + console.error(response); + dispatch(addNotification("error", "USER_NOT_SAVED")); + return response.data; + }); +}); + const usersSlice = createSlice({ name: 'users', initialState, @@ -58,6 +84,7 @@ const usersSlice = createSlice({ // These are used for thunks extraReducers: builder => { builder + // fetchUsers .addCase(fetchUsers.pending, (state) => { state.status = 'loading'; }) diff --git a/app/src/thunks/userThunks.ts b/app/src/thunks/userThunks.ts index 0fb6cfe4a0..d3161a1705 100644 --- a/app/src/thunks/userThunks.ts +++ b/app/src/thunks/userThunks.ts @@ -1,5 +1,4 @@ import axios from "axios"; -import { buildUserBody } from "../utils/resourceUtils"; import { transformToIdValueArray } from "../utils/utils"; import { addNotification } from "./notificationThunks"; @@ -14,29 +13,6 @@ export const fetchUsersAndUsernames = async () => { return transformToIdValueArray(response); }; -// new user to backend -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. -export const postNewUser = (values) => async (dispatch) => { - // get URL params used for post request - let data = buildUserBody(values); - - // POST request - axios - .post("/admin-ng/users", data, { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }) - .then((response) => { - console.info(response); - dispatch(addNotification("success", "USER_ADDED")); - }) - .catch((response) => { - console.error(response); - dispatch(addNotification("error", "USER_NOT_SAVED")); - }); -}; - // delete user with provided id // @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type. export const deleteUser = (id) => async (dispatch) => { From 1b39d83c00d26119e8704b67ad35609c4fa1d9dd Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 19 Dec 2023 17:30:49 +0100 Subject: [PATCH 04/11] Move deleteUser to userSlice Moving code to where I think it belongs --- .../users/partials/UsersActionsCell.tsx | 9 ++++---- app/src/slices/userSlice.ts | 21 +++++++++++++++++-- app/src/thunks/userThunks.ts | 19 ----------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/app/src/components/users/partials/UsersActionsCell.tsx b/app/src/components/users/partials/UsersActionsCell.tsx index 1a72687e9e..c8da94c045 100644 --- a/app/src/components/users/partials/UsersActionsCell.tsx +++ b/app/src/components/users/partials/UsersActionsCell.tsx @@ -2,22 +2,23 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import ConfirmModal from "../../shared/ConfirmModal"; -import { deleteUser } from "../../../thunks/userThunks"; import UserDetailsModal from "./modal/UserDetailsModal"; import { fetchUserDetails } from "../../../thunks/userDetailsThunks"; import { getUserInformation } from "../../../selectors/userInfoSelectors"; import { hasAccess } from "../../../utils/utils"; +import { deleteUser } from "../../../slices/userSlice"; +import { useAppDispatch } from "../../../store"; /** * This component renders the action cells of users in the table view */ const UsersActionCell = ({ row, - deleteUser, fetchUserDetails, user }: any) => { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const [displayDeleteConfirmation, setDeleteConfirmation] = useState(false); const [displayUserDetails, setUserDetails] = useState(false); @@ -28,7 +29,7 @@ const UsersActionCell = ({ // @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type. const deletingUser = (id) => { - deleteUser(id); + dispatch(deleteUser(id)); }; const showUserDetails = async () => { @@ -91,8 +92,6 @@ const mapStateToProps = (state) => ({ // Mapping actions to dispatch // @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. const mapDispatchToProps = (dispatch) => ({ -// @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type. - deleteUser: (id) => dispatch(deleteUser(id)), // @ts-expect-error TS(7006): Parameter 'username' implicitly has an 'any' type. fetchUserDetails: (username) => dispatch(fetchUserDetails(username)), }); diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts index 12892891d5..1a9eb7f641 100644 --- a/app/src/slices/userSlice.ts +++ b/app/src/slices/userSlice.ts @@ -36,6 +36,7 @@ const initialState: UsersState = { limit: 0, }; +// fetch users from server export const fetchUsers = createAsyncThunk('users/fetchUsers', async (_, { getState }) => { const state = getState(); let params = getURLParams(state); @@ -46,6 +47,7 @@ export const fetchUsers = createAsyncThunk('users/fetchUsers', async (_, { getSt return res.data; }); +// new user to backend export const postNewUser = createAsyncThunk('users/postNewUser', async (values: any, {dispatch}) => { // get URL params used for post request let data = buildUserBody(values); @@ -62,12 +64,27 @@ export const postNewUser = createAsyncThunk('users/postNewUser', async (values: .then((response) => { console.info(response); dispatch(addNotification("success", "USER_ADDED")); - return response.data; }) .catch((response) => { console.error(response); dispatch(addNotification("error", "USER_NOT_SAVED")); - return response.data; + }); +}); + +// delete user with provided id +export const deleteUser = createAsyncThunk('users/postNewUser', async (id: any, {dispatch}) => { + // API call for deleting an user + axios + .delete(`/admin-ng/users/${id}.json`) + .then((res) => { + console.info(res); + // add success notification + dispatch(addNotification("success", "USER_DELETED")); + }) + .catch((res) => { + console.error(res); + // add error notification + dispatch(addNotification("error", "USER_NOT_DELETED")); }); }); diff --git a/app/src/thunks/userThunks.ts b/app/src/thunks/userThunks.ts index d3161a1705..1081306b47 100644 --- a/app/src/thunks/userThunks.ts +++ b/app/src/thunks/userThunks.ts @@ -1,6 +1,5 @@ import axios from "axios"; import { transformToIdValueArray } from "../utils/utils"; -import { addNotification } from "./notificationThunks"; // get users and their user names export const fetchUsersAndUsernames = async () => { @@ -12,21 +11,3 @@ export const fetchUsersAndUsernames = async () => { return transformToIdValueArray(response); }; - -// delete user with provided id -// @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type. -export const deleteUser = (id) => async (dispatch) => { - // API call for deleting an user - axios - .delete(`/admin-ng/users/${id}.json`) - .then((res) => { - console.info(res); - // add success notification - dispatch(addNotification("success", "USER_DELETED")); - }) - .catch((res) => { - console.error(res); - // add error notification - dispatch(addNotification("error", "USER_NOT_DELETED")); - }); -}; From 59f6ac3c7525a45fcd7d8f0b7d6fbf37d8b17484 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 19 Dec 2023 17:37:25 +0100 Subject: [PATCH 05/11] Move fetchUsersAndUsernames to userSlice Which allows us to get rid of userThunks.ts --- .../users/partials/wizard/GroupUsersPage.tsx | 2 +- app/src/slices/userSlice.ts | 12 ++++++++++++ app/src/thunks/userThunks.ts | 13 ------------- 3 files changed, 13 insertions(+), 14 deletions(-) delete mode 100644 app/src/thunks/userThunks.ts diff --git a/app/src/components/users/partials/wizard/GroupUsersPage.tsx b/app/src/components/users/partials/wizard/GroupUsersPage.tsx index 852cdf4780..4514eae008 100644 --- a/app/src/components/users/partials/wizard/GroupUsersPage.tsx +++ b/app/src/components/users/partials/wizard/GroupUsersPage.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import SelectContainer from "../../../shared/wizard/SelectContainer"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; -import { fetchUsersAndUsernames } from "../../../../thunks/userThunks"; +import { fetchUsersAndUsernames } from "../../../../slices/userSlice"; /** * This component renders the user selection page of the new group wizard and group details wizard diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts index 1a9eb7f641..dffb8f3c61 100644 --- a/app/src/slices/userSlice.ts +++ b/app/src/slices/userSlice.ts @@ -1,6 +1,7 @@ import { PayloadAction, SerializedError, createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { usersTableConfig } from "../configs/tableConfigs/usersTableConfig"; import axios from 'axios'; +import { transformToIdValueArray } from "../utils/utils"; import { buildUserBody, getURLParams } from "../utils/resourceUtils"; import { addNotification } from '../thunks/notificationThunks'; @@ -88,6 +89,17 @@ export const deleteUser = createAsyncThunk('users/postNewUser', async (id: any, }); }); +// get users and their user names +export const fetchUsersAndUsernames = async () => { + let data = await axios.get( + "/admin-ng/resources/USERS.NAME.AND.USERNAME.json" + ); + + const response = await data.data; + + return transformToIdValueArray(response); +}; + const usersSlice = createSlice({ name: 'users', initialState, diff --git a/app/src/thunks/userThunks.ts b/app/src/thunks/userThunks.ts deleted file mode 100644 index 1081306b47..0000000000 --- a/app/src/thunks/userThunks.ts +++ /dev/null @@ -1,13 +0,0 @@ -import axios from "axios"; -import { transformToIdValueArray } from "../utils/utils"; - -// get users and their user names -export const fetchUsersAndUsernames = async () => { - let data = await axios.get( - "/admin-ng/resources/USERS.NAME.AND.USERNAME.json" - ); - - const response = await data.data; - - return transformToIdValueArray(response); -}; From 94da6fb0af288d077a69133720c41045d70aacce Mon Sep 17 00:00:00 2001 From: Arnei Date: Wed, 20 Dec 2023 11:06:49 +0100 Subject: [PATCH 06/11] Move `usersTableMap` into its own file Fixes a "can't access lexical declaration 'X' before initialization" error, by resolving a kind of circular dependency. --- app/src/components/users/Users.tsx | 2 +- app/src/configs/tableConfigs/usersTableConfig.ts | 12 ------------ app/src/configs/tableConfigs/usersTableMap.ts | 12 ++++++++++++ 3 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 app/src/configs/tableConfigs/usersTableMap.ts diff --git a/app/src/components/users/Users.tsx b/app/src/components/users/Users.tsx index c582e04b2a..caebc004a3 100644 --- a/app/src/components/users/Users.tsx +++ b/app/src/components/users/Users.tsx @@ -7,7 +7,7 @@ import TableFilters from "../shared/TableFilters"; import Table from "../shared/Table"; import Notifications from "../shared/Notifications"; import NewResourceModal from "../shared/NewResourceModal"; -import { usersTemplateMap } from "../../configs/tableConfigs/usersTableConfig"; +import { usersTemplateMap } from "../../configs/tableConfigs/usersTableMap"; import { getTotalUsers } from "../../selectors/userSelectors"; import { loadAclsIntoTable, diff --git a/app/src/configs/tableConfigs/usersTableConfig.ts b/app/src/configs/tableConfigs/usersTableConfig.ts index 9c966e943b..f28b9e314a 100644 --- a/app/src/configs/tableConfigs/usersTableConfig.ts +++ b/app/src/configs/tableConfigs/usersTableConfig.ts @@ -1,6 +1,3 @@ -import UsersActionCell from "../../components/users/partials/UsersActionsCell"; -import UsersRolesCell from "../../components/users/partials/UsersRolesCell"; - /** * Config that contains the columns and further information regarding users. These are the information that never or hardly changes. * That's why it is hard coded here and not fetched from server. @@ -51,12 +48,3 @@ export const usersTableConfig = { multiSelect: false, }; -/** - * This map contains the mapping between the template strings above and the corresponding react component. - * This helps to render different templates of cells more dynamically. Even empty needed, because Table component - * uses template map. - */ -export const usersTemplateMap = { - UsersActionsCell: UsersActionCell, - UsersRolesCell: UsersRolesCell, -}; diff --git a/app/src/configs/tableConfigs/usersTableMap.ts b/app/src/configs/tableConfigs/usersTableMap.ts new file mode 100644 index 0000000000..779668b582 --- /dev/null +++ b/app/src/configs/tableConfigs/usersTableMap.ts @@ -0,0 +1,12 @@ +import UsersActionCell from "../../components/users/partials/UsersActionsCell"; +import UsersRolesCell from "../../components/users/partials/UsersRolesCell"; + +/** + * This map contains the mapping between the template strings above and the corresponding react component. + * This helps to render different templates of cells more dynamically. Even empty needed, because Table component + * uses template map. + */ +export const usersTemplateMap = { + UsersActionsCell: UsersActionCell, + UsersRolesCell: UsersRolesCell, +}; From 80c2d94b426399de27f16af045a8f595ae4fa373 Mon Sep 17 00:00:00 2001 From: Arnei Date: Wed, 20 Dec 2023 14:10:02 +0100 Subject: [PATCH 07/11] Add typing for user selectors --- app/src/selectors/userSelectors.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/selectors/userSelectors.ts b/app/src/selectors/userSelectors.ts index fcdceeb3ba..a9624c054e 100644 --- a/app/src/selectors/userSelectors.ts +++ b/app/src/selectors/userSelectors.ts @@ -2,11 +2,11 @@ * This file contains selectors regarding users */ import { createSelector } from "reselect"; +import { RootState } from "../store"; -export const getUsers = (state: any) => state.users.results; -export const getTotalUsers = (state: any) => state.users.total; +export const getUsers = (state: RootState) => state.users.results; +export const getTotalUsers = (state: RootState) => state.users.total; export const getUsernames = createSelector(getUsers, (users) => { -// @ts-expect-error TS(7006): Parameter 'user' implicitly has an 'any' type. return users.map((user) => user.username); }); From 42841f3634a1d8e9f96f93f13ac1d7f400981110 Mon Sep 17 00:00:00 2001 From: Arnei Date: Wed, 3 Jan 2024 15:05:22 +0100 Subject: [PATCH 08/11] Fix user thunk path --- app/src/slices/userSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts index dffb8f3c61..6113f20a5c 100644 --- a/app/src/slices/userSlice.ts +++ b/app/src/slices/userSlice.ts @@ -73,7 +73,7 @@ export const postNewUser = createAsyncThunk('users/postNewUser', async (values: }); // delete user with provided id -export const deleteUser = createAsyncThunk('users/postNewUser', async (id: any, {dispatch}) => { +export const deleteUser = createAsyncThunk('users/deleteUser', async (id: any, {dispatch}) => { // API call for deleting an user axios .delete(`/admin-ng/users/${id}.json`) From 30dc0c32cb40ada40e34bd78657645af6defef38 Mon Sep 17 00:00:00 2001 From: Arnei Date: Mon, 12 Feb 2024 10:53:15 +0100 Subject: [PATCH 09/11] Add typing for user result Adds typing on what we expect the backend to return for users. --- app/src/slices/userSlice.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts index 6113f20a5c..f826fa68c2 100644 --- a/app/src/slices/userSlice.ts +++ b/app/src/slices/userSlice.ts @@ -8,10 +8,19 @@ import { addNotification } from '../thunks/notificationThunks'; /** * This file contains redux reducer for actions affecting the state of users */ +type UserResult = { + email?: string, + manageable: boolean, + name: string, + provider: string, + roles: { name: string, type: string }[], + username: string, +} + type UsersState = { status: 'uninitialized' | 'loading' | 'succeeded' | 'failed', error: SerializedError | null, - results: any[], // TODO: proper typing + results: UserResult[], columns: any, // TODO: proper typing, derive from `initialColumns` total: number, count: number, From 450233e891cef059dafc7b1e1b1e24290e4a038c Mon Sep 17 00:00:00 2001 From: Arnei Date: Wed, 14 Feb 2024 15:15:20 +0100 Subject: [PATCH 10/11] Fix table filters in user table --- app/src/components/users/Users.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/components/users/Users.tsx b/app/src/components/users/Users.tsx index caebc004a3..227908db1c 100644 --- a/app/src/components/users/Users.tsx +++ b/app/src/components/users/Users.tsx @@ -41,6 +41,11 @@ const Users: React.FC = () => { const user = useAppSelector(state => getUserInformation(state)); const currentFilterType = useAppSelector(state => getCurrentFilterResource(state)); + // TODO: Get rid of the wrappers when modernizing redux is done + const fetchUsersWrapper = () => { + dispatch(fetchUsers()) + } + const loadUsers = async () => { // Fetching users from server await dispatch(fetchUsers()); @@ -165,8 +170,8 @@ const Users: React.FC = () => {
{/* Include filters component */}

{t("USERS.USERS.TABLE.CAPTION")}

From 981c74275d367a5efd8765a46dac4e5776312c7f Mon Sep 17 00:00:00 2001 From: Arnei Date: Wed, 14 Feb 2024 16:11:58 +0100 Subject: [PATCH 11/11] Fix user table crashing when changing columns --- app/src/slices/userSlice.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts index f826fa68c2..ce0e7842df 100644 --- a/app/src/slices/userSlice.ts +++ b/app/src/slices/userSlice.ts @@ -113,10 +113,10 @@ const usersSlice = createSlice({ name: 'users', initialState, reducers: { - setUserColumns(state, action: PayloadAction<{ - updatedColumns: UsersState["columns"], - }>) { - state.columns = action.payload.updatedColumns; + setUserColumns(state, action: PayloadAction< + UsersState["columns"] + >) { + state.columns = action.payload; }, }, // These are used for thunks