Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions packages/services/api/src/modules/organization/module.graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left out of public API until we solidify this pattern

): MemberConnection! @tag(name: "public")
invitations(
first: Int @tag(name: "public")
after: String @tag(name: "public")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
${
Expand All @@ -178,11 +188,12 @@ export class OrganizationMembers {
`
: sql``
}
${searching ? sql`AND to_tsvector("u"."display_name" || ' ' || "u"."email") @@ to_tsquery('simple', ${searchTerm + ':*'})` : sql``}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this happens after the pagination. So even though it isn't indexed and so it isnt very performant -- running on <= 50 elements is fine.

ORDER BY
"om"."organization_id" DESC
, "om"."created_at" DESC
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

necessary or the order doesn't match the cursor's WHERE clause. Basically, without this the last item in the array may have a cursor that is less than another element's based on:

AND (
                  "om"."created_at" < ${cursor.createdAt}
                  OR (
                    "om"."created_at" = ${cursor.createdAt}
                    AND "om"."user_id" < ${cursor.id}
                  )
                )

, "om"."user_id" DESC
, "om"."user_id" DESC
${first ? sql`LIMIT ${first + 1}` : sql``}
LIMIT ${first + 1}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This enforces the pagination. Whereas before it was optional. This is critical to avoid performance issues from the filter.

`;

const result = await this.pool.any<unknown>(query);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
Loading