issues.rb 10.2 KB
Newer Older
1
module API
Nihad Abbasov's avatar
Nihad Abbasov committed
2
  class Issues < Grape::API
Robert Schilling's avatar
Robert Schilling committed
3 4
    include PaginationParams

Nihad Abbasov's avatar
Nihad Abbasov committed
5 6
    before { authenticate! }

jubianchi's avatar
jubianchi committed
7
    helpers do
8 9 10 11 12
      def find_issues(args = {})
        args = params.merge(args)

        args.delete(:id)
        args[:milestone_title] = args.delete(:milestone)
13
        args[:label_name] = args.delete(:labels)
14

15
        issues = IssuesFinder.new(current_user, args).execute
16 17

        issues.reorder(args[:order_by] => args[:sort])
18 19
      end

Robert Schilling's avatar
Robert Schilling committed
20 21
      params :issues_params do
        optional :labels, type: String, desc: 'Comma-separated list of label names'
22
        optional :milestone, type: String, desc: 'Milestone title'
Robert Schilling's avatar
Robert Schilling committed
23 24 25 26
        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.'
27
        optional :milestone, type: String, desc: 'Return issues for a specific milestone'
28
        optional :iids, type: Array[Integer], desc: 'The IID array of issues'
29
        optional :search, type: String, desc: 'Search issues for text present in the title or description'
30 31
        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'
Robert Schilling's avatar
Robert Schilling committed
32 33
        use :pagination
      end
34

35
      params :issue_params_ce do
Robert Schilling's avatar
Robert Schilling committed
36
        optional :description, type: String, desc: 'The description of an issue'
37 38
        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'
Robert Schilling's avatar
Robert Schilling committed
39 40
        optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
        optional :labels, type: String, desc: 'Comma-separated list of label names'
41
        optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
Robert Schilling's avatar
Robert Schilling committed
42
        optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
43
      end
44 45 46 47

      params :issue_params do
        use :issue_params_ce
      end
jubianchi's avatar
jubianchi committed
48 49
    end

Nihad Abbasov's avatar
Nihad Abbasov committed
50
    resource :issues do
Robert Schilling's avatar
Robert Schilling committed
51
      desc "Get currently authenticated user's issues" do
52
        success Entities::IssueBasic
Robert Schilling's avatar
Robert Schilling committed
53 54 55 56 57 58
      end
      params do
        optional :state, type: String, values: %w[opened closed all], default: 'all',
                         desc: 'Return opened, closed, or all issues'
        use :issues_params
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
59
      get do
60
        issues = find_issues(scope: 'authored')
Sean McGivern's avatar
Sean McGivern committed
61

62
        present paginate(issues), with: Entities::IssueBasic, current_user: current_user
Nihad Abbasov's avatar
Nihad Abbasov committed
63 64 65
      end
    end

Robert Schilling's avatar
Robert Schilling committed
66 67 68
    params do
      requires :id, type: String, desc: 'The ID of a group'
    end
69
    resource :groups, requirements: { id: %r{[^/]+} } do
Robert Schilling's avatar
Robert Schilling committed
70
      desc 'Get a list of group issues' do
71
        success Entities::IssueBasic
Robert Schilling's avatar
Robert Schilling committed
72 73
      end
      params do
74
        optional :state, type: String, values: %w[opened closed all], default: 'all',
Robert Schilling's avatar
Robert Schilling committed
75 76 77
                         desc: 'Return opened, closed, or all issues'
        use :issues_params
      end
78
      get ":id/issues" do
79
        group = find_group!(params[:id])
80

81
        issues = find_issues(group_id: group.id)
Sean McGivern's avatar
Sean McGivern committed
82

83
        present paginate(issues), with: Entities::IssueBasic, current_user: current_user
84 85 86
      end
    end

87 88 89
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
90
    resource :projects, requirements: { id: %r{[^/]+} } do
91 92
      include TimeTrackingEndpoints

Robert Schilling's avatar
Robert Schilling committed
93
      desc 'Get a list of project issues' do
94
        success Entities::IssueBasic
Robert Schilling's avatar
Robert Schilling committed
95 96 97 98 99 100
      end
      params do
        optional :state, type: String, values: %w[opened closed all], default: 'all',
                         desc: 'Return opened, closed, or all issues'
        use :issues_params
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
101
      get ":id/issues" do
102
        project = find_project!(params[:id])
103

104
        issues = find_issues(project_id: project.id)
105

106
        present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
Nihad Abbasov's avatar
Nihad Abbasov committed
107 108
      end

Robert Schilling's avatar
Robert Schilling committed
109 110 111 112
      desc 'Get a single project issue' do
        success Entities::Issue
      end
      params do
113
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
114
      end
115
      get ":id/issues/:issue_iid", as: :api_v4_project_issue do
116
        issue = find_project_issue(params[:issue_iid])
Robert Schilling's avatar
Robert Schilling committed
117
        present issue, with: Entities::Issue, current_user: current_user, project: user_project
Nihad Abbasov's avatar
Nihad Abbasov committed
118 119
      end

Robert Schilling's avatar
Robert Schilling committed
120 121 122 123 124 125 126
      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.'
Bob Van Landuyt's avatar
Bob Van Landuyt committed
127
        optional :merge_request_to_resolve_discussions_of, type: Integer,
Robert Schilling's avatar
Robert Schilling committed
128
                                                           desc: 'The IID of a merge request for which to resolve discussions'
129
        optional :discussion_to_resolve, type: String,
Bob Van Landuyt's avatar
Bob Van Landuyt committed
130
                                         desc: 'The ID of a discussion to resolve, also pass `merge_request_to_resolve_discussions_of`'
Robert Schilling's avatar
Robert Schilling committed
131 132
        use :issue_params
      end
133
      post ':id/issues' do
Robert Schilling's avatar
Robert Schilling committed
134 135 136 137
        # Setting created_at time only allowed for admins and project owners
        unless current_user.admin? || user_project.owner == current_user
          params.delete(:created_at)
        end
138

Robert Schilling's avatar
Robert Schilling committed
139
        issue_params = declared_params(include_missing: false)
140

141 142
        issue_params = convert_parameters_from_legacy_format(issue_params)

Robert Schilling's avatar
Robert Schilling committed
143 144 145
        issue = ::Issues::CreateService.new(user_project,
                                            current_user,
                                            issue_params.merge(request: request, api: true)).execute
146
        if issue.spam?
147 148
          render_api_error!({ error: 'Spam detected' }, 400)
        end
149

150
        if issue.valid?
151
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
152
        else
153
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
154 155 156
        end
      end

157 158 159 160
      desc 'Update an existing issue' do
        success Entities::Issue
      end
      params do
161
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
162 163 164
        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.'
165
        optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
Robert Schilling's avatar
Robert Schilling committed
166
        use :issue_params
167
        at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id,
Robert Schilling's avatar
Robert Schilling committed
168
                        :labels, :created_at, :due_date, :confidential, :state_event
169
      end
170 171
      put ':id/issues/:issue_iid' do
        issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
172
        authorize! :update_issue, issue
173

Robert Schilling's avatar
Robert Schilling committed
174 175 176 177
        # Setting created_at time only allowed for admins and project owners
        unless current_user.admin? || user_project.owner == current_user
          params.delete(:updated_at)
        end
178

179 180
        update_params = declared_params(include_missing: false).merge(request: request, api: true)

181 182
        update_params = convert_parameters_from_legacy_format(update_params)

Robert Schilling's avatar
Robert Schilling committed
183 184
        issue = ::Issues::UpdateService.new(user_project,
                                            current_user,
185 186 187
                                            update_params).execute(issue)

        render_spam_error! if issue.spam?
188

189
        if issue.valid?
190
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
191
        else
192
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
193 194 195
        end
      end

Robert Schilling's avatar
Robert Schilling committed
196 197 198 199
      desc 'Move an existing issue' do
        success Entities::Issue
      end
      params do
200
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
201 202
        requires :to_project_id, type: Integer, desc: 'The ID of the new project'
      end
203 204
      post ':id/issues/:issue_iid/move' do
        issue = user_project.issues.find_by(iid: params[:issue_iid])
Robert Schilling's avatar
Robert Schilling committed
205
        not_found!('Issue') unless issue
206

Robert Schilling's avatar
Robert Schilling committed
207 208
        new_project = Project.find_by(id: params[:to_project_id])
        not_found!('Project') unless new_project
209 210 211

        begin
          issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
212
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
213 214 215 216 217
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

Robert Schilling's avatar
Robert Schilling committed
218 219
      desc 'Delete a project issue'
      params do
220
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
221
      end
222 223
      delete ":id/issues/:issue_iid" do
        issue = user_project.issues.find_by(iid: params[:issue_iid])
Robert Schilling's avatar
Robert Schilling committed
224
        not_found!('Issue') unless issue
225

226
        authorize!(:destroy_issue, issue)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
227
        status 204
228
        issue.destroy
Nihad Abbasov's avatar
Nihad Abbasov committed
229
      end
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

      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
      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
245 246 247 248 249 250 251 252 253 254 255 256 257 258

      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])

        return not_found!('UserAgentDetail') unless issue.user_agent_detail

259
        present issue.user_agent_detail, with: Entities::UserAgentDetail
260
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
261 262 263
    end
  end
end