From eecf8a1d97ab5c91dc07fd2d8e67c8044b1e613d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 28 Jul 2026 18:16:52 +0200 Subject: [PATCH] Make profile state filters multi-value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR review feedback on the profile-state split: SAML sign-in now activates a pending profile, deactivation counts owners against the profile's own organization to close a last-owner bypass, the migration leaves historical activated_at/deactivated_at NULL rather than fabricating timestamps, and pending members are no longer rendered with the deactivated (faded) styling. Drop the single-value state filter in favor of the multi-value states across the profile and signatory surfaces. Remove ProfileFilter.state (only states[] remains) and convert the signatures profileState filter to profileStates. Turn the console people filter, the CLI "user list --state" flag, and the n8n listUsers and getAllSignatures state inputs into multi-select controls, where an empty selection means all states. Signed-off-by: Émile Ré --- apps/console/src/_locales/en-US.json | 2 +- apps/console/src/_locales/fr-FR.json | 1 + .../src/pages/iam/enroll/EnrollDevicePage.tsx | 2 +- .../pages/iam/memberships/MembershipsPage.tsx | 2 +- .../_components/MembershipsDropdownMenu.tsx | 2 +- .../people/_components/PeopleList.tsx | 46 +++++++++++-------- .../people/_components/PeopleListItem.tsx | 2 +- .../documents/DocumentLayout.tsx | 8 ++-- .../_components/DocumentListItem.tsx | 4 +- .../_components/SignatureDocumentsDialog.tsx | 2 +- .../signatures/DocumentSignaturesPage.tsx | 2 +- .../_components/DocumentSignatureList.tsx | 4 +- .../document/getAllSignatures.operation.ts | 11 ++--- .../Probo/actions/user/listUsers.operation.ts | 15 +++--- pkg/cmd/user/list/list.go | 14 +++--- .../document_version_signature_filter.go | 12 ++--- pkg/coredata/membership_profile_filter.go | 5 -- pkg/coredata/migrations/20260728T143246Z.sql | 7 --- pkg/iam/organization_service.go | 2 +- pkg/iam/saml/service.go | 4 ++ .../api/connect/v1/graphql/profile.graphql | 1 - .../api/connect/v1/identity_resolvers.go | 4 -- .../api/connect/v1/organization_resolvers.go | 4 -- .../api/console/v1/document_resolvers.go | 8 ++-- .../api/console/v1/graphql/document.graphql | 2 +- .../console/v1/graphql/organization.graphql | 1 - .../api/console/v1/organization_resolvers.go | 4 -- pkg/server/api/mcp/v1/schema.resolvers.go | 12 ++--- pkg/server/api/mcp/v1/specification.yaml | 11 ++--- 29 files changed, 88 insertions(+), 106 deletions(-) diff --git a/apps/console/src/_locales/en-US.json b/apps/console/src/_locales/en-US.json index af4d85cd9..3c453d3fc 100644 --- a/apps/console/src/_locales/en-US.json +++ b/apps/console/src/_locales/en-US.json @@ -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", "pending": "Pending", "active": "Active", "deactivated": "Deactivated", "allRoles": "All roles", "allTypes": "All types" } + "filters": { "allStatuses": "All statuses", "status": "Status", "pending": "Pending", "active": "Active", "deactivated": "Deactivated", "allRoles": "All roles", "allTypes": "All types" } }, "peopleListItem": { "messages": { "invitationSent": "Invitation sent successfully", "roleUpdated": "Role updated successfully", "deactivated": "Person deactivated successfully", "removed": "Person removed successfully" }, diff --git a/apps/console/src/_locales/fr-FR.json b/apps/console/src/_locales/fr-FR.json index 512205353..c87da6a5c 100644 --- a/apps/console/src/_locales/fr-FR.json +++ b/apps/console/src/_locales/fr-FR.json @@ -2458,6 +2458,7 @@ "searchPlaceholder": "Rechercher des personnes...", "filters": { "allStatuses": "Tous les statuts", + "status": "Statut", "pending": "En attente", "active": "Actif", "deactivated": "Désactivé", diff --git a/apps/console/src/pages/iam/enroll/EnrollDevicePage.tsx b/apps/console/src/pages/iam/enroll/EnrollDevicePage.tsx index bc081b59a..8e3059f62 100644 --- a/apps/console/src/pages/iam/enroll/EnrollDevicePage.tsx +++ b/apps/console/src/pages/iam/enroll/EnrollDevicePage.tsx @@ -43,7 +43,7 @@ export const enrollDevicePageQuery = graphql` profiles( first: 1000 orderBy: { direction: ASC, field: ORGANIZATION_NAME } - filter: { state: ACTIVE } + filter: { states: [ACTIVE] } ) @required(action: THROW) { edges @required(action: THROW) { node @required(action: THROW) { diff --git a/apps/console/src/pages/iam/memberships/MembershipsPage.tsx b/apps/console/src/pages/iam/memberships/MembershipsPage.tsx index 1e14eae7c..4ea35f3a6 100644 --- a/apps/console/src/pages/iam/memberships/MembershipsPage.tsx +++ b/apps/console/src/pages/iam/memberships/MembershipsPage.tsx @@ -41,7 +41,7 @@ export const membershipsPageQuery = graphql` profiles( first: 1000 orderBy: { direction: ASC, field: ORGANIZATION_NAME } - filter: { state: ACTIVE } + filter: { states: [ACTIVE] } ) @connection(key: "MembershipsPage_profiles") @required(action: THROW) { diff --git a/apps/console/src/pages/iam/organizations/_components/MembershipsDropdownMenu.tsx b/apps/console/src/pages/iam/organizations/_components/MembershipsDropdownMenu.tsx index fd61aadf7..b90871091 100644 --- a/apps/console/src/pages/iam/organizations/_components/MembershipsDropdownMenu.tsx +++ b/apps/console/src/pages/iam/organizations/_components/MembershipsDropdownMenu.tsx @@ -34,7 +34,7 @@ export const membershipsDropdownMenuQuery = graphql` profiles( first: 1000 orderBy: { direction: ASC, field: ORGANIZATION_NAME } - filter: { state: ACTIVE } + filter: { states: [ACTIVE] } ) @required(action: THROW) { edges @required(action: THROW) { node @required(action: THROW) { diff --git a/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx b/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx index af0a9a189..659231a34 100644 --- a/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx +++ b/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx @@ -20,6 +20,7 @@ import { getAssignableRoles, getMembershipRoles, peopleRoles } from "@probo/helpers"; import { + Checkbox, IconMagnifyingGlass, Input, Option, @@ -89,11 +90,13 @@ const fragment = graphql` type PeopleFilter = { query: string | null; - state: ProfileState | null; + states: ProfileState[]; role: MembershipRole | null; kind: string | null; }; +const PROFILE_STATES: ProfileState[] = ["PENDING", "ACTIVE", "DEACTIVATED"]; + export function PeopleList(props: { fKey: PeopleListFragment$key; onConnectionIdChange: (connectionId: string) => void; @@ -105,7 +108,7 @@ export function PeopleList(props: { const canManageRoles = getAssignableRoles(role).length > 0; const [queryFilter, setQueryFilter] = useState(null); - const [stateFilter, setStateFilter] = useState(null); + const [statesFilter, setStatesFilter] = useState([]); const [roleFilter, setRoleFilter] = useState(null); const [kindFilter, setKindFilter] = useState(null); const [order, setOrder] = useState({ @@ -127,7 +130,7 @@ export function PeopleList(props: { const currentFilter = (overrides: Partial = {}): PeopleFilter => ({ query: queryFilter, - state: stateFilter, + states: statesFilter, role: roleFilter, kind: kindFilter, ...overrides, @@ -135,7 +138,7 @@ export function PeopleList(props: { const connectionFilter = (filter: PeopleFilter) => ({ query: filter.query, - state: filter.state, + states: filter.states.length > 0 ? filter.states : null, role: filter.role, kind: filter.kind, contractEnded: null, @@ -170,7 +173,7 @@ export function PeopleList(props: { }, filter: { query: newQuery, - state: stateFilter, + states: statesFilter.length > 0 ? statesFilter : null, role: roleFilter, kind: kindFilter, contractEnded: null, @@ -180,7 +183,7 @@ export function PeopleList(props: { ); }); }, - [peoplePagination, order, stateFilter, roleFilter, kindFilter], + [peoplePagination, order, statesFilter, roleFilter, kindFilter], ), SEARCH_DEBOUNCE_MS, ); @@ -190,10 +193,12 @@ export function PeopleList(props: { debouncedRefetchQuery(value); }; - const handleStateFilterChange = (value: string) => { - const newState = value === "ALL" ? null : (value as ProfileState); - setStateFilter(newState); - refetchPeople({ state: newState }); + const handleStateFilterToggle = (state: ProfileState) => { + const newStates = statesFilter.includes(state) + ? statesFilter.filter(s => s !== state) + : [...statesFilter, state]; + setStatesFilter(newStates); + refetchPeople({ states: newStates }); }; const handleRoleFilterChange = (value: string) => { @@ -226,15 +231,18 @@ export function PeopleList(props: { value={queryFilter ?? ""} onValueChange={handleQueryFilterChange} /> - +
+ {t("peopleList.filters.status")} + {PROFILE_STATES.map(state => ( + + ))} +