issues.rb 8.68 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 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
      def find_issues(args = {})
        args = params.merge(args)

        args.delete(:id)
        args[:milestone_title] = args.delete(:milestone)

        match_all_labels = args.delete(:match_all_labels)
        labels = args.delete(:labels)
        args[:label_name] = labels if match_all_labels

        args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)

        issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations

        # TODO: Remove in 9.0  pass `label_name: args.delete(:labels)` to IssuesFinder
        if !match_all_labels && labels.present?
          issues = issues.includes(:labels).where('labels.title' => labels.split(','))
        end

        issues.reorder(args[:order_by] => args[:sort])
28 29
      end

Robert Schilling's avatar
Robert Schilling committed
30 31
      params :issues_params do
        optional :labels, type: String, desc: 'Comma-separated list of label names'
32
        optional :milestone, type: String, desc: 'Milestone title'
Robert Schilling's avatar
Robert Schilling committed
33 34 35 36
        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.'
37
        optional :milestone, type: String, desc: 'Return issues for a specific milestone'
Robert Schilling's avatar
Robert Schilling committed
38 39
        use :pagination
      end
40

Robert Schilling's avatar
Robert Schilling committed
41 42 43 44 45 46 47
      params :issue_params do
        optional :description, type: String, desc: 'The description of an issue'
        optional :assignee_id, type: Integer, desc: '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: String, desc: 'Comma-separated list of label names'
        optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY'
        optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
48
      end
jubianchi's avatar
jubianchi committed
49 50
    end

Nihad Abbasov's avatar
Nihad Abbasov committed
51
    resource :issues do
Robert Schilling's avatar
Robert Schilling committed
52 53 54 55 56 57 58 59
      desc "Get currently authenticated user's issues" do
        success Entities::Issue
      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
60
      get do
61
        issues = find_issues(scope: 'authored')
Sean McGivern's avatar
Sean McGivern committed
62

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

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

82
        issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true)
Sean McGivern's avatar
Sean McGivern committed
83

84 85 86 87
        present paginate(issues), with: Entities::Issue, current_user: current_user
      end
    end

88 89 90
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
91
    resource :projects do
92 93
      include TimeTrackingEndpoints

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

106
        issues = find_issues(project_id: project.id)
107

108
        present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
Nihad Abbasov's avatar
Nihad Abbasov committed
109 110
      end

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

Robert Schilling's avatar
Robert Schilling committed
122 123 124 125 126 127 128 129 130 131 132
      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_for_resolving_discussions, type: Integer,
                                                           desc: 'The IID of a merge request for which to resolve discussions'
        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

        if merge_request_iid = params[:merge_request_for_resolving_discussions]
Robert Schilling's avatar
Robert Schilling committed
142
          issue_params[:merge_request_for_resolving_discussions] = MergeRequestsFinder.new(current_user, project_id: user_project.id).
143 144 145
            execute.
            find_by(iid: merge_request_iid)
        end
146

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

154
        if issue.valid?
155
          present issue, with: Entities::Issue, current_user: current_user, project: user_project
156
        else
157
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
158 159 160
        end
      end

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

Robert Schilling's avatar
Robert Schilling committed
178 179 180 181
        # 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
182

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

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

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

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

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

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

224
        authorize!(:destroy_issue, issue)
225
        issue.destroy
Nihad Abbasov's avatar
Nihad Abbasov committed
226 227 228 229
      end
    end
  end
end