Split inactive profile state
Replace the binary profile ACTIVE/INACTIVE model with PENDING, ACTIVE, and DEACTIVATED so invited-but-not-yet-activated members remain assignable to assets, data, and risks instead of being treated like deactivated users. Add activated_at/deactivated_at timestamps and Mark* lifecycle helpers, and update every transition (create, invite/re-invite, activation, archive, SCIM, SAML, sessions, compliance-portal grant) to the new states. Expose a multi-state states[] filter across coredata, GraphQL, MCP, and the console owner pickers, which now request ACTIVE and PENDING members. A migration renames the membership_state enum, classifies existing inactive profiles as PENDING from recent invitation activity, and backfills the new timestamp columns. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -1371,7 +1371,7 @@
|
||||
"columns": { "name": "Name", "status": "Status", "email": "Email", "role": "Role", "createdOn": "Created on" },
|
||||
"empty": "No people",
|
||||
"searchPlaceholder": "Search people...",
|
||||
"filters": { "allStatuses": "All statuses", "active": "Active", "inactive": "Inactive", "allRoles": "All roles", "allTypes": "All types" }
|
||||
"filters": { "allStatuses": "All statuses", "pending": "Pending", "active": "Active", "deactivated": "Deactivated", "allRoles": "All roles", "allTypes": "All types" }
|
||||
},
|
||||
"peopleListItem": {
|
||||
"messages": { "invitationSent": "Invitation sent successfully", "roleUpdated": "Role updated successfully", "archived": "Person archived successfully", "removed": "Person removed successfully" },
|
||||
|
||||
@@ -2458,8 +2458,9 @@
|
||||
"searchPlaceholder": "Rechercher des personnes...",
|
||||
"filters": {
|
||||
"allStatuses": "Tous les statuts",
|
||||
"pending": "En attente",
|
||||
"active": "Actif",
|
||||
"inactive": "Inactif",
|
||||
"deactivated": "Désactivé",
|
||||
"allRoles": "Tous les rôles",
|
||||
"allTypes": "Tous les types"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
import type { PeopleGraphQuery } from "#/__generated__/core/PeopleGraphQuery.graphql";
|
||||
import type { PeopleGraphQuery, ProfileFilter } from "#/__generated__/core/PeopleGraphQuery.graphql";
|
||||
|
||||
/* eslint-disable relay/unused-fields */
|
||||
|
||||
@@ -57,11 +57,15 @@ export function usePeople(
|
||||
organizationId: string,
|
||||
{ contractEnded }: { contractEnded?: boolean } = {},
|
||||
) {
|
||||
const filter: ProfileFilter = contractEnded !== undefined
|
||||
? { contractEnded, states: ["ACTIVE", "PENDING"] }
|
||||
: { states: ["ACTIVE", "PENDING"] };
|
||||
|
||||
const data = useLazyLoadQuery<PeopleGraphQuery>(
|
||||
peopleQuery,
|
||||
{
|
||||
organizationId: organizationId,
|
||||
filter: contractEnded !== undefined ? { contractEnded } : null,
|
||||
filter,
|
||||
},
|
||||
{ fetchPolicy: "network-only" },
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ export function PersonPage(props: { queryRef: PreloadedQuery<PersonPageQuery> })
|
||||
);
|
||||
};
|
||||
|
||||
const canArchive = person.canDelete && person.source !== "SCIM" && person.state !== "INACTIVE";
|
||||
const canArchive = person.canDelete && person.source !== "SCIM" && person.state !== "DEACTIVATED";
|
||||
const canRemove = person.canRemoveMember && person.source !== "SCIM";
|
||||
|
||||
return (
|
||||
|
||||
@@ -231,8 +231,9 @@ export function PeopleList(props: {
|
||||
onValueChange={handleStateFilterChange}
|
||||
>
|
||||
<Option value="ALL">{t("peopleList.filters.allStatuses")}</Option>
|
||||
<Option value="PENDING">{t("peopleList.filters.pending")}</Option>
|
||||
<Option value="ACTIVE">{t("peopleList.filters.active")}</Option>
|
||||
<Option value="INACTIVE">{t("peopleList.filters.inactive")}</Option>
|
||||
<Option value="DEACTIVATED">{t("peopleList.filters.deactivated")}</Option>
|
||||
</Select>
|
||||
<Select
|
||||
value={roleFilter ?? "ALL"}
|
||||
|
||||
@@ -145,10 +145,11 @@ export function PeopleListItem(props: {
|
||||
? availableRoles
|
||||
: [...availableRoles, profile.membership.role];
|
||||
|
||||
const isInactive = profile.state === "INACTIVE";
|
||||
const isActive = profile.state === "ACTIVE";
|
||||
const isInactive = !isActive;
|
||||
|
||||
const canSendActivationMail = isInactive && profile.source !== "SCIM" && profile.canInvite;
|
||||
const canArchive = profile.canDelete && profile.source !== "SCIM" && profile.state !== "INACTIVE";
|
||||
const canSendActivationMail = !isActive && profile.source !== "SCIM" && profile.canInvite;
|
||||
const canArchive = profile.canDelete && profile.source !== "SCIM" && profile.state !== "DEACTIVATED";
|
||||
const canRemove = profile.canRemoveMember && profile.source !== "SCIM";
|
||||
|
||||
const [inviteUser]
|
||||
@@ -262,7 +263,7 @@ export function PeopleListItem(props: {
|
||||
<span className="font-semibold">{profile.fullName}</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={profile.state === "INACTIVE" ? "neutral" : "success"}>{profile.state}</Badge>
|
||||
<Badge variant={isActive ? "success" : "neutral"}>{profile.state}</Badge>
|
||||
</Td>
|
||||
<Td className={clsx(
|
||||
isMutating && "opacity-60 pointer-events-none",
|
||||
|
||||
Reference in New Issue
Block a user