Add state to MCP profile and refactor contract filter

Add the profile state attribute (ACTIVE/INACTIVE) to the MCP
Profile schema so listUsers and getUser tools expose it, and
add a state filter to listUsers.

Rename excludeContractEnded to contractEnded across the entire
stack (MCP, GraphQL, CLI, frontend). The new boolean is two-way:
true returns only users with ended contracts, false returns only
users with active or no contract, and null returns all.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-24 11:45:52 +04:00
parent b7a28573f7
commit 272f63828f
16 changed files with 43 additions and 23 deletions

View File

@@ -65,7 +65,7 @@ function PeopleMultiSelectWithQuery<T extends FieldValues = FieldValues>(
) {
const { __ } = useTranslate();
const { name, organizationId, control, selectedPeople = [], placeholder } = props;
const people = usePeople(organizationId, { excludeContractEnded: true });
const people = usePeople(organizationId, { contractEnded: false });
const [isOpen, setIsOpen] = useState(false);
const allPeople = [...people];

View File

@@ -61,7 +61,7 @@ function PeopleSelectWithQuery<TFieldValues extends FieldValues = FieldValues>(
) {
const { __ } = useTranslate();
const { name, organizationId, control } = props;
const people = usePeople(organizationId, { excludeContractEnded: true });
const people = usePeople(organizationId, { contractEnded: false });
return (
<Controller

View File

@@ -31,7 +31,7 @@ export function PeopleCell(props: Props) {
query={peopleQuery}
variables={{
organizationId: props.organizationId,
filter: { excludeContractEnded: true },
filter: { contractEnded: false },
}}
items={data =>
data.organization?.profiles?.edges.map(edge => edge.node) ?? []}

View File

@@ -49,13 +49,13 @@ export const peopleQuery = graphql`
*/
export function usePeople(
organizationId: string,
{ excludeContractEnded }: { excludeContractEnded?: boolean } = {},
{ contractEnded }: { contractEnded?: boolean } = {},
) {
const data = useLazyLoadQuery<PeopleGraphQuery>(
peopleQuery,
{
organizationId: organizationId,
filter: excludeContractEnded ? { excludeContractEnded: true } : null,
filter: contractEnded !== undefined ? { contractEnded } : null,
},
{ fetchPolicy: "network-only" },
);

View File

@@ -196,7 +196,7 @@ function PeopleList({
signatureDocumentsDialogPeopleQuery,
{
organizationId,
filter: { excludeContractEnded: true },
filter: { contractEnded: false },
},
);
const {

View File

@@ -27,7 +27,7 @@ export const documentSignaturesPageQuery = graphql`
query DocumentSignaturesPageQuery($documentId: ID! $organizationId: ID! $versionId: ID! $versionSpecified: Boolean!) {
organization: node(id: $organizationId) {
__typename
...DocumentSignatureList_peopleFragment @arguments(filter: { excludeContractEnded: true })
...DocumentSignatureList_peopleFragment @arguments(filter: { contractEnded: false })
}
# We use this on /documents/:documentId
document: node(id: $documentId) @skip(if: $versionSpecified) {

View File

@@ -550,7 +550,7 @@ function OwnerFilterSelect({
onChange,
}: OwnerFilterSelectProps) {
const { __ } = useTranslate();
const people = usePeople(organizationId, { excludeContractEnded: true });
const people = usePeople(organizationId, { contractEnded: false });
return (
<Select value={value ?? "ALL"} onValueChange={onChange}>

View File

@@ -126,7 +126,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
if flagActive {
variables["filter"] = map[string]any{
"excludeContractEnded": true,
"contractEnded": false,
}
}
@@ -210,7 +210,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().IntVarP(&flagLimit, "limit", "L", 30, "Maximum number of users to list")
cmd.Flags().StringVar(&flagOrder, "order-by", "", "Order by field (FULL_NAME, CREATED_AT, KIND)")
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
cmd.Flags().BoolVar(&flagActive, "active", false, "Exclude users whose contract has ended")
cmd.Flags().BoolVar(&flagActive, "active", false, "Show only users with active or no contract")
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd

View File

@@ -25,7 +25,7 @@ type (
MembershipProfileFilter struct {
withMembership *bool
withTrustCenterAccess *bool
excludeContractEnded *bool
contractEnded *bool
currentDate time.Time
email *mail.Addr
userName *string
@@ -35,10 +35,10 @@ type (
}
)
func NewMembershipProfileFilter(excludeContractEnded *bool) *MembershipProfileFilter {
func NewMembershipProfileFilter(contractEnded *bool) *MembershipProfileFilter {
return &MembershipProfileFilter{
excludeContractEnded: excludeContractEnded,
currentDate: time.Now(),
contractEnded: contractEnded,
currentDate: time.Now(),
}
}
@@ -96,7 +96,7 @@ func (f *MembershipProfileFilter) SQLArguments() pgx.StrictNamedArgs {
"filter_external_id": f.externalID,
"with_membership": f.withMembership,
"with_trust_center_access": f.withTrustCenterAccess,
"exclude_contract_ended": f.excludeContractEnded,
"contract_ended": f.contractEnded,
"current_date": f.currentDate,
"filter_state": f.state,
"filter_source": f.source,
@@ -132,7 +132,9 @@ AND (
)
AND (
CASE
WHEN @exclude_contract_ended::boolean IS NOT NULL AND @exclude_contract_ended::boolean = true THEN
WHEN @contract_ended::boolean IS NOT NULL AND @contract_ended::boolean = true THEN
(p.contract_end_date IS NOT NULL AND p.contract_end_date < @current_date::date)
WHEN @contract_ended::boolean IS NOT NULL AND @contract_ended::boolean = false THEN
(p.contract_end_date IS NULL OR p.contract_end_date >= @current_date::date)
ELSE TRUE
END

View File

@@ -65,7 +65,7 @@ enum ProfileOrderField
}
input ProfileFilter {
excludeContractEnded: Boolean
contractEnded: Boolean
state: ProfileState
}

View File

@@ -30,7 +30,7 @@ func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, fi
filters := coredata.NewMembershipProfileFilter(nil).WithMembership()
if filter != nil {
filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership()
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
if filter.State != nil {
filters.WithState(*filter.State)
}

View File

@@ -52,7 +52,7 @@ input ProfileOrder
}
input ProfileFilter {
excludeContractEnded: Boolean
contractEnded: Boolean
}
type ProfileConnection

View File

@@ -123,7 +123,7 @@ func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organiza
filters := coredata.NewMembershipProfileFilter(nil).WithMembership()
if filter != nil {
filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership()
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
}
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{

View File

@@ -2491,7 +2491,10 @@ func (r *Resolver) ListUsersTool(ctx context.Context, req *mcp.CallToolRequest,
filter := coredata.NewMembershipProfileFilter(nil).WithMembership()
if input.Filter != nil {
filter = coredata.NewMembershipProfileFilter(input.Filter.ExcludeContractEnded).WithMembership()
filter = coredata.NewMembershipProfileFilter(input.Filter.ContractEnded).WithMembership()
if input.Filter.State != nil {
filter.WithState(*input.Filter.State)
}
}
pageResult, err := r.iamSvc.OrganizationService.ListProfiles(ctx, input.OrganizationID, cursor, filter)

View File

@@ -180,6 +180,13 @@ components:
- AUDITOR
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.MembershipRole
ProfileState:
type: string
enum:
- ACTIVE
- INACTIVE
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ProfileState
Profile:
type: object
required:
@@ -189,6 +196,7 @@ components:
- email_address
- additional_email_addresses
- kind
- state
- created_at
- updated_at
properties:
@@ -214,6 +222,9 @@ components:
- string
- "null"
description: Profile kind
state:
$ref: "#/components/schemas/ProfileState"
description: Profile state (ACTIVE or INACTIVE)
position:
type:
- string
@@ -260,9 +271,12 @@ components:
filter:
type: object
properties:
exclude_contract_ended:
contract_ended:
type: boolean
description: Exclude users with ended contracts
description: Filter by contract status. True returns only users with ended contracts, false returns only users with active or no contract.
state:
$ref: "#/components/schemas/ProfileState"
description: Filter by profile state (ACTIVE or INACTIVE)
ListUsersOutput:
type: object

View File

@@ -24,6 +24,7 @@ func NewProfile(p *coredata.MembershipProfile) *Profile {
EmailAddress: p.EmailAddress,
AdditionalEmailAddresses: p.AdditionalEmailAddresses,
Kind: p.Kind,
State: p.State,
Position: p.Position,
ContractStartDate: p.ContractStartDate,
ContractEndDate: p.ContractEndDate,