issues.rb 9.56 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! }

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

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

Nihad Abbasov's avatar
Nihad Abbasov committed
24 25 26
    resource :issues do
      # Get currently authenticated user's issues
      #
jubianchi's avatar
jubianchi committed
27 28
      # Parameters:
      #   state (optional) - Return "opened" or "closed" issues
jubianchi's avatar
jubianchi committed
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`
      #
jubianchi's avatar
jubianchi committed
33
      # Example Requests:
Nihad Abbasov's avatar
Nihad Abbasov committed
34
      #   GET /issues
jubianchi's avatar
jubianchi committed
35 36
      #   GET /issues?state=opened
      #   GET /issues?state=closed
jubianchi's avatar
jubianchi committed
37 38 39
      #   GET /issues?labels=foo
      #   GET /issues?labels=foo,bar
      #   GET /issues?labels=foo,bar&state=opened
Nihad Abbasov's avatar
Nihad Abbasov committed
40
      get do
41
        issues = current_user.issues.inc_notes_with_associations
jubianchi's avatar
jubianchi committed
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
Nihad Abbasov's avatar
Nihad Abbasov committed
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

Nihad Abbasov's avatar
Nihad Abbasov committed
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`
jubianchi's avatar
jubianchi committed
90
      #   state (optional) - Return "opened" or "closed" issues
jubianchi's avatar
jubianchi committed
91
      #   labels (optional) - Comma-separated list of label names
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`
jubianchi's avatar
jubianchi committed
95 96
      #
      # Example Requests:
Nihad Abbasov's avatar
Nihad Abbasov committed
97
      #   GET /projects/:id/issues
jubianchi's avatar
jubianchi committed
98 99
      #   GET /projects/:id/issues?state=opened
      #   GET /projects/:id/issues?state=closed
jubianchi's avatar
jubianchi committed
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
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
Nihad Abbasov's avatar
Nihad Abbasov committed
106
      get ":id/issues" do
107
        issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user)
jubianchi's avatar
jubianchi committed
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

112 113 114
        unless params[:milestone].nil?
          issues = filter_issues_milestone(issues, params[:milestone])
        end
jubianchi's avatar
jubianchi committed
115

116
        issues.reorder(issuable_order_by => issuable_sort)
117
        present paginate(issues), with: Entities::Issue, current_user: current_user
Nihad Abbasov's avatar
Nihad Abbasov committed
118 119 120 121 122
      end

      # Get a single project issue
      #
      # Parameters:
123
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
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
Nihad Abbasov's avatar
Nihad Abbasov committed
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
Nihad Abbasov's avatar
Nihad Abbasov committed
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
Nihad Abbasov's avatar
Nihad Abbasov committed
143 144
      # Example Request:
      #   POST /projects/:id/issues
145
      post ':id/issues' do
146
        required_attributes! [:title]
147

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

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

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

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

161
        if issue.spam?
162 163
          render_api_error!({ error: 'Spam detected' }, 400)
        end
164

165
        if issue.valid?
166
          present issue, with: Entities::Issue, current_user: current_user
167
        else
168
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
169 170 171 172 173 174
        end
      end

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

194
        # Validate label names in advance
195 196
        if (errors = validate_label_params(params)).any?
          render_api_error!({ labels: errors }, 400)
197 198
        end

199
        attrs[:labels] = params[:labels] if params[:labels]
200

201
        issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
202

203
        if issue.valid?
204
          present issue, with: Entities::Issue, current_user: current_user
205
        else
206
          render_validation_error!(issue)
Nihad Abbasov's avatar
Nihad Abbasov committed
207 208 209
        end
      end

210 211 212
      # Move an existing issue
      #
      # Parameters:
213 214 215
      #  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
216 217
      # Example Request:
      #   POST /projects/:id/issues/:issue_id/move
218 219
      post ':id/issues/:issue_id/move' do
        required_attributes! [:to_project_id]
220 221

        issue = user_project.issues.find(params[:issue_id])
222
        new_project = Project.find(params[:to_project_id])
223 224 225

        begin
          issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
226
          present issue, with: Entities::Issue, current_user: current_user
227 228 229 230 231 232
        rescue ::Issues::MoveService::MoveError => error
          render_api_error!(error.message, 400)
        end
      end

      #
233
      # Delete a project issue
Nihad Abbasov's avatar
Nihad Abbasov committed
234 235
      #
      # Parameters:
236
      #   id (required) - The ID of a project
Nihad Abbasov's avatar
Nihad Abbasov committed
237 238 239 240
      #   issue_id (required) - The ID of a project issue
      # Example Request:
      #   DELETE /projects/:id/issues/:issue_id
      delete ":id/issues/:issue_id" do
241
        issue = user_project.issues.find_by(id: params[:issue_id])
242

243
        authorize!(:destroy_issue, issue)
244
        issue.destroy
Nihad Abbasov's avatar
Nihad Abbasov committed
245 246 247 248
      end
    end
  end
end