diff --git a/client/src/__tests__/utils/UserRole.test.js b/client/src/__tests__/utils/UserRole.test.js
index 99326483..8e55b5f6 100644
--- a/client/src/__tests__/utils/UserRole.test.js
+++ b/client/src/__tests__/utils/UserRole.test.js
@@ -133,10 +133,10 @@ test("Allowed to delete Invitation", () => {
role: {id: "2", applicationUsages: applicationUsagesForManageId("11")}
};
const user = {userRoles: [mail, research]}
- const invitation = {intendedAuthority: AUTHORITIES.GUEST, roles: [mail, research]};
+ const invitation = {intended_authority: AUTHORITIES.GUEST, roles: [mail, research]};
expect(allowedToDeleteInvitation(user, invitation)).toBeTruthy();
- invitation.intendedAuthority = AUTHORITIES.INVITER;
+ invitation.intended_authority = AUTHORITIES.INVITER;
expect(allowedToDeleteInvitation(user, invitation)).toBeFalsy();
});
diff --git a/client/src/api/index.js b/client/src/api/index.js
index 0aab4ce5..d4d5d18c 100644
--- a/client/src/api/index.js
+++ b/client/src/api/index.js
@@ -147,6 +147,14 @@ export function allInvitations() {
return fetchJson(`/api/v1/invitations/all`, {}, {}, false);
}
+export function searchInvitations(roleId, pagination = {}) {
+ if (roleId) {
+ pagination.roleId = roleId;
+ }
+ const queryPart = paginationQueryParams(pagination, {})
+ return fetchJson(`/api/v1/invitations/search?${queryPart}`, {}, {}, false);
+}
+
//Manage
export function allProviders() {
return fetchJson("/api/v1/manage/providers");
diff --git a/client/src/components/Entities.jsx b/client/src/components/Entities.jsx
index 1858c2b6..39836760 100644
--- a/client/src/components/Entities.jsx
+++ b/client/src/components/Entities.jsx
@@ -206,7 +206,7 @@ export const Entities = ({
}
- {(!hasEntities && !initial && !customEmptySearch && !loading && !hideTitle) &&
+ {(!hasEntities && !initial && !customEmptySearch && !loading && !hideTitle && !busy) &&
{customNoEntities || I18n.t(`${modelName}.noEntities`)}
}
{
diff --git a/client/src/locale/en.js b/client/src/locale/en.js
index cee68ea7..f39f7765 100644
--- a/client/src/locale/en.js
+++ b/client/src/locale/en.js
@@ -227,10 +227,7 @@ const en = {
delete: "Remove"
},
invitations: {
- found: "{{count}} {{plural}} found",
- foundWithStatus: "{{count}} {{status}} {{plural}}",
- singleInvitation: "invitation",
- multipleInvitations: "invitations",
+ title: "Invitations",
searchPlaceHolder: "Search for invitation...",
noResults: "No invitation where found",
inviter: "Invited by",
@@ -254,7 +251,7 @@ const en = {
roles: "Roles",
inviterRoles: "Select the roles for the new invitation",
rolesPlaceHolder: "Choose one or more roles",
- expiryDate: "Invite valid till",
+ expiryDate: "Valid till",
acceptedAt: "Date accepted",
roleExpiryDate: "Role expiry date",
roleExpiryDateQuestion: "Set a custom role expiration period",
diff --git a/client/src/locale/nl.js b/client/src/locale/nl.js
index 5e0d1dff..670b9cb3 100644
--- a/client/src/locale/nl.js
+++ b/client/src/locale/nl.js
@@ -227,10 +227,7 @@ const nl = {
delete: "Verwijder"
},
invitations: {
- found: "{{count}} {{plural}} gevonden",
- foundWithStatus: "{{count}} {{status}} {{plural}}",
- singleInvitation: "uitnodiging",
- multipleInvitations: "uitnodigingen",
+ title: "Uitnodigingen",
searchPlaceHolder: "Zoek uitnodiging...",
noResults: "Geen uitnodigingen gevonden",
inviter: "Uitgenodigd door",
@@ -254,7 +251,7 @@ const nl = {
roles: "Rollen",
inviterRoles: "Selecteer de rollen voor de nieuwe uitnodiging",
rolesPlaceHolder: "Kies een of meer rollen",
- expiryDate: "Uitnodiging geldig tot",
+ expiryDate: "Geldig tot",
acceptedAt: "Datum geaccepteeerd",
roleExpiryDate: "Verloopdatum rol",
roleExpiryDateQuestion: "Zet een specifieke verloopdatum voor de rol",
diff --git a/client/src/pages/Application.js b/client/src/pages/Application.js
index d706f966..58519fc4 100644
--- a/client/src/pages/Application.js
+++ b/client/src/pages/Application.js
@@ -3,7 +3,7 @@ import {rolesPerApplicationManageId} from "../api";
import I18n from "../locale/I18n";
import "./Application.scss";
import {ReactComponent as WebsiteIcon} from "../icons/network-information.svg";
-import {Chip, Loader} from "@surfnet/sds";
+import {Chip} from "@surfnet/sds";
import {useNavigate, useParams} from "react-router-dom";
import {useAppStore} from "../stores/AppStore";
import {UnitHeader} from "../components/UnitHeader";
@@ -18,7 +18,7 @@ export const Application = () => {
const {manageId} = useParams();
const navigate = useNavigate();
const {user} = useAppStore(state => state);
- const [roles, setRoles] = useState({});
+ const [roles, setRoles] = useState([]);
const [application, setApplication] = useState({});
const [loading, setLoading] = useState(true);
@@ -40,7 +40,6 @@ export const Application = () => {
app.name = I18n.locale === "en" ? app["name:en"] || app["name:nl"] : app["name:nl"] || app["name:en"];
app.description = I18n.locale === "en" ? app["OrganizationName:en"] || app["OrganizationName:nl"] : app["OrganizationName:nl"] || app["OrganizationName:en"];
setApplication(app);
- setLoading(false);
const paths = [
{path: "/home", value: I18n.t("tabs.home")},
{path: "/home/applications", value: I18n.t("tabs.applications")},
@@ -49,17 +48,13 @@ export const Application = () => {
useAppStore.setState({
breadcrumbPath: paths
});
+ // setTimeout(() => setLoading(false), 40);
setLoading(false);
})
.catch(() => navigate("/"))
},
[user]); // eslint-disable-line react-hooks/exhaustive-deps
-
- if (loading) {
- return
- }
-
const openRole = (e, role) => {
const path = `/roles/${role.id}`
if (e.metaKey || e.ctrlKey) {
@@ -128,6 +123,8 @@ export const Application = () => {
modelName="applicationRoles"
title={I18n.t("applications.title", {nbr: roles.length})}
showNew={true}
+ busy={loading}
+ loading={false}
newLabel={I18n.t("applications.new")}
newEntityFunc={() => navigate("/role/new", {state: application.id})}
defaultSort="name"
diff --git a/client/src/pages/System.js b/client/src/pages/System.js
index 200b3022..076ce462 100644
--- a/client/src/pages/System.js
+++ b/client/src/pages/System.js
@@ -40,7 +40,7 @@ export const System = () => {
name="invitations"
label={I18n.t("tabs.invitations")}
>
-
+
,
{
results.forEach(user => user.roleSummaries
.sort((r1, r2) => (r1.endDate || Number.MAX_VALUE) - (r2.endDate || Number.MAX_VALUE)));
setUsers(results);
- //we need to avoid flickerings
- setTimeout(() => setSearching(false), 75);
- setTotalElements(page.totalElements);
setSearching(false);
+ setTotalElements(page.totalElements);
});
}, [paginationQueryParams]);
diff --git a/client/src/tabs/Applications.js b/client/src/tabs/Applications.js
index 3ad78414..9a02df76 100644
--- a/client/src/tabs/Applications.js
+++ b/client/src/tabs/Applications.js
@@ -29,8 +29,7 @@ const Applications = () => {
const mergedApps = mergeProvidersProvisioningsRoles(
res[0].providers, res[0].provisionings, res[1].content);
setApplications(mergedApps);
- //we need to avoid flickerings
- setTimeout(() => setSearching(false), 75);
+ setSearching(false);
})
},
[]) // eslint-disable-line react-hooks/exhaustive-deps
diff --git a/client/src/tabs/Invitations.js b/client/src/tabs/Invitations.js
index de0ca407..66f63452 100644
--- a/client/src/tabs/Invitations.js
+++ b/client/src/tabs/Invitations.js
@@ -1,102 +1,94 @@
-import React, {useEffect, useRef, useState} from "react";
+import React, {useEffect, useState} from "react";
import I18n from "../locale/I18n";
import "./Invitations.scss";
-import {Button, ButtonSize, ButtonType, Checkbox, Chip, Loader, Tooltip} from "@surfnet/sds";
+import {Button, ButtonSize, ButtonType, Checkbox, Chip, Tooltip} from "@surfnet/sds";
import {Entities} from "../components/Entities";
import "./Users.scss";
import {shortDateFromEpoch} from "../utils/Date";
import {chipTypeForUserRole, invitationExpiry} from "../utils/Authority";
import {useNavigate} from "react-router-dom";
-import {allInvitations, deleteInvitation, invitationsByRoleId, resendInvitation} from "../api";
+import {deleteInvitation, resendInvitation, searchInvitations} from "../api";
import ConfirmationDialog from "../components/ConfirmationDialog";
import {useAppStore} from "../stores/AppStore";
import {isEmpty, pseudoGuid} from "../utils/Utils";
import {allowedToDeleteInvitation, AUTHORITIES, INVITATION_STATUS, isUserAllowed} from "../utils/UserRole";
-import {UnitHeader} from "../components/UnitHeader";
-import Select from "react-select";
import {ReactComponent as TrashIcon} from "@surfnet/sds/icons/functional-icons/bin.svg";
import {ReactComponent as ResendIcon} from "@surfnet/sds/icons/functional-icons/go-to-other-website.svg";
+import {defaultPagination, pageCount} from "../utils/Pagination";
+import debounce from "lodash.debounce";
-const allValue = "all";
-const mineValue = "mine";
export const Invitations = ({
role,
- standAlone = false,
- systemView = false,
- history = false,
- pending = true
+ systemView = false
}) => {
const navigate = useNavigate();
const {user, setFlash} = useAppStore(state => state);
- const invitations = useRef();
+ const [invitations, setInvitations] = useState([]);
const [selectedInvitations, setSelectedInvitations] = useState({});
const [allSelected, setAllSelected] = useState(false);
- const [resultAfterSearch, setResultAfterSearch] = useState([])
- const [loading, setLoading] = useState(true);
+ const [paginationQueryParams, setPaginationQueryParams] = useState(defaultPagination("email"));
+ const [totalElements, setTotalElements] = useState(0);
+ const [searching, setSearching] = useState(true);
const [confirmation, setConfirmation] = useState({});
const [confirmationOpen, setConfirmationOpen] = useState(false);
- const [filterOptions, setFilterOptions] = useState([]);
- const [filterValue, setFilterValue] = useState(null);
useEffect(() => {
- const promise = systemView ? allInvitations() : invitationsByRoleId(role.id);
- if (history) {
- useAppStore.setState({
- breadcrumbPath: [
- {path: "/inviter", value: I18n.t("tabs.home")},
- {value: I18n.t("tabs.invitations")}
- ]
- });
- }
- promise.then(res => {
- res.forEach(invitation => {
- invitation.intendedRoles = invitation.roles
- .sort((r1, r2) => r1.role.name.localeCompare(r2.role.name))
- .map(role => role.role.name).join(", ");
- const now = new Date();
- invitation.status = new Date(invitation.expiryDate * 1000) < now ? INVITATION_STATUS.EXPIRED : invitation.status;
- });
- setSelectedInvitations(res
- .reduce((acc, invitation) => {
- acc[invitation.id] = {
- selected: false,
- ref: invitation,
- allowed: allowedToDeleteInvitation(user, invitation)
- };
- return acc;
- }, {}));
- invitations.current = res;
- const newFilterOptions = [{
- label: I18n.t("invitations.statuses.all", {nbr: res.length}),
- value: allValue
- }];
- const statusOptions = res.reduce((acc, invitation) => {
- const option = acc.find(opt => opt.status === invitation.status);
- if (option) {
- ++option.nbr;
- } else {
- acc.push({status: invitation.status, nbr: 1})
- }
- return acc;
- }, []).map(option => ({
- label: `${I18n.t("invitations.statuses." + option.status.toLowerCase())} (${option.nbr})`,
- value: option.status
- })).concat({
- label: `${I18n.t("invitations.statuses.mine")} (${res.filter(inv => inv.inviter.email === user.email).length})`,
- value: mineValue
- }).sort((o1, o2) => o1.label.localeCompare(o2.label));
-
- setFilterOptions(newFilterOptions.concat(statusOptions));
- setFilterValue(newFilterOptions[0]);
+ searchInvitations(systemView ? null : role.id, paginationQueryParams)
+ .then(page => {
+ const content = page.content;
+ content.forEach(invitation => {
+ invitation.intendedRoles = (invitation.roles || [])
+ .sort((r1, r2) => r1.name.localeCompare(r2.name))
+ .map(role => role.name).join(", ");
+ const now = new Date();
+ invitation.status = new Date(invitation.expiryDate * 1000) < now ? INVITATION_STATUS.EXPIRED : invitation.status;
+ //We don't get the invitation.user_roles.role.applicationUsages from the server anymore due to custom pagination queries
+ (invitation.roles || []).forEach(invitationRole => {
+ invitationRole.role = {
+ id: invitationRole.id,
+ applicationUsages: invitationRole.manageIdentifiers.map(mi => ({application: {manageId: mi}})),
+ user_id: invitationRole.user_id,
+ authority: invitationRole.intended_authority
+ }
+ });
- setResultAfterSearch(res);
- //we need to avoid flickerings
- setTimeout(() => setLoading(false), 75);
- })
+ });
+ setInvitations(content);
+ setSelectedInvitations(content
+ .reduce((acc, invitation) => {
+ acc[invitation.id] = {
+ selected: false,
+ ref: invitation,
+ allowed: allowedToDeleteInvitation(user, invitation)
+ };
+ return acc;
+ }, {}));
+ setAllSelected(false);
+ setTotalElements(page.totalElements);
+ setSearching(false);
+ })
},
- [invitations, user]) // eslint-disable-line react-hooks/exhaustive-deps
+ [user, paginationQueryParams]) // eslint-disable-line react-hooks/exhaustive-deps
+
+ const search = (query, sorted, reverse, page) => {
+ if (isEmpty(query) || query.trim().length > 2) {
+ delayedAutocomplete(query, sorted, reverse, page);
+ }
+ };
+
+ const delayedAutocomplete = debounce((query, sorted, reverse, page) => {
+ setSearching(true);
+ //this will trigger a new search
+ setPaginationQueryParams({
+ query: query,
+ pageNumber: page,
+ pageSize: pageCount,
+ sort: sorted,
+ sortDirection: reverse ? "DESC" : "ASC"
+ })
+ }, 375);
const onCheck = invitation => e => {
const checked = e.target.checked;
@@ -119,8 +111,8 @@ export const Invitations = ({
const invitationIdentifiers = () => {
return Object.entries(selectedInvitations)
.filter(entry => (entry[1].selected) && entry[1].allowed)
- .map(entry => parseInt(entry[0]))
- .filter(id => resultAfterSearch.some(res => res.id === id));
+ .map(entry => parseInt(entry[0]));
+
}
const showCheckAllHeader = () => {
@@ -144,8 +136,7 @@ export const Invitations = ({
.then(() => {
setConfirmationOpen(false);
setFlash(I18n.t("invitations.resendFlash"));
- const path = encodeURIComponent(window.location.pathname);
- navigate(`/refresh-route/${path}`, {replace: true});
+ setPaginationQueryParams({...paginationQueryParams});
})
}
};
@@ -183,8 +174,7 @@ export const Invitations = ({
.then(() => {
setConfirmationOpen(false);
setFlash(I18n.t("invitations.deleteFlash"));
- const path = encodeURIComponent(window.location.pathname);
- navigate(`/refresh-route/${path}`, {replace: true});
+ setPaginationQueryParams({...paginationQueryParams});
})
}
};
@@ -203,20 +193,11 @@ export const Invitations = ({
.then(() => {
setConfirmationOpen(false);
setFlash(I18n.t("invitations.deleteFlash"));
- const path = encodeURIComponent(window.location.pathname);
- navigate(`/refresh-route/${path}`, {replace: true});
+ setPaginationQueryParams({...paginationQueryParams});
})
}
};
- if (loading) {
- return
- }
-
- const searchCallback = afterSearch => {
- setResultAfterSearch(afterSearch);
- }
-
const actionButtons = () => {
if (isEmpty(invitationIdentifiers())) {
return null;
@@ -234,35 +215,19 @@ export const Invitations = ({
txt={I18n.t("invitations.delete")}/>
}/>
- {pending &&
-
- doResendInvitations(true)}
- size={ButtonSize.Small}
- type={ButtonType.Secondary}
- txt={I18n.t("invitations.resend")}/>
- }/>
-
}
- );
- }
- const filter = () => {
- return (
-
-
- );
+
+ doResendInvitations(true)}
+ size={ButtonSize.Small}
+ type={ButtonType.Secondary}
+ txt={I18n.t("invitations.resend")}/>
+ }/>
+
+ );
}
const actionIcons = invitation => {
@@ -271,14 +236,14 @@ export const Invitations = ({
}
return (
- {pending &&
doResendInvitationsFromActionLink(invitation, true)}>
+
doResendInvitationsFromActionLink(invitation, true)}>
}/>
-
}
+
doDeleteInvitationsFromActionLink(invitation, true)}>
{invitation.email}
},
{
- key: "intendedAuthority",
+ key: "intended_authority",
header: I18n.t("users.authority"),
- mapper: invitation =>
+ mapper: invitation =>
},
{
key: "intendedRoles",
+ nonSortable: true,
header: I18n.t("invitations.intendedRoles"),
mapper: invitation => invitation.intendedRoles
},
{
- key: "inviter__name",
+ key: "name",
header: I18n.t("invitations.inviter"),
mapper: invitation =>
- {invitation.inviter.name}
- {invitation.inviter.email}
+ {invitation.name}
+ {invitation.inviter_email}
},
{
- key: "createdAt",
+ key: "created_at",
header: I18n.t("invitations.createdAt"),
- mapper: invitation => shortDateFromEpoch(invitation.createdAt)
+ mapper: invitation => shortDateFromEpoch(invitation.created_at, false)
},
{
- key: pending ? "expiryDate" : "acceptedAt",
- header: I18n.t(pending ? "invitations.expiryDate" : "invitations.acceptedAt"),
- mapper: invitation => pending ? invitationExpiry(invitation) : shortDateFromEpoch(invitation.acceptedAt)
+ key: "expiry_date",
+ header: I18n.t("invitations.expiryDate"),
+ mapper: invitation => invitationExpiry(invitation)
},
{
key: "adminIcons",
@@ -357,21 +323,6 @@ export const Invitations = ({
header: "",
mapper: invitation => actionIcons(invitation)
}];
- const filteredInvitations = filterValue.value === allValue ? invitations.current :
- invitations.current.filter(invitation => invitation.status === filterValue.value ||
- (filterValue.value === mineValue && invitation.inviter.email === user.email)
- );
- const countInvitations = filteredInvitations.length;
- const hasEntities = countInvitations > 0;
- let title = " ";
-
- if (hasEntities) {
- title = I18n.t(`invitations.${standAlone ? "found" : "foundWithStatus"}`, {
- count: countInvitations,
- status: pending ? I18n.t("invitations.pending") : I18n.t("invitations.accepted").toLowerCase(),
- plural: I18n.t(`invitations.${countInvitations === 1 ? "singleInvitation" : "multipleInvitations"}`)
- })
- }
return (
{confirmationOpen && }
- {history && }
- navigate("/invitation/new", {state: role.id}) : null}
customNoEntities={I18n.t(`invitations.noResults`)}
- loading={loading}
+ loading={false}
onHover={true}
- hideTitle={loading}
- filters={filter(filterOptions, filterValue)}
actions={actionButtons()}
- searchCallback={searchCallback}
searchAttributes={["name", "email", "schacHomeOrganization", "inviter__email", "inviter__name"]}
- inputFocus={true}>
+ customSearch={search}
+ totalElements={totalElements}
+ inputFocus={!searching}
+ hideTitle={searching}
+ busy={searching}
+ >
)
diff --git a/client/src/tabs/Invitations.scss b/client/src/tabs/Invitations.scss
index 22fa88fd..20d9fc83 100644
--- a/client/src/tabs/Invitations.scss
+++ b/client/src/tabs/Invitations.scss
@@ -14,7 +14,7 @@
width: 20%;
}
- &.intendedAuthority {
+ &.intended_authority {
width: 15%;
text-align: center;
}
@@ -23,17 +23,18 @@
width: 15%;
}
- &.inviter__name {
- width: 20%;
+ &.name {
+ width: 26%;
}
- &.createdAt {
- width: 15%;
+ &.created_at {
+ width: 12%;
}
- &.expiryDate {
- width: 15%;
+ &.expiry_date {
+ width: 12%;
}
+
&.adminIcons {
width: 80px;
}
@@ -51,19 +52,7 @@
text-align: center;
}
- &.enforceEmailEquality {
- text-align: center;
- }
-
- &.eduIDOnly {
- text-align: center;
- }
-
- &.intendedAuthority {
- text-align: center;
- }
-
- &.status {
+ &.intended_authority {
text-align: center;
}
diff --git a/client/src/tabs/Roles.js b/client/src/tabs/Roles.js
index 1752aa47..75e23681 100644
--- a/client/src/tabs/Roles.js
+++ b/client/src/tabs/Roles.js
@@ -3,7 +3,7 @@ import {useAppStore} from "../stores/AppStore";
import React, {useEffect, useState} from "react";
import {Entities} from "../components/Entities";
import I18n from "../locale/I18n";
-import {Button, ButtonSize, Chip, Loader, Tooltip} from "@surfnet/sds";
+import {Button, ButtonSize, Chip, Tooltip} from "@surfnet/sds";
import {useNavigate} from "react-router-dom";
import {AUTHORITIES, highestAuthority, isUserAllowed, markAndFilterRoles} from "../utils/UserRole";
import {rolesByApplication} from "../api";
@@ -19,7 +19,6 @@ export const Roles = () => {
const {user, config} = useAppStore(state => state);
const navigate = useNavigate();
- const [loading, setLoading] = useState(false);
const [searching, setSearching] = useState(true);
const [roles, setRoles] = useState([]);
const [paginationQueryParams, setPaginationQueryParams] = useState(defaultPagination());
@@ -39,9 +38,7 @@ export const Roles = () => {
paginationQueryParams.sortDirection === "DESC");
setRoles(newRoles);
setTotalElements(page.totalElements);
- //we need to avoid flickerings
- setTimeout(() => setSearching(false), 75);
- setLoading(false);
+ setSearching(false);
})
} else {
const newRoles = markAndFilterRoles(
@@ -54,7 +51,6 @@ export const Roles = () => {
false);
setRoles(newRoles);
setSearching(false);
- setLoading(false);
}
}, [user, paginationQueryParams]);// eslint-disable-line react-hooks/exhaustive-deps
@@ -144,7 +140,8 @@ export const Roles = () => {
const authority = authorityForRole(user, role);
const label = authority ? I18n.t(`access.${authority}`) : I18n.t("roles.noMember");
return }
+ label={label}/>
+ }
},
{
key: "userRoleCount",
@@ -154,10 +151,6 @@ export const Roles = () => {
];
- if (loading) {
- return
- }
-
const isSuperUser = isUserAllowed(AUTHORITIES.SUPER_USER, user);
const isManager = isUserAllowed(AUTHORITIES.INSTITUTION_ADMIN, user);
const isInstitutionAdmin = highestAuthority(user) === AUTHORITIES.INSTITUTION_ADMIN;
diff --git a/client/src/tabs/UserRoles.js b/client/src/tabs/UserRoles.js
index b5afe604..af55a9ea 100644
--- a/client/src/tabs/UserRoles.js
+++ b/client/src/tabs/UserRoles.js
@@ -1,7 +1,7 @@
import React, {useEffect, useState} from "react";
import I18n from "../locale/I18n";
import "./UserRoles.scss";
-import {Button, ButtonSize, ButtonType, Checkbox, Chip, ChipType, Loader, Tooltip} from "@surfnet/sds";
+import {Button, ButtonSize, ButtonType, Checkbox, Chip, ChipType, Tooltip} from "@surfnet/sds";
import {Entities} from "../components/Entities";
import {ReactComponent as AlarmBell} from "../icons/alarm_bell.svg";
import "./Users.scss";
@@ -25,7 +25,7 @@ export const UserRoles = ({role, guests}) => {
const {user, setFlash, config} = useAppStore(state => state);
const [removeNotAllowed, setRemoveNotAllowed] = useState({});
- const [userRoles, setUserRoles] = useState({});
+ const [userRoles, setUserRoles] = useState([]);
const [selectedUserRoles, setSelectedUserRoles] = useState({});
const [allSelected, setAllSelected] = useState(false);
const [paginationQueryParams, setPaginationQueryParams] = useState(defaultPagination());
@@ -33,7 +33,6 @@ export const UserRoles = ({role, guests}) => {
const [confirmation, setConfirmation] = useState({});
const [confirmationOpen, setConfirmationOpen] = useState(false);
const [searching, setSearching] = useState(true);
- const [loading, setLoading] = useState(true);
useEffect(() => {
setRemoveNotAllowed(highestAuthority(user) === AUTHORITIES.INVITER && !guests);
@@ -54,8 +53,7 @@ export const UserRoles = ({role, guests}) => {
setAllSelected(false);
setTotalElements(page.totalElements);
//we need to avoid flickerings
- setTimeout(() => setSearching(false), 75);
- setLoading(false);
+ setSearching(false);
});
},
[guests, user, paginationQueryParams]);// eslint-disable-line react-hooks/exhaustive-deps
@@ -78,10 +76,6 @@ export const UserRoles = ({role, guests}) => {
})
}, 375);
- if (loading) {
- return
- }
-
const showCheckAllHeader = () => {
return Object.entries(selectedUserRoles)
.filter(entry => entry[1].allowed)
@@ -106,8 +100,7 @@ export const UserRoles = ({role, guests}) => {
.then(() => {
setConfirmationOpen(false);
setFlash(I18n.t("userRoles.updateFlash", {roleName: userRole.role.name}));
- const path = encodeURIComponent(window.location.pathname);
- navigate(`/refresh-route/${path}`, {replace: true});
+ setPaginationQueryParams({...paginationQueryParams});
})
}
};
@@ -195,7 +188,7 @@ export const UserRoles = ({role, guests}) => {
};
const handleError = e => {
- setLoading(false);
+ setSearching(false);
e.response.json().then(j => {
const reference = j.reference || 999;
setConfirmation({
diff --git a/client/src/tabs/UserRoles.scss b/client/src/tabs/UserRoles.scss
index 38d275dc..14450c1f 100644
--- a/client/src/tabs/UserRoles.scss
+++ b/client/src/tabs/UserRoles.scss
@@ -20,7 +20,7 @@
}
&.name {
- width: 22%;
+ width: 24%;
}
&.me {
@@ -28,7 +28,7 @@
}
&.schac_home_organization {
- width: 14%;
+ width: 16%;
}
&.authority {
@@ -42,11 +42,11 @@
}
&.endDate {
- width: 22%;
+ width: 20%;
}
&.createdAt {
- width: 14%;
+ width: 12%;
}
&.adminIcons {
diff --git a/client/src/tabs/Users.js b/client/src/tabs/Users.js
index 2748f454..10f8d0cf 100644
--- a/client/src/tabs/Users.js
+++ b/client/src/tabs/Users.js
@@ -32,8 +32,7 @@ export const Users = () => {
.then(page => {
setUsers(page.content);
setTotalElements(page.totalElements);
- //we need to avoid flickerings
- setTimeout(() => setSearching(false), 75);
+ setSearching(false);
});
},
[currentUser, paginationQueryParams]);// eslint-disable-line react-hooks/exhaustive-deps
diff --git a/client/src/utils/Authority.js b/client/src/utils/Authority.js
index 3f7dd19c..23361d4a 100644
--- a/client/src/utils/Authority.js
+++ b/client/src/utils/Authority.js
@@ -36,13 +36,13 @@ export const chipTypeForUserRole = authority => {
}
export const invitationExpiry = invitation => {
- const expired = new Date(invitation.expiryDate * 1000) < new Date();
+ const expired = new Date(invitation.expiry_date) < new Date();
if (expired) {
return
}
- return shortDateFromEpoch(invitation.expiryDate);
+ return shortDateFromEpoch(invitation.expiry_date, false);
}
export const authorityForRole = (user, role) => {
diff --git a/client/src/utils/Pagination.js b/client/src/utils/Pagination.js
index e1fab010..80d6dbbb 100644
--- a/client/src/utils/Pagination.js
+++ b/client/src/utils/Pagination.js
@@ -19,6 +19,9 @@ export const paginationQueryParams = (page, queryParams = {}) => {
if (!isEmpty(page.sortDirection)) {
queryParams.sortDirection = page.sortDirection;
}
+ if (!isEmpty(page.roleId)) {
+ queryParams.roleId = encodeURIComponent(page.roleId);
+ }
}
return Object.entries(queryParams).reduce((acc, entry) => {
acc += `${entry[0]}=${encodeURIComponent(entry[1])}&`
diff --git a/client/src/utils/UserRole.js b/client/src/utils/UserRole.js
index f181b1f0..be8b2438 100644
--- a/client/src/utils/UserRole.js
+++ b/client/src/utils/UserRole.js
@@ -87,7 +87,8 @@ export const allowedToDeleteInvitation = (user, invitation) => {
invitation.roles
.every(invitationRole => allowedToRenewUserRole(user, {
...invitationRole,
- authority: invitation.intendedAuthority
+ authority: invitation.intended_authority,
+ user_id: invitation.user_id
}))
}
@@ -98,6 +99,7 @@ export const allowedToRenewUserRole = (user, userRole, deleteAction = false, tar
if (deleteAction && (user.id === userRole.userInfo?.id || user.id === userRole.user_id)) {
return true;
}
+
const allowedByApplicationForInstitutionAdmin = user.institutionAdmin && (user.applications || [])
.some(application => roleIsConnectedToApp(userRole.role, application));
const allowedByApplicationForManager = isApplicationManagerForUserRole(user, userRole);
diff --git a/server/src/main/java/access/api/InvitationController.java b/server/src/main/java/access/api/InvitationController.java
index 9ce82a83..7953c53d 100644
--- a/server/src/main/java/access/api/InvitationController.java
+++ b/server/src/main/java/access/api/InvitationController.java
@@ -26,6 +26,10 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -36,15 +40,19 @@
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;
+import static java.util.Collections.emptyList;
@RestController
@RequestMapping(value = {
@@ -261,18 +269,15 @@ public ResponseEntity