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

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

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

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

N
Nihad Abbasov 已提交
24 25 26
    resource :issues do
      # Get currently authenticated user's issues
      #
J
jubianchi 已提交
27 28
      # Parameters:
      #   state (optional) - Return "opened" or "closed" issues
J
jubianchi 已提交
29
      #   labels (optional) - Comma-separated list of label names
30 31 32
      #   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 已提交
33
      # Example Requests:
N
Nihad Abbasov 已提交
34
      #   GET /issues
J
jubianchi 已提交
35 36
      #   GET /issues?state=opened
      #   GET /issues?state=closed
J
jubianchi 已提交
37 38 39
      #   GET /issues?labels=foo
      #   GET /issues?labels=foo,bar
      #   GET /issues?labels=foo,bar&state=opened
N
Nihad Abbasov 已提交
40
      get do
41
        issues = current_user.issues.inc_notes_with_associations
J
jubianchi 已提交
42 43
        issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
        issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
S
Sean McGivern 已提交
44 45
        issues = issues.reorder(issuable_order_by => issuable_sort)

46
        present paginate(issues), with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
47 48 49
      end
    end

50 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
    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)
S
Sean McGivern 已提交
77 78 79 80 81

        if params[:order_by] || params[:sort]
          # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example)
          params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}"
        end
82 83 84 85 86 87 88

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

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

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

J
jubianchi 已提交
117 118 119
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
J
jubianchi 已提交
120

S
Sean McGivern 已提交
121 122
        issues = issues.reorder(issuable_order_by => issuable_sort)

123
        present paginate(issues), with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
124 125 126 127 128
      end

      # Get a single project issue
      #
      # Parameters:
129
      #   id (required) - The ID of a project
N
Nihad Abbasov 已提交
130 131 132 133
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   GET /projects/:id/issues/:issue_id
      get ":id/issues/:issue_id" do
134
        @issue = find_project_issue(params[:issue_id])
135
        present @issue, with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
136 137 138 139 140
      end

      # Create a new project issue
      #
      # Parameters:
141 142 143 144
      #   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 已提交
145
      #   milestone_id (optional) - The ID of a milestone to assign issue
146
      #   labels (optional)       - The labels of an issue
147
      #   created_at (optional)   - Date time string, ISO 8601 formatted
148
      #   due_date (optional)     - Date time string in the format YEAR-MONTH-DAY
149
      #   confidential (optional) - Boolean parameter if the issue should be confidential
N
Nihad Abbasov 已提交
150 151
      # Example Request:
      #   POST /projects/:id/issues
152
      post ':id/issues' do
153
        required_attributes! [:title]
154

155
        keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential]
156 157
        keys << :created_at if current_user.admin? || user_project.owner == current_user
        attrs = attributes_for_keys(keys)
158

159
        # Validate label names in advance
160 161
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
162 163
        end

164
        attrs[:labels] = params[:labels] if params[:labels]
165

166 167 168 169
        # Convert and filter out invalid confidential flags
        attrs['confidential'] = to_boolean(attrs['confidential'])
        attrs.delete('confidential') if attrs['confidential'].nil?

170
        issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute
171

172
        if issue.spam?
173 174
          render_api_error!({ error: 'Spam detected' }, 400)
        end
175

176
        if issue.valid?
177
          present issue, with: Entities::Issue, current_user: current_user
178
        else
J
jubianchi 已提交
179
          render_validation_error!(issue)
N
Nihad Abbasov 已提交
180 181 182 183 184 185
        end
      end

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

206
        # Validate label names in advance
207 208
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
209 210
        end

211
        attrs[:labels] = params[:labels] if params[:labels]
212

213 214 215 216
        # Convert and filter out invalid confidential flags
        attrs['confidential'] = to_boolean(attrs['confidential'])
        attrs.delete('confidential') if attrs['confidential'].nil?

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

219
        if issue.valid?
220
          present issue, with: Entities::Issue, current_user: current_user
221
        else
J
jubianchi 已提交
222
          render_validation_error!(issue)
N
Nihad Abbasov 已提交
223 224 225
        end
      end

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

        issue = user_project.issues.find(params[:issue_id])
238
        new_project = Project.find(params[:to_project_id])
R
Robert Schilling 已提交
239 240 241

        begin
          issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
242
          present issue, with: Entities::Issue, current_user: current_user
R
Robert Schilling 已提交
243 244 245 246 247 248
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

      #
Z
Zeger-Jan van de Weg 已提交
249
      # Delete a project issue
N
Nihad Abbasov 已提交
250 251
      #
      # Parameters:
252
      #   id (required) - The ID of a project
N
Nihad Abbasov 已提交
253 254 255 256
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   DELETE /projects/:id/issues/:issue_id
      delete ":id/issues/:issue_id" do
257
        issue = user_project.issues.find_by(id: params[:issue_id])
Z
Zeger-Jan van de Weg 已提交
258

259
        authorize!(:destroy_issue, issue)
Z
Zeger-Jan van de Weg 已提交
260
        issue.destroy
N
Nihad Abbasov 已提交
261 262 263 264
      end
    end
  end
end