From 14de714855522ec84c91da8defafd166968bc932 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu <heinrich@gitlab.com> Date: Fri, 30 Jun 2023 23:51:38 +0800 Subject: [PATCH] Add GraphQL project users autocomplete This is equivalent to the AutocompleteController#users endpoint --- .../graphql_shared/possible_types.json | 1 + .../projects/autocomplete_users_resolver.rb | 26 ++ app/graphql/types/project_type.rb | 5 + .../types/users/autocompleted_user_type.rb | 24 ++ doc/api/graphql/reference/index.md | 304 ++++++++++++++++++ spec/graphql/types/project_type_spec.rb | 2 +- .../users/autocompleted_user_type_spec.rb | 19 ++ .../project/autocomplete_users_spec.rb | 99 ++++++ 8 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 app/graphql/resolvers/projects/autocomplete_users_resolver.rb create mode 100644 app/graphql/types/users/autocompleted_user_type.rb create mode 100644 spec/graphql/types/users/autocompleted_user_type_spec.rb create mode 100644 spec/requests/api/graphql/project/autocomplete_users_spec.rb diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 7651bbba71c75..d60a83bc156dc 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -138,6 +138,7 @@ "WorkItem" ], "User": [ + "AutocompletedUser", "MergeRequestAssignee", "MergeRequestAuthor", "MergeRequestParticipant", diff --git a/app/graphql/resolvers/projects/autocomplete_users_resolver.rb b/app/graphql/resolvers/projects/autocomplete_users_resolver.rb new file mode 100644 index 0000000000000..73771fdc531eb --- /dev/null +++ b/app/graphql/resolvers/projects/autocomplete_users_resolver.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Resolvers + module Projects + class AutocompleteUsersResolver < BaseResolver + type [::Types::Users::AutocompletedUserType], null: true + + argument :search, GraphQL::Types::String, + required: false, + description: 'Query to search users by name, username, or public email.' + + alias_method :project, :object + + def resolve(search: nil) + ::Autocomplete::UsersFinder.new( + current_user: context[:current_user], + project: project, + group: nil, + params: { + search: search + } + ).execute + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 992663b4d9879..919d18e8aa72e 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -623,6 +623,11 @@ class ProjectType < BaseObject alpha: { milestone: '16.0' }, description: "Get tag names containing a given commit." + field :autocomplete_users, + null: true, + resolver: Resolvers::Projects::AutocompleteUsersResolver, + description: 'Search users for autocompletion' + def timelog_categories object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories) end diff --git a/app/graphql/types/users/autocompleted_user_type.rb b/app/graphql/types/users/autocompleted_user_type.rb new file mode 100644 index 0000000000000..8a70f398954cd --- /dev/null +++ b/app/graphql/types/users/autocompleted_user_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Users + class AutocompletedUserType < ::Types::UserType + graphql_name 'AutocompletedUser' + + authorize :read_user + + field :merge_request_interaction, Types::UserMergeRequestInteractionType, + null: true, + description: 'Merge request state related to the user.' do + argument :id, ::Types::GlobalIDType[::MergeRequest], required: true, + description: 'Global ID of the merge request.' + end + + def merge_request_interaction(id: nil) + Gitlab::Graphql::Lazy.with_value(GitlabSchema.object_from_id(id, expected_class: ::MergeRequest)) do |mr| + ::Users::MergeRequestInteraction.new(user: object.user, merge_request: mr) if mr + end + end + end + end +end diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3fff01abf3e89..98f9485e7ef5d 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -12445,6 +12445,297 @@ Represents a HTTP header key/value that belongs to an instance level audit strea | <a id="auditeventsstreaminginstanceheaderkey"></a>`key` | [`String!`](#string) | Key of the header. | | <a id="auditeventsstreaminginstanceheadervalue"></a>`value` | [`String!`](#string) | Value of the header. | +### `AutocompletedUser` + +Core represention of a GitLab user. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompleteduseravatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. | +| <a id="autocompleteduserbio"></a>`bio` | [`String`](#string) | Bio of the user. | +| <a id="autocompleteduserbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | +| <a id="autocompletedusercallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | +| <a id="autocompletedusercommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. | +| <a id="autocompletedusercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. | +| <a id="autocompleteduserdiscord"></a>`discord` | [`String`](#string) | Discord ID of the user. | +| <a id="autocompleteduseremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | +| <a id="autocompleteduseremails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) | +| <a id="autocompletedusergitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. | +| <a id="autocompletedusergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. | +| <a id="autocompletedusergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) | +| <a id="autocompleteduserid"></a>`id` | [`ID!`](#id) | ID of the user. | +| <a id="autocompleteduseride"></a>`ide` | [`Ide`](#ide) | IDE settings. | +| <a id="autocompleteduserjobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. | +| <a id="autocompleteduserlinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. | +| <a id="autocompleteduserlocation"></a>`location` | [`String`](#string) | Location of the user. | +| <a id="autocompletedusername"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. | +| <a id="autocompletedusernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. | +| <a id="autocompletedusernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) | +| <a id="autocompleteduserorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. | +| <a id="autocompleteduserpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. | +| <a id="autocompleteduserprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. | +| <a id="autocompleteduserprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) | +| <a id="autocompleteduserpronouns"></a>`pronouns` | [`String`](#string) | Pronouns of the user. | +| <a id="autocompleteduserpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. | +| <a id="autocompletedusersavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) | +| <a id="autocompleteduserstate"></a>`state` | [`UserState!`](#userstate) | State of the user. | +| <a id="autocompleteduserstatus"></a>`status` | [`UserStatus`](#userstatus) | User status. | +| <a id="autocompletedusertwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. | +| <a id="autocompleteduseruserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. | +| <a id="autocompleteduseruserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. | +| <a id="autocompleteduserusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. | +| <a id="autocompleteduserwebpath"></a>`webPath` | [`String!`](#string) | Web path of the user. | +| <a id="autocompleteduserweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the user. | + +#### Fields with arguments + +##### `AutocompletedUser.assignedMergeRequests` + +Merge requests assigned to the user. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompleteduserassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. | +| <a id="autocompleteduserassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. | +| <a id="autocompleteduserassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. | +| <a id="autocompleteduserassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. | +| <a id="autocompleteduserassignedmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | +| <a id="autocompleteduserassignedmergerequestsgroupid"></a>`groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | +| <a id="autocompleteduserassignedmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| <a id="autocompleteduserassignedmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| <a id="autocompleteduserassignedmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| <a id="autocompleteduserassignedmergerequestsmergedbefore"></a>`mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| <a id="autocompleteduserassignedmergerequestsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Title of the milestone. | +| <a id="autocompleteduserassignedmergerequestsnot"></a>`not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| <a id="autocompleteduserassignedmergerequestsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| <a id="autocompleteduserassignedmergerequestsprojectpath"></a>`projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| <a id="autocompleteduserassignedmergerequestsreviewerusername"></a>`reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| <a id="autocompleteduserassignedmergerequestssort"></a>`sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| <a id="autocompleteduserassignedmergerequestssourcebranches"></a>`sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| <a id="autocompleteduserassignedmergerequestsstate"></a>`state` | [`MergeRequestState`](#mergerequeststate) | Merge request state. If provided, all resolved merge requests will have this state. | +| <a id="autocompleteduserassignedmergerequeststargetbranches"></a>`targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | +| <a id="autocompleteduserassignedmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. | +| <a id="autocompleteduserassignedmergerequestsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. | + +##### `AutocompletedUser.authoredMergeRequests` + +Merge requests authored by the user. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompleteduserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. | +| <a id="autocompleteduserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. | +| <a id="autocompleteduserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. | +| <a id="autocompleteduserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. | +| <a id="autocompleteduserauthoredmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | +| <a id="autocompleteduserauthoredmergerequestsgroupid"></a>`groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | +| <a id="autocompleteduserauthoredmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| <a id="autocompleteduserauthoredmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| <a id="autocompleteduserauthoredmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| <a id="autocompleteduserauthoredmergerequestsmergedbefore"></a>`mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| <a id="autocompleteduserauthoredmergerequestsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Title of the milestone. | +| <a id="autocompleteduserauthoredmergerequestsnot"></a>`not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| <a id="autocompleteduserauthoredmergerequestsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| <a id="autocompleteduserauthoredmergerequestsprojectpath"></a>`projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| <a id="autocompleteduserauthoredmergerequestsreviewerusername"></a>`reviewerUsername` | [`String`](#string) | Username of the reviewer. | +| <a id="autocompleteduserauthoredmergerequestssort"></a>`sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| <a id="autocompleteduserauthoredmergerequestssourcebranches"></a>`sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| <a id="autocompleteduserauthoredmergerequestsstate"></a>`state` | [`MergeRequestState`](#mergerequeststate) | Merge request state. If provided, all resolved merge requests will have this state. | +| <a id="autocompleteduserauthoredmergerequeststargetbranches"></a>`targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | +| <a id="autocompleteduserauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. | +| <a id="autocompleteduserauthoredmergerequestsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. | + +##### `AutocompletedUser.groups` + +Groups where the user has access. + +Returns [`GroupConnection`](#groupconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompletedusergroupspermissionscope"></a>`permissionScope` | [`GroupPermission`](#grouppermission) | Filter by permissions the user has on groups. | +| <a id="autocompletedusergroupssearch"></a>`search` | [`String`](#string) | Search by group name or path. | + +##### `AutocompletedUser.mergeRequestInteraction` + +Merge request state related to the user. + +Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompletedusermergerequestinteractionid"></a>`id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. | + +##### `AutocompletedUser.reviewRequestedMergeRequests` + +Merge requests assigned to the user for review. + +Returns [`MergeRequestConnection`](#mergerequestconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompleteduserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. | +| <a id="autocompleteduserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. | +| <a id="autocompleteduserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. | +| <a id="autocompleteduserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. | +| <a id="autocompleteduserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. | +| <a id="autocompleteduserreviewrequestedmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | +| <a id="autocompleteduserreviewrequestedmergerequestsgroupid"></a>`groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. | +| <a id="autocompleteduserreviewrequestedmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| <a id="autocompleteduserreviewrequestedmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | +| <a id="autocompleteduserreviewrequestedmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | +| <a id="autocompleteduserreviewrequestedmergerequestsmergedbefore"></a>`mergedBefore` | [`Time`](#time) | Merge requests merged before this date. | +| <a id="autocompleteduserreviewrequestedmergerequestsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Title of the milestone. | +| <a id="autocompleteduserreviewrequestedmergerequestsnot"></a>`not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. | +| <a id="autocompleteduserreviewrequestedmergerequestsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. | +| <a id="autocompleteduserreviewrequestedmergerequestsprojectpath"></a>`projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. | +| <a id="autocompleteduserreviewrequestedmergerequestssort"></a>`sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. | +| <a id="autocompleteduserreviewrequestedmergerequestssourcebranches"></a>`sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. | +| <a id="autocompleteduserreviewrequestedmergerequestsstate"></a>`state` | [`MergeRequestState`](#mergerequeststate) | Merge request state. If provided, all resolved merge requests will have this state. | +| <a id="autocompleteduserreviewrequestedmergerequeststargetbranches"></a>`targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. | +| <a id="autocompleteduserreviewrequestedmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. | +| <a id="autocompleteduserreviewrequestedmergerequestsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. | + +##### `AutocompletedUser.savedReply` + +Saved reply authored by the user. Will not return saved reply if `saved_replies` feature flag is disabled. + +Returns [`SavedReply`](#savedreply). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompletedusersavedreplyid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | ID of a saved reply. | + +##### `AutocompletedUser.snippets` + +Snippets authored by the user. + +Returns [`SnippetConnection`](#snippetconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompletedusersnippetsids"></a>`ids` | [`[SnippetID!]`](#snippetid) | Array of global snippet IDs. For example, `gid://gitlab/ProjectSnippet/1`. | +| <a id="autocompletedusersnippetstype"></a>`type` | [`TypeEnum`](#typeenum) | Type of snippet. | +| <a id="autocompletedusersnippetsvisibility"></a>`visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | Visibility of the snippet. | + +##### `AutocompletedUser.starredProjects` + +Projects starred by the user. + +Returns [`ProjectConnection`](#projectconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompleteduserstarredprojectssearch"></a>`search` | [`String`](#string) | Search query. | + +##### `AutocompletedUser.timelogs` + +Time logged by the user. + +Returns [`TimelogConnection`](#timelogconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompletedusertimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. | +| <a id="autocompletedusertimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. | +| <a id="autocompletedusertimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. | +| <a id="autocompletedusertimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. | +| <a id="autocompletedusertimelogssort"></a>`sort` | [`TimelogSort`](#timelogsort) | List timelogs in a particular order. | +| <a id="autocompletedusertimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. | +| <a id="autocompletedusertimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. | +| <a id="autocompletedusertimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. | + +##### `AutocompletedUser.todos` + +To-do items of the user. + +Returns [`TodoConnection`](#todoconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompletedusertodosaction"></a>`action` | [`[TodoActionEnum!]`](#todoactionenum) | Action to be filtered. | +| <a id="autocompletedusertodosauthorid"></a>`authorId` | [`[ID!]`](#id) | ID of an author. | +| <a id="autocompletedusertodosgroupid"></a>`groupId` | [`[ID!]`](#id) | ID of a group. | +| <a id="autocompletedusertodosprojectid"></a>`projectId` | [`[ID!]`](#id) | ID of a project. | +| <a id="autocompletedusertodosstate"></a>`state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. | +| <a id="autocompletedusertodostype"></a>`type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. | + +##### `AutocompletedUser.workspaces` + +Workspaces owned by the current user. + +Returns [`WorkspaceConnection`](#workspaceconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="autocompleteduserworkspacesids"></a>`ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. | +| <a id="autocompleteduserworkspacesincludeactualstates"></a>`includeActualStates` | [`[String!]`](#string) | Includes all workspaces that match any of the actual states. | +| <a id="autocompleteduserworkspacesprojectids"></a>`projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project id. | + ### `AwardEmoji` An emoji awarded by a user. @@ -20320,6 +20611,18 @@ Returns [`[AlertManagementPayloadAlertField!]`](#alertmanagementpayloadalertfiel | ---- | ---- | ----------- | | <a id="projectalertmanagementpayloadfieldspayloadexample"></a>`payloadExample` | [`String!`](#string) | Sample payload for extracting alert fields for custom mappings. | +##### `Project.autocompleteUsers` + +Search users for autocompletion. + +Returns [`[AutocompletedUser!]`](#autocompleteduser). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="projectautocompleteuserssearch"></a>`search` | [`String`](#string) | Query to search users by name, username, or public email. | + ##### `Project.board` A single board of the project. @@ -28443,6 +28746,7 @@ Representation of a GitLab user. Implementations: +- [`AutocompletedUser`](#autocompleteduser) - [`MergeRequestAssignee`](#mergerequestassignee) - [`MergeRequestAuthor`](#mergerequestauthor) - [`MergeRequestParticipant`](#mergerequestparticipant) diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 262164a082159..db8d177e26c0f 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -38,7 +38,7 @@ ci_template timelogs merge_commit_template squash_commit_template work_item_types recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages - incident_management_timeline_event_tags visible_forks inherited_ci_variables + incident_management_timeline_event_tags visible_forks inherited_ci_variables autocomplete_users ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/users/autocompleted_user_type_spec.rb b/spec/graphql/types/users/autocompleted_user_type_spec.rb new file mode 100644 index 0000000000000..7b7af42976541 --- /dev/null +++ b/spec/graphql/types/users/autocompleted_user_type_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AutocompletedUser'], feature_category: :team_planning do + it { expect(described_class).to require_graphql_authorizations(:read_user) } + + describe '#merge_request_interaction' do + subject { described_class.fields['mergeRequestInteraction'] } + + it 'returns the correct type' do + is_expected.to have_graphql_type(Types::UserMergeRequestInteractionType) + end + + it 'has the correct arguments' do + expect(subject.arguments).to have_key('id') + end + end +end diff --git a/spec/requests/api/graphql/project/autocomplete_users_spec.rb b/spec/requests/api/graphql/project/autocomplete_users_spec.rb new file mode 100644 index 0000000000000..7c416465ed430 --- /dev/null +++ b/spec/requests/api/graphql/project/autocomplete_users_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'autocomplete users for a project', feature_category: :team_planning do + include GraphqlHelpers + + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, :public, group: group) } + + let_it_be(:direct_member) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:indirect_member) { create(:user).tap { |u| group.add_guest(u) } } + + let_it_be(:group_invited_to_project) do + create(:group).tap { |g| create(:project_group_link, project: project, group: g) } + end + + let_it_be(:member_from_project_share) { create(:user).tap { |u| group_invited_to_project.add_guest(u) } } + + let_it_be(:group_invited_to_parent_group) do + create(:group).tap { |g| create(:group_group_link, shared_group: group, shared_with_group: g) } + end + + let_it_be(:member_from_parent_group_share) { create(:user).tap { |u| group_invited_to_parent_group.add_guest(u) } } + + let_it_be(:sibling_project) { create(:project, :repository, :public, group: group) } + let_it_be(:sibling_member) { create(:user).tap { |u| sibling_project.add_guest(u) } } + + let(:params) { {} } + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('autocompleteUsers', params, 'id') + ) + end + + let(:response_user_ids) { graphql_data.dig('project', 'autocompleteUsers').pluck('id') } + + it 'returns members of the project' do + post_graphql(query, current_user: direct_member) + + expected_user_ids = [ + direct_member, + indirect_member, + member_from_project_share, + member_from_parent_group_share + ].map { |u| u.to_global_id.to_s } + + expect(response_user_ids).to match_array(expected_user_ids) + end + + context 'with search param' do + let(:params) { { search: indirect_member.username } } + + it 'only returns users matching the search query' do + post_graphql(query, current_user: direct_member) + + expect(response_user_ids).to contain_exactly(indirect_member.to_global_id.to_s) + end + end + + context 'with merge request interaction' do + let(:merge_request) { create(:merge_request, source_project: project) } + let(:fields) do + <<~FIELDS + id + mergeRequestInteraction(id: "#{merge_request.to_global_id}") { + canMerge + } + FIELDS + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('autocompleteUsers', params, fields) + ) + end + + it 'returns MR state related to the users' do + project.add_maintainer(direct_member) + + post_graphql(query, current_user: direct_member) + + expect(graphql_data.dig('project', 'autocompleteUsers')).to include( + a_hash_including( + 'id' => direct_member.to_global_id.to_s, + 'mergeRequestInteraction' => { 'canMerge' => true } + ), + a_hash_including( + 'id' => indirect_member.to_global_id.to_s, + 'mergeRequestInteraction' => { 'canMerge' => false } + ) + ) + end + end +end -- GitLab