gitlab-org--gitlab-foss/lib/api/issues.rb
Alexandru Croitor 09163e423a Expose merge requests count based on user access
Count issues related merge requests based on user access level. And
issue can have related MRs from projects where user does not have
access so the number of related merge requests should be adjusted
based on user's ability to access the related MRs.

https://gitlab.com/gitlab-org/gitlab-ce/issues/59581
2019-06-18 12:46:46 +03:00

386 lines
16 KiB
Ruby

# frozen_string_literal: true
module API
class Issues < Grape::API
include PaginationParams
helpers Helpers::IssuesHelpers
helpers ::Gitlab::IssuableMetadata
before { authenticate_non_get! }
helpers do
params :issues_stats_params do
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
optional :milestone, type: String, desc: 'Return issues for a specific milestone'
optional :iids, type: Array[Integer], desc: 'The IID array of issues'
optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
optional :author_username, type: String, desc: 'Return issues which are authored by the user with the given username'
mutually_exclusive :author_id, :author_username
optional :assignee_id, types: [Integer, String], integer_none_any: true,
desc: 'Return issues which are assigned to the user with the given ID'
optional :assignee_username, type: Array[String], check_assignees_count: true,
coerce_with: Validations::CheckAssigneesCount.coerce,
desc: 'Return issues which are assigned to the user with the given username'
mutually_exclusive :assignee_id, :assignee_username
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
use :optional_issues_params_ee
end
params :issues_params do
optional :with_labels_details, type: Boolean, desc: 'Return more label data than just lable title', default: false
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
use :issues_stats_params
use :pagination
end
params :issue_params do
optional :description, type: String, desc: 'The description of an issue'
optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
optional :assignee_id, type: Integer, desc: '[Deprecated] The ID of a user to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
use :optional_issue_params_ee
end
end
desc "Get currently authenticated user's issues statistics"
params do
use :issues_stats_params
optional :scope, type: String, values: %w[created_by_me assigned_to_me all], default: 'created_by_me',
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
end
get '/issues_statistics' do
authenticate! unless params[:scope] == 'all'
present issues_statistics, with: Grape::Presenters::Presenter
end
resource :issues do
desc "Get currently authenticated user's issues" do
success Entities::Issue
end
params do
use :issues_params
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me',
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
end
get do
authenticate! unless params[:scope] == 'all'
issues = paginate(find_issues)
options = {
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
}
present issues, options
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of group issues' do
success Entities::Issue
end
params do
use :issues_params
end
get ":id/issues" do
group = find_group!(params[:id])
issues = paginate(find_issues(group_id: group.id, include_subgroups: true))
options = {
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
}
present issues, options
end
desc 'Get statistics for the list of group issues'
params do
use :issues_stats_params
end
get ":id/issues_statistics" do
group = find_group!(params[:id])
present issues_statistics(group_id: group.id, include_subgroups: true), with: Grape::Presenters::Presenter
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
success Entities::Issue
end
params do
use :issues_params
end
get ":id/issues" do
project = find_project!(params[:id])
issues = paginate(find_issues(project_id: project.id))
options = {
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
project: user_project,
issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
}
present issues, options
end
desc 'Get statistics for the list of project issues'
params do
use :issues_stats_params
end
get ":id/issues_statistics" do
project = find_project!(params[:id])
present issues_statistics(project_id: project.id), with: Grape::Presenters::Presenter
end
desc 'Get a single project issue' do
success Entities::Issue
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
get ":id/issues/:issue_iid", as: :api_v4_project_issue do
issue = find_project_issue(params[:issue_iid])
present issue, with: Entities::Issue, current_user: current_user, project: user_project
end
desc 'Create a new project issue' do
success Entities::Issue
end
params do
requires :title, type: String, desc: 'The title of an issue'
optional :created_at, type: DateTime,
desc: 'Date time when the issue was created. Available only for admins and project owners.'
optional :merge_request_to_resolve_discussions_of, type: Integer,
desc: 'The IID of a merge request for which to resolve discussions'
optional :discussion_to_resolve, type: String,
desc: 'The ID of a discussion to resolve, also pass `merge_request_to_resolve_discussions_of`'
optional :iid, type: Integer,
desc: 'The internal ID of a project issue. Available only for admins and project owners.'
use :issue_params
end
post ':id/issues' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42320')
authorize! :create_issue, user_project
params.delete(:created_at) unless current_user.can?(:set_issue_created_at, user_project)
params.delete(:iid) unless current_user.can?(:set_issue_iid, user_project)
issue_params = declared_params(include_missing: false)
issue_params[:system_note_timestamp] = params[:created_at]
issue_params = convert_parameters_from_legacy_format(issue_params)
issue = ::Issues::CreateService.new(user_project,
current_user,
issue_params.merge(request: request, api: true)).execute
if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400)
end
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
else
render_validation_error!(issue)
end
end
desc 'Update an existing issue' do
success Entities::Issue
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
optional :title, type: String, desc: 'The title of an issue'
optional :updated_at, type: DateTime,
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
use :issue_params
at_least_one_of(*Helpers::IssuesHelpers.update_params_at_least_one_of)
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/issues/:issue_iid' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42322')
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
# Setting updated_at is allowed only for admins and owners
params.delete(:updated_at) unless current_user.can?(:set_issue_updated_at, user_project)
issue.system_note_timestamp = params[:updated_at]
update_params = declared_params(include_missing: false).merge(request: request, api: true)
update_params = convert_parameters_from_legacy_format(update_params)
issue = ::Issues::UpdateService.new(user_project,
current_user,
update_params).execute(issue)
render_spam_error! if issue.spam?
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
else
render_validation_error!(issue)
end
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Move an existing issue' do
success Entities::Issue
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/issues/:issue_iid/move' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42323')
issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
new_project = Project.find_by(id: params[:to_project_id])
not_found!('Project') unless new_project
begin
issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
present issue, with: Entities::Issue, current_user: current_user, project: user_project
rescue ::Issues::MoveService::MoveError => error
render_api_error!(error.message, 400)
end
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Delete a project issue'
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ":id/issues/:issue_iid" do
issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
destroy_conditionally!(issue) do |issue|
Issuable::DestroyService.new(user_project, current_user).execute(issue)
end
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'List merge requests that are related to the issue' do
success Entities::MergeRequestBasic
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
get ':id/issues/:issue_iid/related_merge_requests' do
issue = find_project_issue(params[:issue_iid])
merge_requests = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user)
.execute(issue)
.first
present paginate(::Kaminari.paginate_array(merge_requests)),
with: Entities::MergeRequest,
current_user: current_user,
project: user_project
end
desc 'List merge requests closing issue' do
success Entities::MergeRequestBasic
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/issues/:issue_iid/closed_by' do
issue = find_project_issue(params[:issue_iid])
merge_request_ids = MergeRequestsClosingIssues.where(issue_id: issue).select(:merge_request_id)
merge_requests = MergeRequestsFinder.new(current_user, project_id: user_project.id).execute.where(id: merge_request_ids)
present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'List participants for an issue' do
success Entities::UserBasic
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
get ':id/issues/:issue_iid/participants' do
issue = find_project_issue(params[:issue_iid])
participants = ::Kaminari.paginate_array(issue.participants)
present paginate(participants), with: Entities::UserBasic, current_user: current_user, project: user_project
end
desc 'Get the user agent details for an issue' do
success Entities::UserAgentDetail
end
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
get ":id/issues/:issue_iid/user_agent_detail" do
authenticated_as_admin!
issue = find_project_issue(params[:issue_iid])
break not_found!('UserAgentDetail') unless issue.user_agent_detail
present issue.user_agent_detail, with: Entities::UserAgentDetail
end
end
end
end