issues.rb 7.53 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

8
    helpers do
9
      def filter_issues_state(issues, state)
10
        case state
11 12
        when 'opened' then issues.opened
        when 'closed' then issues.closed
13
        else issues
14 15
        end
      end
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)
23
      end
24 25 26 27 28 29 30 31 32

      def create_spam_log(project, current_user, attrs)
        params = attrs.dup
        params[:source_ip] = env['REMOTE_ADDR']
        params[:user_agent] = env['HTTP_USER_AGENT']
        params[:noteable_type] = 'Issue'
        params[:via_api] = true
        ::CreateSpamLogService.new(project, current_user, params).execute
      end
33 34
    end

Nihad Abbasov's avatar
Nihad Abbasov committed
35 36 37
    resource :issues do
      # Get currently authenticated user's issues
      #
38 39
      # Parameters:
      #   state (optional) - Return "opened" or "closed" issues
40
      #   labels (optional) - Comma-separated list of label names
41 42 43
      #   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`
      #
44
      # Example Requests:
Nihad Abbasov's avatar
Nihad Abbasov committed
45
      #   GET /issues
46 47
      #   GET /issues?state=opened
      #   GET /issues?state=closed
48 49 50
      #   GET /issues?labels=foo
      #   GET /issues?labels=foo,bar
      #   GET /issues?labels=foo,bar&state=opened
Nihad Abbasov's avatar
Nihad Abbasov committed
51
      get do
52 53 54
        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?
55
        issues.reorder(issuable_order_by => issuable_sort)
56
        present paginate(issues), with: Entities::Issue
Nihad Abbasov's avatar
Nihad Abbasov committed
57 58 59 60 61 62 63
      end
    end

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

88 89 90
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
91

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

      # Get a single project issue
      #
      # Parameters:
99
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
100 101 102 103 104
      #   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])
105
        present @issue, with: Entities::Issue
Nihad Abbasov's avatar
Nihad Abbasov committed
106 107 108 109 110
      end

      # Create a new project issue
      #
      # Parameters:
111
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
112 113 114 115 116 117 118 119
      #   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
      #   milestone_id (optional) - The ID of a milestone to assign issue
      #   labels (optional) - The labels of an issue
      # Example Request:
      #   POST /projects/:id/issues
      post ":id/issues" do
120 121
        required_attributes! [:title]
        attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
122

123
        # Validate label names in advance
124 125
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
126 127
        end

128 129 130 131 132 133 134 135 136 137
        project = user_project
        text = attrs[:title]
        text += "\n#{attrs[:description]}" if attrs[:description].present?

        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
138 139

        if issue.valid?
140 141
          # 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
142
          if params[:labels].present?
143
            issue.add_labels_by_names(params[:labels].split(','))
144 145
          end

146 147
          present issue, with: Entities::Issue
        else
148
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
149 150 151 152 153 154
        end
      end

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

170
        # Validate label names in advance
171 172
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
173 174
        end

175
        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
176

177
        if issue.valid?
178 179
          # 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
180
          if params[:labels] && can?(current_user, :admin_issue, user_project)
181
            issue.remove_labels
182 183
            # Create and add labels to the new created issue
            issue.add_labels_by_names(params[:labels].split(','))
184 185
          end

186 187
          present issue, with: Entities::Issue
        else
188
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
189 190 191
        end
      end

192
      # Delete a project issue (deprecated)
Nihad Abbasov's avatar
Nihad Abbasov committed
193 194
      #
      # Parameters:
195
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
196 197 198 199
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   DELETE /projects/:id/issues/:issue_id
      delete ":id/issues/:issue_id" do
200
        not_allowed!
Nihad Abbasov's avatar
Nihad Abbasov committed
201 202 203 204
      end
    end
  end
end