issues.rb 11.3 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! }

7 8
    helpers ::Gitlab::IssuableMetadata

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

        args.delete(:id)
        args[:milestone_title] = args.delete(:milestone)
15
        args[:label_name] = args.delete(:labels)
16

17
        issues = IssuesFinder.new(current_user, args).execute
18
          .preload(:assignees, :labels, :notes, :timelogs)
19 20

        issues.reorder(args[:order_by] => args[:sort])
21 22
      end

Robert Schilling's avatar
Robert Schilling committed
23 24
      params :issues_params do
        optional :labels, type: String, desc: 'Comma-separated list of label names'
25
        optional :milestone, type: String, desc: 'Milestone title'
Robert Schilling's avatar
Robert Schilling committed
26 27 28 29
        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.'
30
        optional :milestone, type: String, desc: 'Return issues for a specific milestone'
31
        optional :iids, type: Array[Integer], desc: 'The IID array of issues'
32
        optional :search, type: String, desc: 'Search issues for text present in the title or description'
33 34
        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'
35 36
        optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
        optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
37
        optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
38
                         desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
Robert Schilling's avatar
Robert Schilling committed
39 40
        use :pagination
      end
41

42
      params :issue_params_ce do
Robert Schilling's avatar
Robert Schilling committed
43
        optional :description, type: String, desc: 'The description of an issue'
44 45
        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
46 47
        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'
48
        optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
Robert Schilling's avatar
Robert Schilling committed
49
        optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
50
      end
51 52 53 54

      params :issue_params do
        use :issue_params_ce
      end
jubianchi's avatar
jubianchi committed
55 56
    end

Nihad Abbasov's avatar
Nihad Abbasov committed
57
    resource :issues do
Robert Schilling's avatar
Robert Schilling committed
58
      desc "Get currently authenticated user's issues" do
59
        success Entities::IssueBasic
Robert Schilling's avatar
Robert Schilling committed
60 61 62 63 64
      end
      params do
        optional :state, type: String, values: %w[opened closed all], default: 'all',
                         desc: 'Return opened, closed, or all issues'
        use :issues_params
65
        optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me',
66
                         desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
Robert Schilling's avatar
Robert Schilling committed
67
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
68
      get do
69
        issues = find_issues
Sean McGivern's avatar
Sean McGivern committed
70

71 72 73 74 75
        options = {
          with: Entities::IssueBasic,
          current_user: current_user,
          issuable_metadata: issuable_meta_data(issues, 'Issue')
        }
76 77

        present paginate(issues), options
Nihad Abbasov's avatar
Nihad Abbasov committed
78 79 80
      end
    end

Robert Schilling's avatar
Robert Schilling committed
81 82 83
    params do
      requires :id, type: String, desc: 'The ID of a group'
    end
84
    resource :groups, requirements: { id: %r{[^/]+} } do
Robert Schilling's avatar
Robert Schilling committed
85
      desc 'Get a list of group issues' do
86
        success Entities::IssueBasic
Robert Schilling's avatar
Robert Schilling committed
87 88
      end
      params do
89
        optional :state, type: String, values: %w[opened closed all], default: 'all',
Robert Schilling's avatar
Robert Schilling committed
90 91 92
                         desc: 'Return opened, closed, or all issues'
        use :issues_params
      end
93
      get ":id/issues" do
94
        group = find_group!(params[:id])
95

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

98 99 100 101 102
        options = {
          with: Entities::IssueBasic,
          current_user: current_user,
          issuable_metadata: issuable_meta_data(issues, 'Issue')
        }
103 104

        present paginate(issues), options
105 106 107
      end
    end

108 109 110
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
111
    resource :projects, requirements: { id: %r{[^/]+} } do
112 113
      include TimeTrackingEndpoints

Robert Schilling's avatar
Robert Schilling committed
114
      desc 'Get a list of project issues' do
115
        success Entities::IssueBasic
Robert Schilling's avatar
Robert Schilling committed
116 117 118 119 120 121
      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
122
      get ":id/issues" do
123
        project = find_project!(params[:id])
124

125
        issues = find_issues(project_id: project.id)
126

127 128 129 130 131 132
        options = {
          with: Entities::IssueBasic,
          current_user: current_user,
          project: user_project,
          issuable_metadata: issuable_meta_data(issues, 'Issue')
        }
133 134

        present paginate(issues), options
Nihad Abbasov's avatar
Nihad Abbasov committed
135 136
      end

Robert Schilling's avatar
Robert Schilling committed
137 138 139 140
      desc 'Get a single project issue' do
        success Entities::Issue
      end
      params do
141
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
142
      end
143
      get ":id/issues/:issue_iid", as: :api_v4_project_issue do
144
        issue = find_project_issue(params[:issue_iid])
Robert Schilling's avatar
Robert Schilling committed
145
        present issue, with: Entities::Issue, current_user: current_user, project: user_project
Nihad Abbasov's avatar
Nihad Abbasov committed
146 147
      end

Robert Schilling's avatar
Robert Schilling committed
148 149 150 151 152 153 154
      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
155
        optional :merge_request_to_resolve_discussions_of, type: Integer,
Robert Schilling's avatar
Robert Schilling committed
156
                                                           desc: 'The IID of a merge request for which to resolve discussions'
157
        optional :discussion_to_resolve, type: String,
Bob Van Landuyt's avatar
Bob Van Landuyt committed
158
                                         desc: 'The ID of a discussion to resolve, also pass `merge_request_to_resolve_discussions_of`'
Robert Schilling's avatar
Robert Schilling committed
159 160
        use :issue_params
      end
161
      post ':id/issues' do
Robert Schilling's avatar
Robert Schilling committed
162 163 164 165
        # 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
166

Robert Schilling's avatar
Robert Schilling committed
167
        issue_params = declared_params(include_missing: false)
168

169 170
        issue_params = convert_parameters_from_legacy_format(issue_params)

Robert Schilling's avatar
Robert Schilling committed
171 172 173
        issue = ::Issues::CreateService.new(user_project,
                                            current_user,
                                            issue_params.merge(request: request, api: true)).execute
174
        if issue.spam?
175 176
          render_api_error!({ error: 'Spam detected' }, 400)
        end
177

178
        if issue.valid?
179
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
180
        else
181
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
182 183 184
        end
      end

185 186 187 188
      desc 'Update an existing issue' do
        success Entities::Issue
      end
      params do
189
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
190 191 192
        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.'
193
        optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
Robert Schilling's avatar
Robert Schilling committed
194
        use :issue_params
195
        at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id,
Robert Schilling's avatar
Robert Schilling committed
196
                        :labels, :created_at, :due_date, :confidential, :state_event
197
      end
198 199
      put ':id/issues/:issue_iid' do
        issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
200
        authorize! :update_issue, issue
201

Robert Schilling's avatar
Robert Schilling committed
202 203 204 205
        # 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
206

207 208
        update_params = declared_params(include_missing: false).merge(request: request, api: true)

209 210
        update_params = convert_parameters_from_legacy_format(update_params)

Robert Schilling's avatar
Robert Schilling committed
211 212
        issue = ::Issues::UpdateService.new(user_project,
                                            current_user,
213 214 215
                                            update_params).execute(issue)

        render_spam_error! if issue.spam?
216

217
        if issue.valid?
218
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
219
        else
220
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
221 222 223
        end
      end

Robert Schilling's avatar
Robert Schilling committed
224 225 226 227
      desc 'Move an existing issue' do
        success Entities::Issue
      end
      params do
228
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
229 230
        requires :to_project_id, type: Integer, desc: 'The ID of the new project'
      end
231 232
      post ':id/issues/:issue_iid/move' do
        issue = user_project.issues.find_by(iid: params[:issue_iid])
Robert Schilling's avatar
Robert Schilling committed
233
        not_found!('Issue') unless issue
234

Robert Schilling's avatar
Robert Schilling committed
235 236
        new_project = Project.find_by(id: params[:to_project_id])
        not_found!('Project') unless new_project
237 238 239

        begin
          issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
240
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
241 242 243 244 245
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

Robert Schilling's avatar
Robert Schilling committed
246 247
      desc 'Delete a project issue'
      params do
248
        requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
Robert Schilling's avatar
Robert Schilling committed
249
      end
250 251
      delete ":id/issues/:issue_iid" do
        issue = user_project.issues.find_by(iid: params[:issue_iid])
Robert Schilling's avatar
Robert Schilling committed
252
        not_found!('Issue') unless issue
253

254
        authorize!(:destroy_issue, issue)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
255
        status 204
256
        issue.destroy
Nihad Abbasov's avatar
Nihad Abbasov committed
257
      end
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

      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
273 274 275 276 277 278 279 280 281 282 283 284 285 286

      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

287
        present issue.user_agent_detail, with: Entities::UserAgentDetail
288
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
289 290 291
    end
  end
end