BigW Consortium Gitlab

issues.rb 10.2 KB
Newer Older
1
module API
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
    end

Nihad Abbasov committed
26 27 28
    resource :issues do
      # Get currently authenticated user's issues
      #
29 30
      # Parameters:
      #   state (optional) - Return "opened" or "closed" issues
31
      #   labels (optional) - Comma-separated list of label names
32 33 34
      #   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`
      #
35
      # Example Requests:
Nihad Abbasov committed
36
      #   GET /issues
37 38
      #   GET /issues?state=opened
      #   GET /issues?state=closed
39 40 41
      #   GET /issues?labels=foo
      #   GET /issues?labels=foo,bar
      #   GET /issues?labels=foo,bar&state=opened
Nihad Abbasov committed
42
      get do
43
        issues = current_user.issues.inc_notes_with_associations
44 45
        issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
        issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
46
        issues.reorder(issuable_order_by => issuable_sort)
47
        present paginate(issues), with: Entities::Issue, current_user: current_user
Nihad Abbasov committed
48 49 50
      end
    end

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    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

Nihad Abbasov committed
86 87 88 89
    resource :projects do
      # Get a list of project issues
      #
      # Parameters:
90
      #   id (required) - The ID of a project
91
      #   iid (optional) - Return the project issue having the given `iid`
92
      #   state (optional) - Return "opened" or "closed" issues
93
      #   labels (optional) - Comma-separated list of label names
94
      #   milestone (optional) - Milestone title
95 96
      #   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`
97 98
      #
      # Example Requests:
Nihad Abbasov committed
99
      #   GET /projects/:id/issues
100 101
      #   GET /projects/:id/issues?state=opened
      #   GET /projects/:id/issues?state=closed
102 103 104
      #   GET /projects/:id/issues?labels=foo
      #   GET /projects/:id/issues?labels=foo,bar
      #   GET /projects/:id/issues?labels=foo,bar&state=opened
105 106
      #   GET /projects/:id/issues?milestone=1.0.0
      #   GET /projects/:id/issues?milestone=1.0.0&state=closed
107
      #   GET /issues?iid=42
Nihad Abbasov committed
108
      get ":id/issues" do
109
        issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user)
110 111
        issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
        issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
112
        issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
113

114 115 116
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
117

118
        issues.reorder(issuable_order_by => issuable_sort)
119
        present paginate(issues), with: Entities::Issue, current_user: current_user
Nihad Abbasov committed
120 121 122 123 124
      end

      # Get a single project issue
      #
      # Parameters:
125
      #   id (required) - The ID of a project
Nihad Abbasov committed
126 127 128 129
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   GET /projects/:id/issues/:issue_id
      get ":id/issues/:issue_id" do
130
        @issue = find_project_issue(params[:issue_id])
131
        present @issue, with: Entities::Issue, current_user: current_user
Nihad Abbasov committed
132 133 134 135 136
      end

      # Create a new project issue
      #
      # Parameters:
137 138 139 140
      #   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 committed
141
      #   milestone_id (optional) - The ID of a milestone to assign issue
142
      #   labels (optional)       - The labels of an issue
143
      #   created_at (optional)   - Date time string, ISO 8601 formatted
144
      #   due_date (optional)     - Date time string in the format YEAR-MONTH-DAY
Nihad Abbasov committed
145 146
      # Example Request:
      #   POST /projects/:id/issues
147
      post ':id/issues' do
148
        required_attributes! [:title]
149

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

154
        # Validate label names in advance
155 156
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
157 158
        end

159 160
        project = user_project

161 162 163 164 165
        issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute

        if issue.spam?
          render_api_error!({ error: 'Spam detected' }, 400)
        end
166

167
        if issue.valid?
168 169
          # 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
170
          if params[:labels].present?
171
            issue.add_labels_by_names(params[:labels].split(','))
172 173
          end

174
          present issue, with: Entities::Issue, current_user: current_user
175
        else
176
          render_validation_error!(issue)
Nihad Abbasov committed
177 178 179 180 181 182
        end
      end

      # Update an existing issue
      #
      # Parameters:
183
      #   id (required) - The ID of a project
Nihad Abbasov committed
184 185 186 187 188 189
      #   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
190
      #   state_event (optional) - The state event of an issue (close|reopen)
191
      #   updated_at (optional) - Date time string, ISO 8601 formatted
192
      #   due_date (optional)     - Date time string in the format YEAR-MONTH-DAY
Nihad Abbasov committed
193 194
      # Example Request:
      #   PUT /projects/:id/issues/:issue_id
195
      put ':id/issues/:issue_id' do
196
        issue = user_project.issues.find(params[:issue_id])
197
        authorize! :update_issue, issue
198
        keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date]
199 200
        keys << :updated_at if current_user.admin? || user_project.owner == current_user
        attrs = attributes_for_keys(keys)
201

202
        # Validate label names in advance
203 204
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
205 206
        end

207
        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
208

209
        if issue.valid?
210 211
          # 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
212
          if params[:labels] && can?(current_user, :admin_issue, user_project)
213
            issue.remove_labels
214 215
            # Create and add labels to the new created issue
            issue.add_labels_by_names(params[:labels].split(','))
216 217
          end

218
          present issue, with: Entities::Issue, current_user: current_user
219
        else
220
          render_validation_error!(issue)
Nihad Abbasov committed
221 222 223
        end
      end

224 225 226
      # Move an existing issue
      #
      # Parameters:
227 228 229
      #  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
230 231
      # Example Request:
      #   POST /projects/:id/issues/:issue_id/move
232 233
      post ':id/issues/:issue_id/move' do
        required_attributes! [:to_project_id]
234 235

        issue = user_project.issues.find(params[:issue_id])
236
        new_project = Project.find(params[:to_project_id])
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
241 242 243 244 245 246
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

      #
247
      # Delete a project issue
Nihad Abbasov committed
248 249
      #
      # Parameters:
250
      #   id (required) - The ID of a project
Nihad Abbasov committed
251 252 253 254
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   DELETE /projects/:id/issues/:issue_id
      delete ":id/issues/:issue_id" do
255
        issue = user_project.issues.find_by(id: params[:issue_id])
256

257
        authorize!(:destroy_issue, issue)
258
        issue.destroy
Nihad Abbasov committed
259 260 261 262
      end
    end
  end
end