@deix/rossini-identity-management 5.2.1 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/users/Users.d.ts +3 -1
- package/lib/components/users/Users.d.ts.map +1 -1
- package/lib/components/users/Users.js +77 -11
- package/lib/components/users/UsersTable.d.ts +6 -4
- package/lib/components/users/UsersTable.d.ts.map +1 -1
- package/lib/components/users/UsersTable.js +27 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/lang/users.d.ts.map +1 -1
- package/lib/lang/users.js +16 -0
- package/lib/types/groups.d.ts +4 -0
- package/lib/types/groups.d.ts.map +1 -1
- package/lib/types/users.d.ts +5 -0
- package/lib/types/users.d.ts.map +1 -1
- package/lib/utils/helpers/index.d.ts +2 -0
- package/lib/utils/helpers/index.d.ts.map +1 -1
- package/lib/utils/helpers/index.js +2 -1
- package/lib/utils/hooks/groups.d.ts +1 -0
- package/lib/utils/hooks/groups.d.ts.map +1 -1
- package/lib/utils/hooks/groups.js +1 -1
- package/lib/utils/hooks/users.d.ts +9 -6
- package/lib/utils/hooks/users.d.ts.map +1 -1
- package/lib/utils/hooks/users.js +99 -24
- package/package.json +7 -6
|
@@ -12,7 +12,9 @@ interface UsersProps {
|
|
|
12
12
|
emailAsUsername?: boolean;
|
|
13
13
|
hideGroupsSelection?: boolean;
|
|
14
14
|
disableDelete?: boolean;
|
|
15
|
+
showGroupsColumn?: boolean;
|
|
16
|
+
showExport?: boolean;
|
|
15
17
|
}
|
|
16
|
-
declare const Users: ({ apiUrl, additionalTabs, onUserCreate, onUserUpdate, onUserDelete, editUsername, filteredGroup, emailAsUsername, hideGroupsSelection, disableDelete, }: UsersProps) => React.JSX.Element;
|
|
18
|
+
declare const Users: ({ apiUrl, additionalTabs, onUserCreate, onUserUpdate, onUserDelete, editUsername, filteredGroup, emailAsUsername, hideGroupsSelection, disableDelete, showGroupsColumn, showExport, }: UsersProps) => React.JSX.Element;
|
|
17
19
|
export default Users;
|
|
18
20
|
//# sourceMappingURL=Users.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Users.d.ts","sourceRoot":"","sources":["../../../src/components/users/Users.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"Users.d.ts","sourceRoot":"","sources":["../../../src/components/users/Users.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAuB5D,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAQzC,OAAiB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAShD,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAE3B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IAEtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IAEvC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAEpC,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,QAAA,MAAM,KAAK,0LAaR,UAAU,sBA+RZ,CAAC;eAEa,KAAK"}
|
|
@@ -1,27 +1,59 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
|
5
|
-
import
|
|
5
|
+
import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
|
6
|
+
import { Box, Button, Checkbox, Grid, IconButton, Link, ListItemText, MenuItem, TextField, } from '@mui/material';
|
|
6
7
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
7
8
|
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
|
8
9
|
import { useAPI } from '@deix/rossini-core';
|
|
9
10
|
import { useQueryClient } from '@tanstack/react-query';
|
|
11
|
+
import { useGridApiRef } from '@mui/x-data-grid';
|
|
10
12
|
import { SearchField } from '../../components';
|
|
11
13
|
import CreateUserDialog from './CreateUserDialog';
|
|
12
14
|
import UsersTable from './UsersTable';
|
|
13
15
|
import usersTranslations from '../../lang/users';
|
|
14
|
-
import { useDeleteUser, useUsers } from '../../utils/hooks/users';
|
|
16
|
+
import { useDeleteUser, useGroupsByUser, useUsers, useUsersInGroups, } from '../../utils/hooks/users';
|
|
17
|
+
import { useGroups } from '../../utils/hooks/groups';
|
|
15
18
|
import UserTabs from './UsersTabs';
|
|
16
19
|
import { APIClientProvider } from '../../utils/hooks/useAPIClient';
|
|
17
20
|
import commonTranslations from '../../lang/common';
|
|
18
21
|
import useTranslate from '../../utils/hooks/useTranslate';
|
|
19
|
-
|
|
22
|
+
import { sortGroupsByName } from '../../utils/helpers';
|
|
23
|
+
// Group filter value for the users that are not in any group
|
|
24
|
+
const NO_GROUP = '__no-group__';
|
|
25
|
+
const Users = ({ apiUrl, additionalTabs = [], onUserCreate, onUserUpdate, onUserDelete, editUsername, filteredGroup, emailAsUsername = false, hideGroupsSelection = false, disableDelete = false, showGroupsColumn = false, showExport = false, }) => {
|
|
20
26
|
const t = useTranslate();
|
|
21
27
|
const apiClient = useAPI(apiUrl);
|
|
22
28
|
const queryClient = useQueryClient();
|
|
23
|
-
|
|
29
|
+
// Values selected in the group filter: group ids and/or NO_GROUP.
|
|
30
|
+
// Empty means all users; otherwise a user is shown if it matches any of them.
|
|
31
|
+
const [groupFilter, setGroupFilter] = useState([]);
|
|
32
|
+
const withoutGroup = groupFilter.includes(NO_GROUP);
|
|
33
|
+
const { data: groups } = useGroups(apiClient);
|
|
34
|
+
// Group filter and groups column are hidden when there are no groups
|
|
35
|
+
const hasGroups = !!groups && groups.length > 0;
|
|
36
|
+
const groupsColumnVisible = showGroupsColumn && hasGroups;
|
|
37
|
+
// Passing id and name avoids looking the groups up by name
|
|
38
|
+
const selectedGroups = useMemo(() => groups?.filter((g) => groupFilter.includes(g.id)) ?? [], [groups, groupFilter]);
|
|
39
|
+
const { data: allUsers } = useUsers(apiClient, filteredGroup);
|
|
40
|
+
const { data: groupsUsers } = useUsersInGroups(apiClient, selectedGroups);
|
|
41
|
+
// Needed by the groups column and to find the users without group
|
|
42
|
+
const { data: groupsByUser } = useGroupsByUser(apiClient, groupsColumnVisible || withoutGroup);
|
|
43
|
+
const users = useMemo(() => {
|
|
44
|
+
if (groupFilter.length === 0)
|
|
45
|
+
return allUsers;
|
|
46
|
+
if (!groupsUsers || (withoutGroup && (!allUsers || !groupsByUser))) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (!withoutGroup)
|
|
50
|
+
return groupsUsers;
|
|
51
|
+
// The two lists are disjoint: users without group are in no group
|
|
52
|
+
const usersWithoutGroup = allUsers?.filter((u) => !groupsByUser?.[u.id]) ?? [];
|
|
53
|
+
return [...groupsUsers, ...usersWithoutGroup];
|
|
54
|
+
}, [groupFilter, allUsers, groupsUsers, withoutGroup, groupsByUser]);
|
|
24
55
|
const { mutate: deleteUser } = useDeleteUser(queryClient, apiClient);
|
|
56
|
+
const apiRef = useGridApiRef();
|
|
25
57
|
const [searchString, setSearchString] = useState('');
|
|
26
58
|
const [filteredUsers, setFilteredUsers] = useState();
|
|
27
59
|
const [selectedUser, setSelectedUser] = useState();
|
|
@@ -46,10 +78,12 @@ const Users = ({ apiUrl, additionalTabs = [], onUserCreate, onUserUpdate, onUser
|
|
|
46
78
|
.includes(searchString.toLowerCase())));
|
|
47
79
|
}
|
|
48
80
|
}, [searchString, users, setFilteredUsers]);
|
|
49
|
-
// If the user change updated the selected user to reflect the chages
|
|
81
|
+
// If the user change updated the selected user to reflect the chages.
|
|
82
|
+
// Uses allUsers so that removing the user from the filtered group
|
|
83
|
+
// does not close its details.
|
|
50
84
|
useEffect(() => {
|
|
51
85
|
if (selectedUser) {
|
|
52
|
-
const updatedSelectedUser =
|
|
86
|
+
const updatedSelectedUser = allUsers?.find((user) => user.id === selectedUser.id);
|
|
53
87
|
if (updatedSelectedUser) {
|
|
54
88
|
setSelectedUser(updatedSelectedUser);
|
|
55
89
|
}
|
|
@@ -58,7 +92,16 @@ const Users = ({ apiUrl, additionalTabs = [], onUserCreate, onUserUpdate, onUser
|
|
|
58
92
|
setSelectedUser(undefined);
|
|
59
93
|
}
|
|
60
94
|
}
|
|
61
|
-
}, [
|
|
95
|
+
}, [allUsers, selectedUser, setSelectedUser]);
|
|
96
|
+
// Downloads the users in the table (all pages, with search and group
|
|
97
|
+
// filters applied) as e.g. "Utenti-2026-10-30.csv"
|
|
98
|
+
const handleExport = () => {
|
|
99
|
+
apiRef.current?.exportDataAsCsv({
|
|
100
|
+
fileName: `${t(usersTranslations.title)}-${new Date().toISOString().slice(0, 10)}`,
|
|
101
|
+
// The BOM lets Excel read accented characters correctly
|
|
102
|
+
utf8WithBom: true,
|
|
103
|
+
});
|
|
104
|
+
};
|
|
62
105
|
const handleDelete = (user) => {
|
|
63
106
|
deleteUser(user.id);
|
|
64
107
|
if (typeof onUserDelete === 'function') {
|
|
@@ -76,12 +119,35 @@ const Users = ({ apiUrl, additionalTabs = [], onUserCreate, onUserUpdate, onUser
|
|
|
76
119
|
}, children: [!selectedUser && (_jsxs(_Fragment, { children: [_jsxs(Grid, { container: true, spacing: 2, sx: { marginBottom: 2 }, children: [_jsx(Grid, { children: _jsx(SearchField, { placeholder: t(usersTranslations.search), onSearch: (value) => {
|
|
77
120
|
setSearchString(value);
|
|
78
121
|
setPaginationModel((model) => ({ ...model, page: 0 }));
|
|
79
|
-
} }) }), _jsx(Grid, { children:
|
|
122
|
+
} }) }), !filteredGroup && hasGroups && (_jsx(Grid, { children: _jsxs(TextField, { select: true, size: 'small', label: t(usersTranslations.groupFilter), value: groupFilter, onChange: (evt) => {
|
|
123
|
+
const { value } = evt.target;
|
|
124
|
+
// On autofill the value is a comma separated string
|
|
125
|
+
setGroupFilter(typeof value === 'string' ? value.split(',') : value);
|
|
126
|
+
setPaginationModel((model) => ({ ...model, page: 0 }));
|
|
127
|
+
}, slotProps: {
|
|
128
|
+
select: {
|
|
129
|
+
multiple: true,
|
|
130
|
+
// Show "All groups" instead of an empty select
|
|
131
|
+
displayEmpty: true,
|
|
132
|
+
renderValue: (selected) => {
|
|
133
|
+
const values = selected;
|
|
134
|
+
if (values.length === 0) {
|
|
135
|
+
return t(usersTranslations.allGroups);
|
|
136
|
+
}
|
|
137
|
+
return values
|
|
138
|
+
.map((v) => v === NO_GROUP
|
|
139
|
+
? t(usersTranslations.noGroup)
|
|
140
|
+
: groups.find((g) => g.id === v)?.name)
|
|
141
|
+
.join(', ');
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
inputLabel: { shrink: true },
|
|
145
|
+
}, sx: { minWidth: 200, maxWidth: 400 }, children: [_jsxs(MenuItem, { value: NO_GROUP, children: [_jsx(Checkbox, { size: 'small', checked: groupFilter.includes(NO_GROUP) }), _jsx(ListItemText, { primary: t(usersTranslations.noGroup) })] }), sortGroupsByName(groups).map((group) => (_jsxs(MenuItem, { value: group.id, children: [_jsx(Checkbox, { size: 'small', checked: groupFilter.includes(group.id) }), _jsx(ListItemText, { primary: group.name })] }, group.id)))] }) })), _jsx(Grid, { children: _jsx(Button, { variant: 'contained', startIcon: _jsx(PersonAddIcon, {}), onClick: () => {
|
|
80
146
|
setOpenCreateUserDialog(true);
|
|
81
147
|
setSelectedUser(undefined);
|
|
82
|
-
}, children: t(usersTranslations.new) }) })] }), _jsx(UsersTable, { users: filteredUsers, selectedUser: selectedUser, setSelectedUser: (user) => {
|
|
148
|
+
}, children: t(usersTranslations.new) }) }), showExport && (_jsx(Grid, { sx: { marginLeft: 'auto' }, children: _jsx(Button, { variant: 'outlined', startIcon: _jsx(FileDownloadIcon, {}), onClick: handleExport, children: t(usersTranslations.download) }) }))] }), _jsx(UsersTable, { users: filteredUsers, selectedUser: selectedUser, setSelectedUser: (user) => {
|
|
83
149
|
setSelectedUser(user);
|
|
84
|
-
}, onDelete: handleDelete, emailAsUsername: emailAsUsername, disableDelete: disableDelete, paginationModel: paginationModel, onPaginationModelChange: setPaginationModel })] })), selectedUser && (_jsxs(Box, { sx: {
|
|
150
|
+
}, onDelete: handleDelete, emailAsUsername: emailAsUsername, disableDelete: disableDelete, showGroupsColumn: groupsColumnVisible, groupsByUser: groupsByUser, apiRef: apiRef, paginationModel: paginationModel, onPaginationModelChange: setPaginationModel })] })), selectedUser && (_jsxs(Box, { sx: {
|
|
85
151
|
display: 'flex',
|
|
86
152
|
flexDirection: 'column',
|
|
87
153
|
alignItems: 'flex-start',
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { GridPaginationModel } from '@mui/x-data-grid';
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { GridApi, GridPaginationModel } from '@mui/x-data-grid';
|
|
3
3
|
import { Group } from '../../types/groups';
|
|
4
4
|
import { User } from '../../types/users';
|
|
5
5
|
export interface TableProps {
|
|
6
6
|
users?: User[];
|
|
7
|
-
groups?: Group[];
|
|
8
7
|
selectedUser?: User;
|
|
9
8
|
setSelectedUser: (user: User) => void;
|
|
10
9
|
onDelete: (user: User) => void;
|
|
11
10
|
emailAsUsername?: boolean;
|
|
12
11
|
disableDelete?: boolean;
|
|
12
|
+
showGroupsColumn?: boolean;
|
|
13
|
+
groupsByUser?: Record<string, Group[]>;
|
|
14
|
+
apiRef?: RefObject<GridApi | null>;
|
|
13
15
|
paginationModel?: GridPaginationModel;
|
|
14
16
|
onPaginationModelChange?: (model: GridPaginationModel) => void;
|
|
15
17
|
}
|
|
16
|
-
declare const UsersTable: ({ users, selectedUser, setSelectedUser, onDelete, emailAsUsername, disableDelete, paginationModel, onPaginationModelChange, }: TableProps) =>
|
|
18
|
+
declare const UsersTable: ({ users, selectedUser, setSelectedUser, onDelete, emailAsUsername, disableDelete, showGroupsColumn, groupsByUser, apiRef, paginationModel, onPaginationModelChange, }: TableProps) => import("react").JSX.Element;
|
|
17
19
|
export default UsersTable;
|
|
18
20
|
//# sourceMappingURL=UsersTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UsersTable.d.ts","sourceRoot":"","sources":["../../../src/components/users/UsersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"UsersTable.d.ts","sourceRoot":"","sources":["../../../src/components/users/UsersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAgDzC,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAEvC,MAAM,CAAC,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAChE;AAED,QAAA,MAAM,UAAU,0KAYb,UAAU,gCAgCZ,CAAC;eAEa,UAAU"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Chip, Tooltip } from '@mui/material';
|
|
2
3
|
import usersTranslations from '../../lang/users';
|
|
4
|
+
import { sortGroupsByName } from '../../utils/helpers';
|
|
3
5
|
import Table from '../tables/Table';
|
|
6
|
+
// Groups shown as chips in a cell; the others are summarized in a "+N" chip
|
|
7
|
+
const MAX_GROUP_CHIPS = 2;
|
|
4
8
|
const usersTableColumns = [
|
|
5
9
|
{
|
|
6
10
|
field: 'firstName',
|
|
@@ -21,5 +25,26 @@ const usersTableColumns = [
|
|
|
21
25
|
type: 'boolean',
|
|
22
26
|
},
|
|
23
27
|
];
|
|
24
|
-
const
|
|
28
|
+
const GroupChips = ({ groups = [] }) => {
|
|
29
|
+
const sortedGroups = sortGroupsByName(groups);
|
|
30
|
+
const hiddenGroups = sortedGroups.slice(MAX_GROUP_CHIPS);
|
|
31
|
+
return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5, height: '100%' }, children: [sortedGroups.slice(0, MAX_GROUP_CHIPS).map((group) => (_jsx(Chip, { label: group.name, size: 'small' }, group.id))), hiddenGroups.length > 0 && (_jsx(Tooltip, { title: hiddenGroups.map((g) => g.name).join(', '), children: _jsx(Chip, { label: `+${hiddenGroups.length}`, size: 'small' }) }))] }));
|
|
32
|
+
};
|
|
33
|
+
const UsersTable = ({ users = [], selectedUser, setSelectedUser, onDelete, emailAsUsername = false, disableDelete = false, showGroupsColumn = false, groupsByUser, apiRef, paginationModel, onPaginationModelChange, }) => (_jsx(Table, { rows: users, columns: [
|
|
34
|
+
...usersTableColumns.filter((c) => c.field !== 'username' || !emailAsUsername),
|
|
35
|
+
...(showGroupsColumn
|
|
36
|
+
? [
|
|
37
|
+
{
|
|
38
|
+
field: 'groups',
|
|
39
|
+
label: usersTranslations.userGroups,
|
|
40
|
+
sortable: false,
|
|
41
|
+
// Used by the CSV export, where the chips are not rendered
|
|
42
|
+
valueGetter: (user) => sortGroupsByName(groupsByUser?.[user.id] ?? [])
|
|
43
|
+
.map((g) => g.name)
|
|
44
|
+
.join('; '),
|
|
45
|
+
renderCell: (user) => (_jsx(GroupChips, { groups: groupsByUser?.[user.id] })),
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
: []),
|
|
49
|
+
], selectedRowId: selectedUser?.id, onRowClick: setSelectedUser, onDelete: disableDelete ? undefined : onDelete, paginationModel: paginationModel, onPaginationModelChange: onPaginationModelChange, apiRef: apiRef }));
|
|
25
50
|
export default UsersTable;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default as Users } from './components/users/Users';
|
|
2
2
|
export { default as Groups } from './components/groups/Groups';
|
|
3
|
-
export type { User, KeycloakQueryParams, UserByIdResult, UseUsersByIdsOptions, } from './types/users';
|
|
4
|
-
export type { Group } from './types/groups';
|
|
3
|
+
export type { User, KeycloakQueryParams, UserByIdResult, UsersInGroupsResult, UseUsersByIdsOptions, } from './types/users';
|
|
4
|
+
export type { Group, GroupFilter } from './types/groups';
|
|
5
5
|
export { default as SearchField } from './components/SearchField';
|
|
6
6
|
export { default as DeleteConfirmationDialog } from './components/DeleteConfirmationDialog';
|
|
7
7
|
export { default as AddItemDialog } from './components/AddItemDialog';
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EACV,IAAI,EACJ,mBAAmB,EACnB,cAAc,EACd,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EACV,IAAI,EACJ,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC"}
|
package/lib/lang/users.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/lang/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,QAAA,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/lang/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,QAAA,MAAM,iBAAiB,EA6JlB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAA;CAAE,CAAC;eAE3B,iBAAiB"}
|
package/lib/lang/users.js
CHANGED
|
@@ -7,6 +7,22 @@ const usersTranslations = {
|
|
|
7
7
|
en: 'Search...',
|
|
8
8
|
it: 'Cerca...',
|
|
9
9
|
},
|
|
10
|
+
groupFilter: {
|
|
11
|
+
en: 'Group',
|
|
12
|
+
it: 'Gruppo',
|
|
13
|
+
},
|
|
14
|
+
allGroups: {
|
|
15
|
+
en: 'All groups',
|
|
16
|
+
it: 'Tutti i gruppi',
|
|
17
|
+
},
|
|
18
|
+
noGroup: {
|
|
19
|
+
en: 'No group',
|
|
20
|
+
it: 'Nessun gruppo',
|
|
21
|
+
},
|
|
22
|
+
download: {
|
|
23
|
+
en: 'Download CSV',
|
|
24
|
+
it: 'Scarica CSV',
|
|
25
|
+
},
|
|
10
26
|
new: {
|
|
11
27
|
en: 'New User',
|
|
12
28
|
it: 'Nuovo Utente',
|
package/lib/types/groups.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/types/groups.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
1
|
+
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/types/groups.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAID,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb"}
|
package/lib/types/users.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface UserByIdResult {
|
|
|
17
17
|
isLoading: boolean;
|
|
18
18
|
isError: boolean;
|
|
19
19
|
}
|
|
20
|
+
export interface UsersInGroupsResult {
|
|
21
|
+
data?: User[];
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
isError: boolean;
|
|
24
|
+
}
|
|
20
25
|
export interface UseUsersByIdsOptions {
|
|
21
26
|
/** Milliseconds a cached user is considered fresh. Default: 60000. */
|
|
22
27
|
staleTime?: number;
|
package/lib/types/users.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/types/users.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AASD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B"}
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/types/users.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AASD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAID,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/helpers/index.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,eAAO,MAAM,gBAAgB,WAAY,KAAK,EAAE,YACa,CAAC"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
// Returns a copy of the groups sorted by name
|
|
2
|
+
export const sortGroupsByName = (groups) => groups.slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -2,6 +2,7 @@ import { QueryClient, UseMutationResult, UseQueryResult } from '@tanstack/react-
|
|
|
2
2
|
import { AxiosInstance } from 'axios';
|
|
3
3
|
import { Group } from '../../types/groups';
|
|
4
4
|
import { User } from '../../types/users';
|
|
5
|
+
export declare const getGroups: (apiClient: AxiosInstance) => Promise<Group[]>;
|
|
5
6
|
export declare const useGroups: (apiClient: AxiosInstance) => UseQueryResult<Group[] | undefined, Error>;
|
|
6
7
|
export declare const useAddGroup: (queryClient: QueryClient, apiClient: AxiosInstance) => UseMutationResult<Group, Error, Group, unknown>;
|
|
7
8
|
export declare const useUpdateGroup: (queryClient: QueryClient, apiClient: AxiosInstance) => UseMutationResult<Group, Error, Group, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/groups.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,cAAc,EAGf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/groups.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,cAAc,EAGf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAIzC,eAAO,MAAM,SAAS,cAAqB,aAAa,qBAKvD,CAAC;AAEF,eAAO,MAAM,SAAS,cACT,aAAa,KACvB,cAAc,CAAC,KAAK,EAAE,GAAG,SAAS,EAAE,KAAK,CAM3C,CAAC;AAcF,eAAO,MAAM,WAAW,gBACT,WAAW,aACb,aAAa,oDA6BzB,CAAC;AAiBF,eAAO,MAAM,cAAc,gBACZ,WAAW,aACb,aAAa,oDA8BzB,CAAC;AAgBF,eAAO,MAAM,cAAc,gBACZ,WAAW,aACb,aAAa,6EA6BzB,CAAC;AAgBF,eAAO,MAAM,aAAa,OACpB,MAAM,GAAG,SAAS,aACX,aAAa,KACvB,cAAc,CAAC,IAAI,EAAE,GAAG,SAAS,EAAE,KAAK,CAM1C,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { enqueueSnackbar } from 'notistack';
|
|
|
3
3
|
import groupsTranslations from '../../lang/groups';
|
|
4
4
|
import useTranslate from './useTranslate';
|
|
5
5
|
// Get list of groups
|
|
6
|
-
const getGroups = async (apiClient) => {
|
|
6
|
+
export const getGroups = async (apiClient) => {
|
|
7
7
|
const { data } = await apiClient.get(`/groups`, {
|
|
8
8
|
params: { max: 1000 },
|
|
9
9
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { QueryClient, UseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { AxiosInstance } from 'axios';
|
|
3
3
|
import { UserFormFields } from '../../components/users/UserForm';
|
|
4
|
-
import { Group } from '../../types/groups';
|
|
5
|
-
import { KeycloakQueryParams, User, UserByIdResult, UseUsersByIdsOptions } from '../../types/users';
|
|
6
|
-
export declare const getUsers: (apiClient: AxiosInstance, username?: string, group?: string, offset?: number, limit?: number, params?: KeycloakQueryParams) => Promise<User[]>;
|
|
7
|
-
export declare const useUsers: (apiClient: AxiosInstance, group?: string, offset?: number, limit?: number, params?: KeycloakQueryParams) => UseQueryResult<User[] | undefined, Error>;
|
|
4
|
+
import { Group, GroupFilter } from '../../types/groups';
|
|
5
|
+
import { KeycloakQueryParams, User, UserByIdResult, UsersInGroupsResult, UseUsersByIdsOptions } from '../../types/users';
|
|
6
|
+
export declare const getUsers: (apiClient: AxiosInstance, username?: string, group?: string | GroupFilter, offset?: number, limit?: number, params?: KeycloakQueryParams) => Promise<User[]>;
|
|
7
|
+
export declare const useUsers: (apiClient: AxiosInstance, group?: string | GroupFilter, offset?: number, limit?: number, params?: KeycloakQueryParams) => UseQueryResult<User[] | undefined, Error>;
|
|
8
|
+
export declare const useUsersInGroups: (apiClient: AxiosInstance, groups: (string | GroupFilter)[], params?: KeycloakQueryParams) => UsersInGroupsResult;
|
|
9
|
+
export declare const getGroupsByUser: (apiClient: AxiosInstance) => Promise<Record<string, Group[]>>;
|
|
10
|
+
export declare const useGroupsByUser: (apiClient: AxiosInstance, enabled?: boolean) => UseQueryResult<Record<string, Group[]> | undefined, Error>;
|
|
8
11
|
export declare const getUser: (id: string, apiClient: AxiosInstance) => Promise<User>;
|
|
9
12
|
export declare const useUser: (id: string, apiClient: AxiosInstance) => UseQueryResult<User | undefined, Error>;
|
|
10
13
|
export declare const useUsersByIds: (apiClient: AxiosInstance, ids: (string | undefined)[], options?: UseUsersByIdsOptions) => UserByIdResult[];
|
|
@@ -38,6 +41,6 @@ export declare const useRemoveUserFromGroup: (queryClient: QueryClient, apiClien
|
|
|
38
41
|
export declare const useSendUpdatePasswordEmail: (queryClient: QueryClient, apiClient: AxiosInstance) => import("@tanstack/react-query").UseMutationResult<{
|
|
39
42
|
userId: string;
|
|
40
43
|
}, Error, string, unknown>;
|
|
41
|
-
export declare const getUsersCount: (apiClient: AxiosInstance, username?: string, group?: string) => Promise<number>;
|
|
42
|
-
export declare const useUsersCount: (apiClient: AxiosInstance, group?: string) => UseQueryResult<number | undefined, Error>;
|
|
44
|
+
export declare const getUsersCount: (apiClient: AxiosInstance, username?: string, group?: string | GroupFilter) => Promise<number>;
|
|
45
|
+
export declare const useUsersCount: (apiClient: AxiosInstance, group?: string | GroupFilter) => UseQueryResult<number | undefined, Error>;
|
|
43
46
|
//# sourceMappingURL=users.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/users.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAKX,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/users.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAKX,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,IAAI,EACJ,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AA8C3B,eAAO,MAAM,QAAQ,cACR,aAAa,aACb,MAAM,UACT,MAAM,GAAG,WAAW,WACpB,MAAM,UACP,MAAM,WACJ,mBAAmB,oBAkB7B,CAAC;AAkBF,eAAO,MAAM,QAAQ,cACR,aAAa,UAChB,MAAM,GAAG,WAAW,WACpB,MAAM,UACP,MAAM,WACJ,mBAAmB,KAC3B,cAAc,CAAC,IAAI,EAAE,GAAG,SAAS,EAAE,KAAK,CAM1C,CAAC;AAkBF,eAAO,MAAM,gBAAgB,cAChB,aAAa,UAChB,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,WACvB,mBAAmB,KAC3B,mBAQC,CAAC;AAOL,eAAO,MAAM,eAAe,cAAqB,aAAa,qCAc7D,CAAC;AAEF,eAAO,MAAM,eAAe,cACf,aAAa,YACf,OAAO,KACf,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,KAAK,CAO3D,CAAC;AAGF,eAAO,MAAM,OAAO,OAAc,MAAM,aAAa,aAAa,kBAGjE,CAAC;AAEF,eAAO,MAAM,OAAO,OACd,MAAM,aACC,aAAa,KACvB,cAAc,CAAC,IAAI,GAAG,SAAS,EAAE,KAAK,CAMxC,CAAC;AAoBF,eAAO,MAAM,aAAa,cACb,aAAa,OACnB,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,YACjB,oBAAoB,KAC7B,cAAc,EAUb,CAAC;AAGL,eAAO,MAAM,OAAO,yDAKjB;IACD,OAAO,EAAE,IAAI,CAAC;IACd,mBAAmB,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,kBAWA,CAAC;AAEF,eAAO,MAAM,UAAU,gBACR,WAAW,aACb,aAAa;UAQd,cAAc;YACZ,MAAM,EAAE,GAAG,SAAS;WA6BjC,CAAC;AAiBF,eAAO,MAAM,aAAa,gBACX,WAAW,aACb,aAAa,kFA6BzB,CAAC;AAgBF,eAAO,MAAM,aAAa,gBACX,WAAW,aACb,aAAa,4GA6BzB,CAAC;AAcF,eAAO,MAAM,aAAa,OACpB,MAAM,GAAG,SAAS,aACX,aAAa,KACvB,cAAc,CAAC,KAAK,EAAE,GAAG,SAAS,EAAE,KAAK,CAM3C,CAAC;AAmBF,eAAO,MAAM,iBAAiB,gBACf,WAAW,aACb,aAAa;;;;YAIsB,MAAM;aAAW,MAAM;WAmCtE,CAAC;AAmBF,eAAO,MAAM,sBAAsB,gBACpB,WAAW,aACb,aAAa;;;;YAIsB,MAAM;aAAW,MAAM;WAmCtE,CAAC;AAkBF,eAAO,MAAM,0BAA0B,gBACxB,WAAW,aACb,aAAa;;0BAoBzB,CAAC;AAEF,eAAO,MAAM,aAAa,cACb,aAAa,aACb,MAAM,UACT,MAAM,GAAG,WAAW,oBAiB7B,CAAC;AAEF,eAAO,MAAM,aAAa,cACb,aAAa,UAChB,MAAM,GAAG,WAAW,KAC3B,cAAc,CAAC,MAAM,GAAG,SAAS,EAAE,KAAK,CAS1C,CAAC"}
|
package/lib/utils/hooks/users.js
CHANGED
|
@@ -1,46 +1,110 @@
|
|
|
1
1
|
import { useMutation, useQueries, useQuery, } from '@tanstack/react-query';
|
|
2
2
|
import { enqueueSnackbar } from 'notistack';
|
|
3
3
|
import usersTranslations from '../../lang/users';
|
|
4
|
+
import { getGroups } from './groups';
|
|
4
5
|
import useTranslate from './useTranslate';
|
|
5
6
|
const isServiceAccount = (u) => !!u.serviceAccountClientId || !!u.username?.startsWith('service-account-');
|
|
7
|
+
const getExtraParams = (params) => params?.briefRepresentation !== undefined
|
|
8
|
+
? { briefRepresentation: params.briefRepresentation }
|
|
9
|
+
: {};
|
|
10
|
+
// Returns the id of the group, looking it up by name only when it is missing
|
|
11
|
+
const getGroupId = async (apiClient, group) => {
|
|
12
|
+
const { name, id } = typeof group === 'string' ? { name: group } : group;
|
|
13
|
+
if (id)
|
|
14
|
+
return id;
|
|
15
|
+
const { data: groupData } = await apiClient.get(`/groups`, {
|
|
16
|
+
params: { max: 1, search: name, exact: 'true' },
|
|
17
|
+
});
|
|
18
|
+
return groupData.length === 1 ? groupData[0].id : undefined;
|
|
19
|
+
};
|
|
20
|
+
// Value identifying the group in the query keys: the id when known, so that
|
|
21
|
+
// the same group passed by name or by id does not change the key format
|
|
22
|
+
const getGroupKey = (group) => typeof group === 'string' ? group : (group?.id ?? group?.name);
|
|
23
|
+
// Get the members of a group by id, excluding service accounts
|
|
24
|
+
const getGroupMembers = async (apiClient, groupId, offset = 0, limit = 1000, params) => {
|
|
25
|
+
const { data } = await apiClient.get(`/groups/${groupId}/members`, {
|
|
26
|
+
params: { max: limit, first: offset, ...getExtraParams(params) },
|
|
27
|
+
});
|
|
28
|
+
return data.filter((u) => !isServiceAccount(u));
|
|
29
|
+
};
|
|
6
30
|
// Get list of users
|
|
7
31
|
export const getUsers = async (apiClient, username, group, offset = 0, limit = 1000, params) => {
|
|
8
|
-
const { briefRepresentation } = params ?? {};
|
|
9
|
-
const extraParams = briefRepresentation !== undefined ? { briefRepresentation } : {};
|
|
10
32
|
if (group) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
if (groupData.length !== 1)
|
|
33
|
+
const groupId = await getGroupId(apiClient, group);
|
|
34
|
+
if (!groupId)
|
|
15
35
|
return [];
|
|
16
|
-
|
|
17
|
-
return data.filter((u) => !isServiceAccount(u));
|
|
36
|
+
return getGroupMembers(apiClient, groupId, offset, limit, params);
|
|
18
37
|
}
|
|
19
38
|
const { data } = await apiClient.get(`/users`, {
|
|
20
39
|
params: {
|
|
21
40
|
max: limit,
|
|
22
41
|
first: offset,
|
|
23
42
|
...(username ? { username, exact: 'true' } : {}),
|
|
24
|
-
...
|
|
43
|
+
...getExtraParams(params),
|
|
25
44
|
},
|
|
26
45
|
});
|
|
27
46
|
return data;
|
|
28
47
|
};
|
|
48
|
+
// Shared by useUsers and useUsersInGroups, so they use the same cache
|
|
49
|
+
const getUsersQueryKey = (group, offset, limit, params) => [
|
|
50
|
+
'users',
|
|
51
|
+
`users-${JSON.stringify(getGroupKey(group))}`,
|
|
52
|
+
`${JSON.stringify(offset)}`,
|
|
53
|
+
`${JSON.stringify(limit)}`,
|
|
54
|
+
...(params?.briefRepresentation !== undefined
|
|
55
|
+
? [`brief-${params.briefRepresentation}`]
|
|
56
|
+
: []),
|
|
57
|
+
];
|
|
29
58
|
export const useUsers = (apiClient, group, offset = 0, limit = 1000, params) => {
|
|
30
59
|
return useQuery({
|
|
31
|
-
queryKey:
|
|
32
|
-
'users',
|
|
33
|
-
`users-${JSON.stringify(group)}`,
|
|
34
|
-
`${JSON.stringify(offset)}`,
|
|
35
|
-
`${JSON.stringify(limit)}`,
|
|
36
|
-
...(params?.briefRepresentation !== undefined
|
|
37
|
-
? [`brief-${params.briefRepresentation}`]
|
|
38
|
-
: []),
|
|
39
|
-
],
|
|
60
|
+
queryKey: getUsersQueryKey(group, offset, limit, params),
|
|
40
61
|
queryFn: () => getUsers(apiClient, undefined, group, offset, limit, params),
|
|
41
62
|
staleTime: 0,
|
|
42
63
|
});
|
|
43
64
|
};
|
|
65
|
+
// Defined OUTSIDE the hook to keep its identity stable (see combineUsers)
|
|
66
|
+
const combineGroupsUsers = (results) => {
|
|
67
|
+
const isLoading = results.some((r) => r.isLoading);
|
|
68
|
+
const isError = results.some((r) => r.isError);
|
|
69
|
+
if (results.some((r) => !r.data))
|
|
70
|
+
return { isLoading, isError };
|
|
71
|
+
// A user in more than one of the groups is returned once
|
|
72
|
+
const usersById = new Map();
|
|
73
|
+
results.forEach((r) => r.data?.forEach((u) => usersById.set(u.id, u)));
|
|
74
|
+
return { data: Array.from(usersById.values()), isLoading, isError };
|
|
75
|
+
};
|
|
76
|
+
// Users that are members of at least one of the groups, without duplicates.
|
|
77
|
+
// One request per group, sharing the cache with useUsers.
|
|
78
|
+
export const useUsersInGroups = (apiClient, groups, params) => useQueries({
|
|
79
|
+
queries: groups.map((group) => ({
|
|
80
|
+
queryKey: getUsersQueryKey(group, 0, 1000, params),
|
|
81
|
+
queryFn: () => getUsers(apiClient, undefined, group, 0, 1000, params),
|
|
82
|
+
staleTime: 0,
|
|
83
|
+
})),
|
|
84
|
+
combine: combineGroupsUsers,
|
|
85
|
+
});
|
|
86
|
+
// Get the groups of every user, as a map from user id to groups.
|
|
87
|
+
// Keycloak does not return the groups in the users list, so the members of
|
|
88
|
+
// every group are loaded (one request per group). Only top-level groups are
|
|
89
|
+
// checked; users that are in no group (or only in a subgroup) are not in
|
|
90
|
+
// the map.
|
|
91
|
+
export const getGroupsByUser = async (apiClient) => {
|
|
92
|
+
const groups = await getGroups(apiClient);
|
|
93
|
+
const members = await Promise.all(groups.map((g) => getGroupMembers(apiClient, g.id, 0, 1000, { briefRepresentation: true })));
|
|
94
|
+
const groupsByUser = {};
|
|
95
|
+
groups.forEach((group, idx) => members[idx].forEach((u) => {
|
|
96
|
+
groupsByUser[u.id] = [...(groupsByUser[u.id] ?? []), group];
|
|
97
|
+
}));
|
|
98
|
+
return groupsByUser;
|
|
99
|
+
};
|
|
100
|
+
export const useGroupsByUser = (apiClient, enabled = true) => {
|
|
101
|
+
return useQuery({
|
|
102
|
+
queryKey: ['users', 'groups-by-user'],
|
|
103
|
+
queryFn: () => getGroupsByUser(apiClient),
|
|
104
|
+
enabled,
|
|
105
|
+
staleTime: 0,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
44
108
|
// Get single user
|
|
45
109
|
export const getUser = async (id, apiClient) => {
|
|
46
110
|
const { data } = await apiClient.get(`/users/${id}`);
|
|
@@ -234,6 +298,11 @@ export const useAddUserToGroup = (queryClient, apiClient) => {
|
|
|
234
298
|
queryKey: ['groups'],
|
|
235
299
|
type: 'all',
|
|
236
300
|
});
|
|
301
|
+
// Group filters and groups column depend on the memberships
|
|
302
|
+
await queryClient.refetchQueries({
|
|
303
|
+
queryKey: ['users'],
|
|
304
|
+
type: 'all',
|
|
305
|
+
});
|
|
237
306
|
},
|
|
238
307
|
});
|
|
239
308
|
};
|
|
@@ -272,6 +341,11 @@ export const useRemoveUserFromGroup = (queryClient, apiClient) => {
|
|
|
272
341
|
queryKey: ['groups'],
|
|
273
342
|
type: 'all',
|
|
274
343
|
});
|
|
344
|
+
// Group filters and groups column depend on the memberships
|
|
345
|
+
await queryClient.refetchQueries({
|
|
346
|
+
queryKey: ['users'],
|
|
347
|
+
type: 'all',
|
|
348
|
+
});
|
|
275
349
|
},
|
|
276
350
|
});
|
|
277
351
|
};
|
|
@@ -302,11 +376,9 @@ export const useSendUpdatePasswordEmail = (queryClient, apiClient) => {
|
|
|
302
376
|
};
|
|
303
377
|
export const getUsersCount = async (apiClient, username, group) => {
|
|
304
378
|
if (group) {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (groupData.length === 1) {
|
|
309
|
-
const { data } = await apiClient.get(`/groups/${groupData[0].id}/members`, { params: { max: -1 } });
|
|
379
|
+
const groupId = await getGroupId(apiClient, group);
|
|
380
|
+
if (groupId) {
|
|
381
|
+
const { data } = await apiClient.get(`/groups/${groupId}/members`, { params: { max: -1 } });
|
|
310
382
|
return data.length;
|
|
311
383
|
}
|
|
312
384
|
else {
|
|
@@ -320,7 +392,10 @@ export const getUsersCount = async (apiClient, username, group) => {
|
|
|
320
392
|
};
|
|
321
393
|
export const useUsersCount = (apiClient, group) => {
|
|
322
394
|
return useQuery({
|
|
323
|
-
queryKey: [
|
|
395
|
+
queryKey: [
|
|
396
|
+
'users-count',
|
|
397
|
+
`users-count-${JSON.stringify(getGroupKey(group))}`,
|
|
398
|
+
],
|
|
324
399
|
queryFn: () => getUsersCount(apiClient, undefined, group),
|
|
325
400
|
staleTime: 0,
|
|
326
401
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deix/rossini-identity-management",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@mdx-js/react": "^3.1.0",
|
|
45
45
|
"@mui/icons-material": "^9.4.0",
|
|
46
46
|
"@mui/material": "^9.4.0",
|
|
47
|
-
"@storybook/addon-docs": "^10.6.
|
|
48
|
-
"@storybook/addon-links": "^10.6.
|
|
49
|
-
"@storybook/addon-onboarding": "^10.6.
|
|
47
|
+
"@storybook/addon-docs": "^10.6.5",
|
|
48
|
+
"@storybook/addon-links": "^10.6.5",
|
|
49
|
+
"@storybook/addon-onboarding": "^10.6.5",
|
|
50
50
|
"@storybook/mdx2-csf": "^1.1.0",
|
|
51
|
-
"@storybook/nextjs-vite": "^10.6.
|
|
51
|
+
"@storybook/nextjs-vite": "^10.6.5",
|
|
52
52
|
"@tanstack/react-query": "^5.104.0",
|
|
53
53
|
"@tanstack/react-query-devtools": "^5.104.0",
|
|
54
54
|
"@types/node": "^26.6.3",
|
|
@@ -60,12 +60,13 @@
|
|
|
60
60
|
"eslint": "^10.11.0",
|
|
61
61
|
"eslint-config-prettier": "^10.1.1",
|
|
62
62
|
"eslint-plugin-prettier": "^5.5.6",
|
|
63
|
-
"eslint-plugin-storybook": "^10.6.
|
|
63
|
+
"eslint-plugin-storybook": "^10.6.5",
|
|
64
64
|
"json-server": "^1.0.0-beta.15",
|
|
65
65
|
"next": "~16.4.3",
|
|
66
66
|
"react": "^19.3.0",
|
|
67
67
|
"react-dom": "19.3.0",
|
|
68
68
|
"remark-mdx": "^3.0.0",
|
|
69
|
+
"storybook": "^10.6.5",
|
|
69
70
|
"ts-loader": "^9.6.2",
|
|
70
71
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
71
72
|
"typescript-eslint": "^8.70.1",
|