From 5b30b8b5093f1beb805b7a93be3ffa46a578da43 Mon Sep 17 00:00:00 2001 From: Marc Wodahl Date: Wed, 19 Jun 2024 16:12:14 -0600 Subject: [PATCH 1/4] Update organization dropdown to update correctly on organization add/change --- webapp/src/Dashboard.tsx | 5 +-- .../AdminOrganizationTab.tsx | 23 ++++++++++-- .../AdminOrganizationTabUser.tsx | 16 ++++++++- webapp/src/generalSlices/userSlice.ts | 35 ++++++++++++++++--- webapp/src/managers.tsx | 7 ++-- 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/webapp/src/Dashboard.tsx b/webapp/src/Dashboard.tsx index a472760d9..f2fd4c62e 100644 --- a/webapp/src/Dashboard.tsx +++ b/webapp/src/Dashboard.tsx @@ -14,7 +14,7 @@ import { // Actions getRsuData, } from './generalSlices/rsuSlice' -import { selectAuthLoginData, selectLoadingGlobal } from './generalSlices/userSlice' +import { selectAuthLoginData, selectLoadingGlobal, selectOrganizationName } from './generalSlices/userSlice' import { SecureStorageManager } from './managers' import { ReactKeycloakProvider } from '@react-keycloak/web' import keycloak from './keycloak-config' @@ -31,6 +31,7 @@ const Dashboard = () => { const dispatch: ThunkDispatch = useDispatch() const authLoginData = useSelector(selectAuthLoginData) const loadingGlobal = useSelector(selectLoadingGlobal) + const organizationName = useSelector(selectOrganizationName) useEffect(() => { keycloak @@ -53,7 +54,7 @@ const Dashboard = () => { dispatch(getRsuData()) }, [authLoginData, dispatch]) - console.log('Auth Role', SecureStorageManager.getUserRole()) + useEffect(() => {}, [organizationName]) return ( { if (activeTab === undefined) { @@ -59,8 +61,19 @@ const AdminOrganizationTab = () => { const errorState = useSelector(selectErrorState) const errorMsg = useSelector(selectErrorMsg) + const defaultOrgName = useSelector(selectOrganizationName) + var defaultOrgData = orgData.find((org) => org.name === defaultOrgName) + useEffect(() => { - dispatch(getOrgData({ orgName: 'all', all: true, specifiedOrg: undefined })) + dispatch(getOrgData({ orgName: 'all', all: true, specifiedOrg: undefined })).then(() => { + if (defaultOrgData) { + const selectedOrg = (orgData ?? []).find( + (organization: AdminOrgSummary) => organization?.name === defaultOrgName + ) + dispatch(setSelectedOrg(selectedOrg)) + defaultOrgData = null + } + }) }, [dispatch]) const updateTableData = (orgName: string) => { @@ -79,6 +92,12 @@ const AdminOrganizationTab = () => { updateTableData(selectedOrgName) } + const handleOrgDelete = (orgName) => { + dispatch(deleteOrg(orgName)) + dispatch(setOrganizationList({ value: { name: orgName }, type: 'delete' })) + dispatch(changeOrganization(orgData[0].name)) + } + return (
@@ -148,7 +167,7 @@ const AdminOrganizationTab = () => { dispatch(deleteOrg(selectedOrgName))} + deleteOrganization={() => handleOrgDelete(selectedOrgName)} selectedOrganization={selectedOrgName} /> diff --git a/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx b/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx index cf9660e95..01132647d 100644 --- a/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx +++ b/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx @@ -25,7 +25,7 @@ import { setSelectedUserRole, setSelectedUserList, } from './adminOrganizationTabUserSlice' -import { selectLoadingGlobal } from '../../generalSlices/userSlice' +import { selectAuthLoginData, selectLoadingGlobal, setOrganizationList } from '../../generalSlices/userSlice' import { useSelector, useDispatch } from 'react-redux' import '../adminRsuTab/Admin.css' @@ -47,6 +47,7 @@ const AdminOrganizationTabUser = (props: AdminOrganizationTabUserProps) => { const selectedUserList = useSelector(selectSelectedUserList) const availableRoles = useSelector(selectAvailableRoles) const loadingGlobal = useSelector(selectLoadingGlobal) + const authLoginData = useSelector(selectAuthLoginData) const [userColumns] = useState[]>([ { title: 'First Name', @@ -155,6 +156,9 @@ const AdminOrganizationTabUser = (props: AdminOrganizationTabUserProps) => { updateTableData: props.updateTableData, }) ) + if (row.email === authLoginData?.data?.email) { + dispatch(setOrganizationList({ value: { name: props.selectedOrg, role: row.role }, type: 'delete' })) + } } const userMultiDelete = async (rows: AdminOrgUser[]) => { @@ -165,6 +169,11 @@ const AdminOrganizationTabUser = (props: AdminOrganizationTabUserProps) => { updateTableData: props.updateTableData, }) ) + for (let i = 0; i < rows.length; i++) { + if (rows[i].email === authLoginData?.data?.email) { + dispatch(setOrganizationList({ value: { name: props.selectedOrg, role: rows[i].role }, type: 'delete' })) + } + } } const userMultiAdd = async (userList: AdminOrgUser[]) => { @@ -175,6 +184,11 @@ const AdminOrganizationTabUser = (props: AdminOrganizationTabUserProps) => { updateTableData: props.updateTableData, }) ) + for (let i = 0; i < userList.length; i++) { + if (userList[i].email === authLoginData?.data?.email) { + dispatch(setOrganizationList({ value: { name: props.selectedOrg, role: userList[i].role }, type: 'add' })) + } + } } const userBulkEdit = async ( diff --git a/webapp/src/generalSlices/userSlice.ts b/webapp/src/generalSlices/userSlice.ts index dda091e03..240133cdf 100644 --- a/webapp/src/generalSlices/userSlice.ts +++ b/webapp/src/generalSlices/userSlice.ts @@ -65,8 +65,28 @@ export const userSlice = createSlice({ SecureStorageManager.removeUserRole() }, changeOrganization: (state, action) => { - state.value.organization = + var organization = UserManager.getOrganization(state.value.authLoginData, action.payload) ?? state.value.organization + state.value.organization = organization + SecureStorageManager.setUserRole({ name: organization.name, role: organization.role }) + }, + setOrganizationList: (state, action) => { + if (action.payload.type === 'add') { + console.debug('payload: ', [...state.value.authLoginData.data.organizations, action.payload]) + state.value.authLoginData.data.organizations = [ + ...state.value.authLoginData.data.organizations, + action.payload.value, + ] + } else if (action.payload.type === 'delete') { + var index = state.value.authLoginData.data.organizations.findIndex( + (org) => org.name === action.payload.value.name + ) + if (index > -1) { + var updatedOrgList = state.value.authLoginData.data.organizations + updatedOrgList.splice(index, 1) + state.value.authLoginData.data.organizations = [...updatedOrgList] + } + } }, setLoading: (state, action) => { state.loading = action.payload @@ -102,7 +122,7 @@ export const userSlice = createSlice({ state.value.authLoginData = action.payload state.value.organization = action.payload?.data?.organizations?.[0] LocalStorageManager.setAuthData(action.payload) - SecureStorageManager.setUserRole(action.payload) + SecureStorageManager.setUserRole(action.payload['data']['organizations'][0]) }) .addCase(keycloakLogin.rejected, (state, action: PayloadAction) => { console.debug('keycloakLogin.rejected') @@ -115,8 +135,15 @@ export const userSlice = createSlice({ }, }) -export const { logout, changeOrganization, setLoading, setLoginFailure, setKcFailure, setRouteNotFound } = - userSlice.actions +export const { + logout, + changeOrganization, + setOrganizationList, + setLoading, + setLoginFailure, + setKcFailure, + setRouteNotFound, +} = userSlice.actions export const selectAuthLoginData = (state: RootState) => state.user.value.authLoginData export const selectToken = (state: RootState) => state.user.value.authLoginData.token diff --git a/webapp/src/managers.tsx b/webapp/src/managers.tsx index 6b9d22fe5..b9d502d8a 100644 --- a/webapp/src/managers.tsx +++ b/webapp/src/managers.tsx @@ -44,11 +44,8 @@ const SecureStorageManager = { return authData['role'] } }, - setUserRole: (authData) => { - return secureLocalStorage.setItem( - AUTH_DATA_SECURE_STORAGE_KEY, - JSON.stringify(authData['data']['organizations'][0]) - ) + setUserRole: (organization) => { + return secureLocalStorage.setItem(AUTH_DATA_SECURE_STORAGE_KEY, JSON.stringify(organization)) }, removeUserRole: () => { return secureLocalStorage.removeItem(AUTH_DATA_SECURE_STORAGE_KEY) From 1f4c8ddc649c9746b6f01a28a824afe4dddf4614 Mon Sep 17 00:00:00 2001 From: Marc Wodahl Date: Mon, 24 Jun 2024 10:14:30 -0600 Subject: [PATCH 2/4] Update org dropdown list on user-org edit --- .../AdminOrganizationTabUser.tsx | 9 ++++++++- .../AdminOrganizationTabUserTypes.d.ts | 1 + .../adminOrganizationTabUserSlice.test.ts | 3 ++- .../adminOrganizationTabUserSlice.tsx | 11 +++++++++-- webapp/src/generalSlices/userSlice.ts | 8 +++++++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx b/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx index 01132647d..74882b0ad 100644 --- a/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx +++ b/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUser.tsx @@ -25,7 +25,12 @@ import { setSelectedUserRole, setSelectedUserList, } from './adminOrganizationTabUserSlice' -import { selectAuthLoginData, selectLoadingGlobal, setOrganizationList } from '../../generalSlices/userSlice' +import { + selectAuthLoginData, + selectEmail, + selectLoadingGlobal, + setOrganizationList, +} from '../../generalSlices/userSlice' import { useSelector, useDispatch } from 'react-redux' import '../adminRsuTab/Admin.css' @@ -48,6 +53,7 @@ const AdminOrganizationTabUser = (props: AdminOrganizationTabUserProps) => { const availableRoles = useSelector(selectAvailableRoles) const loadingGlobal = useSelector(selectLoadingGlobal) const authLoginData = useSelector(selectAuthLoginData) + const userEmail = useSelector(selectEmail) const [userColumns] = useState[]>([ { title: 'First Name', @@ -203,6 +209,7 @@ const AdminOrganizationTabUser = (props: AdminOrganizationTabUserProps) => { dispatch( userBulkEditAction({ json, + selectedUser: userEmail, selectedOrg: props.selectedOrg, updateTableData: props.updateTableData, }) diff --git a/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUserTypes.d.ts b/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUserTypes.d.ts index 33d85b83a..d925cb344 100644 --- a/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUserTypes.d.ts +++ b/webapp/src/features/adminOrganizationTabUser/AdminOrganizationTabUserTypes.d.ts @@ -18,5 +18,6 @@ export type AdminOrgUserDeleteMultiple = { export type AdminOrgTabUserBulkEdit = { json: { [key: string]: { newData: AdminOrgTabUser } } selectedOrg: string + selectedUser: string updateTableData: (org: string) => void } diff --git a/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.test.ts b/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.test.ts index e9491fcfd..13f5be0e2 100644 --- a/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.test.ts +++ b/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.test.ts @@ -378,10 +378,11 @@ describe('async thunks', () => { obj2: { newData: { email: 'test2@gmail.com', role: 'role2' } }, } const selectedOrg = 'selectedOrg' + const selectedUser = 'selectedUser' const fetchPatchOrganization = jest.fn() const updateTableData = jest.fn() - const action = userBulkEdit({ json, selectedOrg, updateTableData }) + const action = userBulkEdit({ json, selectedOrg, selectedUser, updateTableData }) await action(dispatch, getState, undefined) expect(dispatch).toHaveBeenCalledTimes(2 + 2) diff --git a/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.tsx b/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.tsx index 73af90a3f..11bf51687 100644 --- a/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.tsx +++ b/webapp/src/features/adminOrganizationTabUser/adminOrganizationTabUserSlice.tsx @@ -1,5 +1,5 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' -import { selectToken } from '../../generalSlices/userSlice' +import { selectToken, setOrganizationList } from '../../generalSlices/userSlice' import EnvironmentVars from '../../EnvironmentVars' import apiHelper from '../../apis/api-helper' import { RootState } from '../../store' @@ -174,19 +174,26 @@ export const userAddMultiple = createAsyncThunk( export const userBulkEdit = createAsyncThunk( 'adminOrganizationTabUser/userBulkEdit', async (payload: AdminOrgTabUserBulkEdit, { dispatch }) => { - const { json, selectedOrg, updateTableData } = payload + const { json, selectedOrg, selectedUser, updateTableData } = payload const patchJson: adminOrgPatch = { name: selectedOrg, users_to_modify: [], } const rows = Object.values(json) + var orgUpdateVal = {} for (var row of rows) { + if (row.newData.email === selectedUser) { + orgUpdateVal = { name: selectedOrg, role: row.newData.role } + } const userRole = { email: row.newData.email, role: row.newData.role } patchJson.users_to_modify.push(userRole) } await dispatch(editOrg(patchJson)) dispatch(refresh({ selectedOrg, updateTableData })) + if (Object.keys(orgUpdateVal).length > 0) { + dispatch(setOrganizationList({ value: orgUpdateVal, orgName: selectedOrg, type: 'update' })) + } }, { condition: (_, { getState }) => selectToken(getState() as RootState) != undefined } ) diff --git a/webapp/src/generalSlices/userSlice.ts b/webapp/src/generalSlices/userSlice.ts index 240133cdf..e65c04fab 100644 --- a/webapp/src/generalSlices/userSlice.ts +++ b/webapp/src/generalSlices/userSlice.ts @@ -72,7 +72,6 @@ export const userSlice = createSlice({ }, setOrganizationList: (state, action) => { if (action.payload.type === 'add') { - console.debug('payload: ', [...state.value.authLoginData.data.organizations, action.payload]) state.value.authLoginData.data.organizations = [ ...state.value.authLoginData.data.organizations, action.payload.value, @@ -86,6 +85,13 @@ export const userSlice = createSlice({ updatedOrgList.splice(index, 1) state.value.authLoginData.data.organizations = [...updatedOrgList] } + } else if (action.payload.type === 'update') { + var index = state.value.authLoginData.data.organizations.findIndex((org) => org.name === action.payload.orgName) + if (index > -1) { + var updatedOrgList = state.value.authLoginData.data.organizations + updatedOrgList[index] = action.payload.value + state.value.authLoginData.data.organizations = [...updatedOrgList] + } } }, setLoading: (state, action) => { From 9ae0319f6783b5f376e1ad7ed384ea2caa2b2e96 Mon Sep 17 00:00:00 2001 From: Marc Wodahl Date: Tue, 2 Jul 2024 08:18:23 -0600 Subject: [PATCH 3/4] add useEffect comment for Admin Org tab --- .../src/features/adminOrganizationTab/AdminOrganizationTab.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/src/features/adminOrganizationTab/AdminOrganizationTab.tsx b/webapp/src/features/adminOrganizationTab/AdminOrganizationTab.tsx index d7c7114c1..e09874d03 100644 --- a/webapp/src/features/adminOrganizationTab/AdminOrganizationTab.tsx +++ b/webapp/src/features/adminOrganizationTab/AdminOrganizationTab.tsx @@ -66,6 +66,8 @@ const AdminOrganizationTab = () => { useEffect(() => { dispatch(getOrgData({ orgName: 'all', all: true, specifiedOrg: undefined })).then(() => { + // on first render set the default organization in the admin + // organization tab to the currently selected organization if (defaultOrgData) { const selectedOrg = (orgData ?? []).find( (organization: AdminOrgSummary) => organization?.name === defaultOrgName From fbda0a350f5e4750ef9c809f5c1f2afc80ed69d7 Mon Sep 17 00:00:00 2001 From: Marc Wodahl Date: Tue, 16 Jul 2024 15:26:11 -0600 Subject: [PATCH 4/4] Update map on org change --- webapp/src/pages/Map.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/src/pages/Map.tsx b/webapp/src/pages/Map.tsx index 7f52908f0..9efa325d6 100644 --- a/webapp/src/pages/Map.tsx +++ b/webapp/src/pages/Map.tsx @@ -35,6 +35,7 @@ import { // actions selectRsu, + getRsuData, toggleMapDisplay, getIssScmsStatus, getMapData, @@ -212,6 +213,7 @@ function MapPage(props: MapPageProps) { // useEffects for RSU layer useEffect(() => { + dispatch(getRsuData()) dispatch(selectRsu(null)) dispatch(clearFirmware()) }, [organization, dispatch])