issues.rb 10.5 KB
Newer Older
1
module API
N
Nihad Abbasov 已提交
2 3 4 5
  # Issues API
  class Issues < Grape::API
    before { authenticate! }

6 7
    helpers ::Gitlab::AkismetHelper

J
jubianchi 已提交
8
    helpers do
J
jubianchi 已提交
9
      def filter_issues_state(issues, state)
J
jubianchi 已提交
10
        case state
11 12
        when 'opened' then issues.opened
        when 'closed' then issues.closed
13
        else issues
J
jubianchi 已提交
14 15
        end
      end
J
jubianchi 已提交
16 17

      def filter_issues_labels(issues, labels)
J
jubianchi 已提交
18 19 20 21 22
        issues.includes(:labels).where('labels.title' => labels.split(','))
      end

      def filter_issues_milestone(issues, milestone)
        issues.includes(:milestone).where('milestones.title' => milestone)
J
jubianchi 已提交
23
      end
24 25

      def create_spam_log(project, current_user, attrs)
26
        params = attrs.merge({
27 28
          source_ip: client_ip(env),
          user_agent: user_agent(env),
29 30 31 32
          noteable_type: 'Issue',
          via_api: true
        })

33 34
        ::CreateSpamLogService.new(project, current_user, params).execute
      end
J
jubianchi 已提交
35 36
    end

N
Nihad Abbasov 已提交
37 38 39
    resource :issues do
      # Get currently authenticated user's issues
      #
J
jubianchi 已提交
40 41
      # Parameters:
      #   state (optional) - Return "opened" or "closed" issues
J
jubianchi 已提交
42
      #   labels (optional) - Comma-separated list of label names
43 44 45
      #   order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
      #   sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
      #
J
jubianchi 已提交
46
      # Example Requests:
N
Nihad Abbasov 已提交
47
      #   GET /issues
J
jubianchi 已提交
48 49
      #   GET /issues?state=opened
      #   GET /issues?state=closed
J
jubianchi 已提交
50 51 52
      #   GET /issues?labels=foo
      #   GET /issues?labels=foo,bar
      #   GET /issues?labels=foo,bar&state=opened
N
Nihad Abbasov 已提交
53
      get do
54
        issues = current_user.issues.inc_notes_with_associations
J
jubianchi 已提交
55 56
        issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
        issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
57
        issues.reorder(issuable_order_by => issuable_sort)
58
        present paginate(issues), with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
59 60 61
      end
    end

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    resource :groups do
      # Get a list of group issues
      #
      # Parameters:
      #   id (required) - The ID of a group
      #   state (optional) - Return "opened" or "closed" issues
      #   labels (optional) - Comma-separated list of label names
      #   milestone (optional) - Milestone title
      #   order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
      #   sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
      #
      # Example Requests:
      #   GET /groups/:id/issues
      #   GET /groups/:id/issues?state=opened
      #   GET /groups/:id/issues?state=closed
      #   GET /groups/:id/issues?labels=foo
      #   GET /groups/:id/issues?labels=foo,bar
      #   GET /groups/:id/issues?labels=foo,bar&state=opened
      #   GET /groups/:id/issues?milestone=1.0.0
      #   GET /groups/:id/issues?milestone=1.0.0&state=closed
      get ":id/issues" do
        group = find_group(params[:id])

        params[:state] ||= 'opened'
        params[:group_id] = group.id
        params[:milestone_title] = params.delete(:milestone)
        params[:label_name] = params.delete(:labels)
        params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort]

        issues = IssuesFinder.new(current_user, params).execute

        present paginate(issues), with: Entities::Issue, current_user: current_user
      end
    end

N
Nihad Abbasov 已提交
97 98 99 100
    resource :projects do
      # Get a list of project issues
      #
      # Parameters:
101
      #   id (required) - The ID of a project
102
      #   iid (optional) - Return the project issue having the given `iid`
J
jubianchi 已提交
103
      #   state (optional) - Return "opened" or "closed" issues
J
jubianchi 已提交
104
      #   labels (optional) - Comma-separated list of label names
J
jubianchi 已提交
105
      #   milestone (optional) - Milestone title
106 107
      #   order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
      #   sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
J
jubianchi 已提交
108 109
      #
      # Example Requests:
N
Nihad Abbasov 已提交
110
      #   GET /projects/:id/issues
J
jubianchi 已提交
111 112
      #   GET /projects/:id/issues?state=opened
      #   GET /projects/:id/issues?state=closed
J
jubianchi 已提交
113 114 115
      #   GET /projects/:id/issues?labels=foo
      #   GET /projects/:id/issues?labels=foo,bar
      #   GET /projects/:id/issues?labels=foo,bar&state=opened
J
jubianchi 已提交
116 117
      #   GET /projects/:id/issues?milestone=1.0.0
      #   GET /projects/:id/issues?milestone=1.0.0&state=closed
118
      #   GET /issues?iid=42
N
Nihad Abbasov 已提交
119
      get ":id/issues" do
120
        issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user)
J
jubianchi 已提交
121 122
        issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
        issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
123
        issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
124

J
jubianchi 已提交
125 126 127
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
J
jubianchi 已提交
128

129
        issues.reorder(issuable_order_by => issuable_sort)
130
        present paginate(issues), with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
131 132 133 134 135
      end

      # Get a single project issue
      #
      # Parameters:
136
      #   id (required) - The ID of a project
N
Nihad Abbasov 已提交
137 138 139 140
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   GET /projects/:id/issues/:issue_id
      get ":id/issues/:issue_id" do
141
        @issue = find_project_issue(params[:issue_id])
142
        present @issue, with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
143 144 145 146 147
      end

      # Create a new project issue
      #
      # Parameters:
148 149 150 151
      #   id (required)           - The ID of a project
      #   title (required)        - The title of an issue
      #   description (optional)  - The description of an issue
      #   assignee_id (optional)  - The ID of a user to assign issue
N
Nihad Abbasov 已提交
152
      #   milestone_id (optional) - The ID of a milestone to assign issue
153
      #   labels (optional)       - The labels of an issue
154
      #   created_at (optional)   - Date time string, ISO 8601 formatted
N
Nihad Abbasov 已提交
155 156 157
      # Example Request:
      #   POST /projects/:id/issues
      post ":id/issues" do
158
        required_attributes! [:title]
159 160 161 162

        keys = [:title, :description, :assignee_id, :milestone_id]
        keys << :created_at if current_user.admin? || user_project.owner == current_user
        attrs = attributes_for_keys(keys)
163

164
        # Validate label names in advance
165 166
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
167 168
        end

169
        project = user_project
170
        text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
171 172 173 174 175 176 177

        if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
          create_spam_log(project, current_user, attrs)
          render_api_error!({ error: 'Spam detected' }, 400)
        end

        issue = ::Issues::CreateService.new(project, current_user, attrs).execute
178 179

        if issue.valid?
180 181
          # Find or create labels and attach to issue. Labels are valid because
          # we already checked its name, so there can't be an error here
182
          if params[:labels].present?
183
            issue.add_labels_by_names(params[:labels].split(','))
184 185
          end

186
          present issue, with: Entities::Issue, current_user: current_user
187
        else
J
jubianchi 已提交
188
          render_validation_error!(issue)
N
Nihad Abbasov 已提交
189 190 191 192 193 194
        end
      end

      # Update an existing issue
      #
      # Parameters:
195
      #   id (required) - The ID of a project
N
Nihad Abbasov 已提交
196 197 198 199 200 201
      #   issue_id (required) - The ID of a project issue
      #   title (optional) - The title of an issue
      #   description (optional) - The description of an issue
      #   assignee_id (optional) - The ID of a user to assign issue
      #   milestone_id (optional) - The ID of a milestone to assign issue
      #   labels (optional) - The labels of an issue
202
      #   state_event (optional) - The state event of an issue (close|reopen)
203
      #   updated_at (optional) - Date time string, ISO 8601 formatted
N
Nihad Abbasov 已提交
204 205 206
      # Example Request:
      #   PUT /projects/:id/issues/:issue_id
      put ":id/issues/:issue_id" do
207
        issue = user_project.issues.find(params[:issue_id])
208
        authorize! :update_issue, issue
209 210 211
        keys = [:title, :description, :assignee_id, :milestone_id, :state_event]
        keys << :updated_at if current_user.admin? || user_project.owner == current_user
        attrs = attributes_for_keys(keys)
212

213
        # Validate label names in advance
214 215
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
216 217
        end

218
        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
219

220
        if issue.valid?
221 222
          # Find or create labels and attach to issue. Labels are valid because
          # we already checked its name, so there can't be an error here
223
          if params[:labels] && can?(current_user, :admin_issue, user_project)
224
            issue.remove_labels
225 226
            # Create and add labels to the new created issue
            issue.add_labels_by_names(params[:labels].split(','))
227 228
          end

229
          present issue, with: Entities::Issue, current_user: current_user
230
        else
J
jubianchi 已提交
231
          render_validation_error!(issue)
N
Nihad Abbasov 已提交
232 233 234
        end
      end

R
Robert Schilling 已提交
235 236 237
      # Move an existing issue
      #
      # Parameters:
238 239 240
      #  id (required)            - The ID of a project
      #  issue_id (required)      - The ID of a project issue
      #  to_project_id (required) - The ID of the new project
R
Robert Schilling 已提交
241 242
      # Example Request:
      #   POST /projects/:id/issues/:issue_id/move
243 244
      post ':id/issues/:issue_id/move' do
        required_attributes! [:to_project_id]
R
Robert Schilling 已提交
245 246

        issue = user_project.issues.find(params[:issue_id])
247
        new_project = Project.find(params[:to_project_id])
R
Robert Schilling 已提交
248 249 250

        begin
          issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
251
          present issue, with: Entities::Issue, current_user: current_user
R
Robert Schilling 已提交
252 253 254 255 256 257
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

      #
Z
Zeger-Jan van de Weg 已提交
258
      # Delete a project issue
N
Nihad Abbasov 已提交
259 260
      #
      # Parameters:
261
      #   id (required) - The ID of a project
N
Nihad Abbasov 已提交
262 263 264 265
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   DELETE /projects/:id/issues/:issue_id
      delete ":id/issues/:issue_id" do
266
        issue = user_project.issues.find_by(id: params[:issue_id])
Z
Zeger-Jan van de Weg 已提交
267

268
        authorize!(:destroy_issue, issue)
Z
Zeger-Jan van de Weg 已提交
269
        issue.destroy
N
Nihad Abbasov 已提交
270 271 272 273
      end
    end
  end
end