From a72f3f856eb88ba85880753fa80de9e89be4263b Mon Sep 17 00:00:00 2001 From: Grant Fitzsimmons <37256050+grantfitzsimmons@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:39:17 -0500 Subject: [PATCH 1/5] feat: institution admins in collection view Show institution-level admins in the Collection user list even when they have no collection-specific roles. --- .../lib/components/Security/Collection.tsx | 44 +++++++++++++++---- .../lib/components/Security/Institution.tsx | 14 +++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Security/Collection.tsx b/specifyweb/frontend/js_src/lib/components/Security/Collection.tsx index 78e92b788bf..684539e2904 100644 --- a/specifyweb/frontend/js_src/lib/components/Security/Collection.tsx +++ b/specifyweb/frontend/js_src/lib/components/Security/Collection.tsx @@ -9,7 +9,7 @@ import { commonText } from '../../localization/common'; import { userText } from '../../localization/user'; import type { GetOrSet, IR, RA } from '../../utils/types'; import { defined, localized } from '../../utils/types'; -import { index } from '../../utils/utils'; +import { index, sortFunction } from '../../utils/utils'; import { Container, Ul } from '../Atoms'; import { formatConjunction } from '../Atoms/Internationalization'; import { Link } from '../Atoms/Link'; @@ -32,6 +32,7 @@ import { useCollectionUserRoles, useCollectionUsersWithPolicies, } from './CollectionHooks'; +import { useAdmins } from './Institution'; import type { Role } from './Role'; import { fetchRoles } from './utils'; @@ -99,7 +100,30 @@ export function CollectionView({ const [userRoles] = getSetUserRoles; useErrorContext('userRoles', userRoles); + const admins = useAdmins(); + + /* + * Include institution admin users in the collection user list so it is + * clear they have power in every collection, even if they don't have + * any explicit collection-level roles or policies assigned. + */ const mergedUsers = mergeCollectionUsers(userRoles, usersWithPolicies); + const displayUsers = + typeof mergedUsers === 'object' && typeof admins === 'object' + ? [ + ...mergedUsers, + ...admins.adminUsers + .filter( + ({ userId }) => + !mergedUsers.some((user) => user.userId === userId) + ) + .map(({ userId, userName }) => ({ + userId, + userName, + roles: [] as RA, + })), + ].sort(sortFunction(({ userName }) => userName)) + : mergedUsers; const navigate = useNavigate(); const location = useLocation(); @@ -152,24 +176,28 @@ export function CollectionView({ collectionTable: tables.Collection.label, })} - {typeof mergedUsers === 'object' ? ( - mergedUsers.length === 0 ? ( + {typeof displayUsers === 'object' ? ( + displayUsers.length === 0 ? ( commonText.none() ) : ( <>