issues.rb 10.1 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?
44
        issues.reorder(issuable_order_by => issuable_sort)
45
        present paginate(issues), with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
46 47 48
      end
    end

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

J
jubianchi 已提交
112 113 114
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
J
jubianchi 已提交
115

116
        issues.reorder(issuable_order_by => issuable_sort)
117
        present paginate(issues), with: Entities::Issue, current_user: current_user
N
Nihad Abbasov 已提交
118 119 120 121 122
      end

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

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

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

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

158
        attrs[:labels] = params[:labels] if params[:labels]
159

160 161 162 163
        # Convert and filter out invalid confidential flags
        attrs['confidential'] = to_boolean(attrs['confidential'])
        attrs.delete('confidential') if attrs['confidential'].nil?

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

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

170
        if issue.valid?
171
          present issue, with: Entities::Issue, current_user: current_user
172
        else
J
jubianchi 已提交
173
          render_validation_error!(issue)
N
Nihad Abbasov 已提交
174 175 176 177 178 179
        end
      end

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

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

205
        attrs[:labels] = params[:labels] if params[:labels]
206

207 208 209 210
        # Convert and filter out invalid confidential flags
        attrs['confidential'] = to_boolean(attrs['confidential'])
        attrs.delete('confidential') if attrs['confidential'].nil?

211
        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
212

213
        if issue.valid?
214
          present issue, with: Entities::Issue, current_user: current_user
215
        else
J
jubianchi 已提交
216
          render_validation_error!(issue)
N
Nihad Abbasov 已提交
217 218 219
        end
      end

R
Robert Schilling 已提交
220 221 222
      # Move an existing issue
      #
      # Parameters:
223 224 225
      #  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 已提交
226 227
      # Example Request:
      #   POST /projects/:id/issues/:issue_id/move
228 229
      post ':id/issues/:issue_id/move' do
        required_attributes! [:to_project_id]
R
Robert Schilling 已提交
230 231

        issue = user_project.issues.find(params[:issue_id])
232
        new_project = Project.find(params[:to_project_id])
R
Robert Schilling 已提交
233 234 235

        begin
          issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
236
          present issue, with: Entities::Issue, current_user: current_user
R
Robert Schilling 已提交
237 238 239 240 241 242
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

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

253
        authorize!(:destroy_issue, issue)
Z
Zeger-Jan van de Weg 已提交
254
        issue.destroy
N
Nihad Abbasov 已提交
255 256 257 258
      end
    end
  end
end