diff --git a/packages/services/api/src/modules/organization/module.graphql.ts b/packages/services/api/src/modules/organization/module.graphql.ts index 208d749bacf..3bf9415d24c 100644 --- a/packages/services/api/src/modules/organization/module.graphql.ts +++ b/packages/services/api/src/modules/organization/module.graphql.ts @@ -868,6 +868,14 @@ export default gql` appDeployments: [String!] } + input MembersFilter { + """ + Part of a user's email or username that is used to filter the list of + members. + """ + searchTerm: String + } + type Organization { """ Unique UUID of the organization @@ -881,8 +889,11 @@ export default gql` name: String! @deprecated(reason: "Use the 'slug' field instead.") owner: Member! @tag(name: "public") me: Member! - members(first: Int @tag(name: "public"), after: String @tag(name: "public")): MemberConnection! - @tag(name: "public") + members( + first: Int @tag(name: "public") + after: String @tag(name: "public") + filters: MembersFilter + ): MemberConnection! @tag(name: "public") invitations( first: Int @tag(name: "public") after: String @tag(name: "public") diff --git a/packages/services/api/src/modules/organization/providers/organization-manager.ts b/packages/services/api/src/modules/organization/providers/organization-manager.ts index 6fe4226b7f0..504b67ac6bf 100644 --- a/packages/services/api/src/modules/organization/providers/organization-manager.ts +++ b/packages/services/api/src/modules/organization/providers/organization-manager.ts @@ -1221,7 +1221,7 @@ export class OrganizationManager { async getPaginatedOrganizationMembersForOrganization( organization: Organization, - args: { first: number | null; after: string | null }, + args: { first: number | null; after: string | null; searchTerm?: string | null }, ) { await this.session.assertPerformAction({ action: 'member:describe', diff --git a/packages/services/api/src/modules/organization/providers/organization-members.ts b/packages/services/api/src/modules/organization/providers/organization-members.ts index abd3de1b17f..34eb22de6c5 100644 --- a/packages/services/api/src/modules/organization/providers/organization-members.ts +++ b/packages/services/api/src/modules/organization/providers/organization-members.ts @@ -148,21 +148,31 @@ export class OrganizationMembers { async getPaginatedOrganizationMembersForOrganization( organization: Organization, - args: { first: number | null; after: string | null }, + args: { first: number | null; after: string | null; searchTerm?: string | null }, ) { this.logger.debug( 'Find paginated organization members for organization. (organizationId=%s)', organization.id, ); - const first = args.first; + const first = args.first ? Math.min(args.first, 50) : 50; const cursor = args.after ? decodeCreatedAtAndUUIDIdBasedCursor(args.after) : null; + const searchTerm = args.searchTerm ?? ''; + const searching = searchTerm.length > 0; const query = sql` SELECT ${organizationMemberFields(sql`"om"`)} FROM "organization_member" AS "om" + ${ + searching + ? sql` + JOIN "users" as "u" + ON "om"."user_id" = "u"."id" + ` + : sql`` + } WHERE "om"."organization_id" = ${organization.id} ${ @@ -178,11 +188,12 @@ export class OrganizationMembers { ` : sql`` } + ${searching ? sql`AND to_tsvector("u"."display_name" || ' ' || "u"."email") @@ to_tsquery('simple', ${searchTerm + ':*'})` : sql``} ORDER BY "om"."organization_id" DESC + , "om"."created_at" DESC , "om"."user_id" DESC - , "om"."user_id" DESC - ${first ? sql`LIMIT ${first + 1}` : sql``} + LIMIT ${first + 1} `; const result = await this.pool.any(query); diff --git a/packages/services/api/src/modules/organization/resolvers/Organization.ts b/packages/services/api/src/modules/organization/resolvers/Organization.ts index ed85c305022..0c1e8c36161 100644 --- a/packages/services/api/src/modules/organization/resolvers/Organization.ts +++ b/packages/services/api/src/modules/organization/resolvers/Organization.ts @@ -67,6 +67,7 @@ export const Organization: Pick< .getPaginatedOrganizationMembersForOrganization(organization, { first: args.first ?? null, after: args.after ?? null, + searchTerm: args.filters?.searchTerm, }); }, invitations: async (organization, args, { injector }) => {