issues.rb 7.99 KB
Newer Older
1
module API
Nihad Abbasov's avatar
Nihad Abbasov committed
2 3 4 5
  # Issues API
  class Issues < Grape::API
    before { authenticate! }

6 7
    helpers ::Gitlab::AkismetHelper

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

      def filter_issues_labels(issues, labels)
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)
jubianchi's avatar
jubianchi committed
23
      end
24 25

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

33 34
        ::CreateSpamLogService.new(project, current_user, params).execute
      end
jubianchi's avatar
jubianchi committed
35 36
    end

Nihad Abbasov's avatar
Nihad Abbasov committed
37 38 39
    resource :issues do
      # Get currently authenticated user's issues
      #
jubianchi's avatar
jubianchi committed
40 41
      # Parameters:
      #   state (optional) - Return "opened" or "closed" issues
jubianchi's avatar
jubianchi committed
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`
      #
jubianchi's avatar
jubianchi committed
46
      # Example Requests:
Nihad Abbasov's avatar
Nihad Abbasov committed
47
      #   GET /issues
jubianchi's avatar
jubianchi committed
48 49
      #   GET /issues?state=opened
      #   GET /issues?state=closed
jubianchi's avatar
jubianchi committed
50 51 52
      #   GET /issues?labels=foo
      #   GET /issues?labels=foo,bar
      #   GET /issues?labels=foo,bar&state=opened
Nihad Abbasov's avatar
Nihad Abbasov committed
53
      get do
jubianchi's avatar
jubianchi committed
54 55 56
        issues = current_user.issues
        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
Nihad Abbasov's avatar
Nihad Abbasov committed
59 60 61 62 63 64 65
      end
    end

    resource :projects do
      # Get a list of project issues
      #
      # Parameters:
66
      #   id (required) - The ID of a project
67
      #   iid (optional) - Return the project issue having the given `iid`
jubianchi's avatar
jubianchi committed
68
      #   state (optional) - Return "opened" or "closed" issues
jubianchi's avatar
jubianchi committed
69
      #   labels (optional) - Comma-separated list of label names
70
      #   milestone (optional) - Milestone title
71 72
      #   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`
jubianchi's avatar
jubianchi committed
73 74
      #
      # Example Requests:
Nihad Abbasov's avatar
Nihad Abbasov committed
75
      #   GET /projects/:id/issues
jubianchi's avatar
jubianchi committed
76 77
      #   GET /projects/:id/issues?state=opened
      #   GET /projects/:id/issues?state=closed
jubianchi's avatar
jubianchi committed
78 79 80
      #   GET /projects/:id/issues?labels=foo
      #   GET /projects/:id/issues?labels=foo,bar
      #   GET /projects/:id/issues?labels=foo,bar&state=opened
81 82
      #   GET /projects/:id/issues?milestone=1.0.0
      #   GET /projects/:id/issues?milestone=1.0.0&state=closed
83
      #   GET /issues?iid=42
Nihad Abbasov's avatar
Nihad Abbasov committed
84
      get ":id/issues" do
85
        issues = user_project.issues.visible_to_user(current_user)
jubianchi's avatar
jubianchi committed
86 87
        issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
        issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
88
        issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
89

90 91 92
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
jubianchi's avatar
jubianchi committed
93

94
        issues.reorder(issuable_order_by => issuable_sort)
95
        present paginate(issues), with: Entities::Issue, current_user: current_user
Nihad Abbasov's avatar
Nihad Abbasov committed
96 97 98 99 100
      end

      # Get a single project issue
      #
      # Parameters:
101
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
102 103 104 105 106
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   GET /projects/:id/issues/:issue_id
      get ":id/issues/:issue_id" do
        @issue = user_project.issues.find(params[:issue_id])
107
        not_found! unless can?(current_user, :read_issue, @issue)
108
        present @issue, with: Entities::Issue, current_user: current_user
Nihad Abbasov's avatar
Nihad Abbasov committed
109 110 111 112 113
      end

      # Create a new project issue
      #
      # Parameters:
114 115 116 117
      #   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
Nihad Abbasov's avatar
Nihad Abbasov committed
118
      #   milestone_id (optional) - The ID of a milestone to assign issue
119 120
      #   labels (optional)       - The labels of an issue
      #   created_at (optional)   - The date
Nihad Abbasov's avatar
Nihad Abbasov committed
121 122 123
      # Example Request:
      #   POST /projects/:id/issues
      post ":id/issues" do
124
        required_attributes! [:title]
125 126 127 128

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

130
        # Validate label names in advance
131 132
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
133 134
        end

135
        project = user_project
136
        text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
137 138 139 140 141 142 143

        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
144 145

        if issue.valid?
146 147
          # 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
148
          if params[:labels].present?
149
            issue.add_labels_by_names(params[:labels].split(','))
150 151
          end

152
          present issue, with: Entities::Issue, current_user: current_user
153
        else
154
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
155 156 157 158 159 160
        end
      end

      # Update an existing issue
      #
      # Parameters:
161
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
162 163 164 165 166 167
      #   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
168
      #   state_event (optional) - The state event of an issue (close|reopen)
Nihad Abbasov's avatar
Nihad Abbasov committed
169 170 171
      # Example Request:
      #   PUT /projects/:id/issues/:issue_id
      put ":id/issues/:issue_id" do
172
        issue = user_project.issues.find(params[:issue_id])
173
        authorize! :update_issue, issue
174 175
        attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]

176
        # Validate label names in advance
177 178
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
179 180
        end

181
        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
182

183
        if issue.valid?
184 185
          # 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
186
          if params[:labels] && can?(current_user, :admin_issue, user_project)
187
            issue.remove_labels
188 189
            # Create and add labels to the new created issue
            issue.add_labels_by_names(params[:labels].split(','))
190 191
          end

192
          present issue, with: Entities::Issue, current_user: current_user
193
        else
194
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
195 196 197
        end
      end

198
      # Delete a project issue
Nihad Abbasov's avatar
Nihad Abbasov committed
199 200
      #
      # Parameters:
201
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
202 203 204 205
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   DELETE /projects/:id/issues/:issue_id
      delete ":id/issues/:issue_id" do
206
        issue = user_project.issues.find_by(id: params[:issue_id])
207

208
        authorize!(:destroy_issue, issue)
209
        issue.destroy
Nihad Abbasov's avatar
Nihad Abbasov committed
210 211 212 213
      end
    end
  end
end