diff --git a/CHANGELOG b/CHANGELOG index 15942e396d7..5c57b14ad15 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 7.4.0 - Do not delete tmp/repositories itself during clean-up, only its contents - Support for backup uploads to remote storage - Prevent notes polling when there are not notes + - API: filter project issues by milestone (Julien Bianchi) v 7.3.1 - Fix ref parsing in Gitlab::GitAccess diff --git a/doc/api/issues.md b/doc/api/issues.md index a935b146d37..ceeb683a6bf 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -95,6 +95,8 @@ GET /projects/:id/issues?state=closed GET /projects/:id/issues?labels=foo GET /projects/:id/issues?labels=foo,bar GET /projects/:id/issues?labels=foo,bar&state=opened +GET /projects/:id/issues?milestone=1.0.0 +GET /projects/:id/issues?milestone=1.0.0&state=opened ``` Parameters: @@ -102,6 +104,7 @@ Parameters: - `id` (required) - The ID of a project - `state` (optional) - Return `all` issues or just those that are `opened` or `closed` - `labels` (optional) - Comma-separated list of label names +- `milestone` (optional) - Milestone title ## Single issue diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 30170c657ba..d2828b24c36 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -4,7 +4,7 @@ module API before { authenticate! } helpers do - def filter_issues_state(issues, state = nil) + def filter_issues_state(issues, state) case state when 'opened' then issues.opened when 'closed' then issues.closed @@ -13,7 +13,11 @@ module API end def filter_issues_labels(issues, labels) - issues.includes(:labels).where("labels.title" => labels.split(',')) + issues.includes(:labels).where('labels.title' => labels.split(',')) + end + + def filter_issues_milestone(issues, milestone) + issues.includes(:milestone).where('milestones.title' => milestone) end end @@ -48,19 +52,24 @@ module API # id (required) - The ID of a project # state (optional) - Return "opened" or "closed" issues # labels (optional) - Comma-separated list of label names + # milestone (optional) - Milestone title # # Example Requests: # GET /projects/:id/issues # GET /projects/:id/issues?state=opened # GET /projects/:id/issues?state=closed - # GET /projects/:id/issues # GET /projects/:id/issues?labels=foo # GET /projects/:id/issues?labels=foo,bar # GET /projects/:id/issues?labels=foo,bar&state=opened + # GET /projects/:id/issues?milestone=1.0.0 + # GET /projects/:id/issues?milestone=1.0.0&state=closed get ":id/issues" do 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? + unless params[:milestone].nil? + issues = filter_issues_milestone(issues, params[:milestone]) + end issues = issues.order('issues.id DESC') present paginate(issues), with: Entities::Issue diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 9876452f81d..775d7b4e18d 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -4,12 +4,29 @@ describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } - let!(:closed_issue) { create(:closed_issue, author: user, assignee: user, project: project, state: :closed) } - let!(:issue) { create(:issue, author: user, assignee: user, project: project) } + let!(:closed_issue) do + create :closed_issue, + author: user, + assignee: user, + project: project, + state: :closed, + milestone: milestone + end + let!(:issue) do + create :issue, + author: user, + assignee: user, + project: project, + milestone: milestone + end let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) end let!(:label_link) { create(:label_link, label: label, target: issue) } + let!(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let!(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end before { project.team << [user, :reporter] } @@ -102,15 +119,18 @@ describe API::API, api: true do end describe "GET /projects/:id/issues" do + let(:base_url) { "/projects/#{project.id}" } + let(:title) { milestone.title } + it "should return project issues" do - get api("/projects/#{project.id}/issues", user) + get api("#{base_url}/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title end it 'should return an array of labeled project issues' do - get api("/projects/#{project.id}/issues?labels=#{label.title}", user) + get api("#{base_url}/issues?labels=#{label.title}", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 1 @@ -118,7 +138,7 @@ describe API::API, api: true do end it 'should return an array of labeled project issues when at least one label matches' do - get api("/projects/#{project.id}/issues?labels=#{label.title},foo,bar", user) + get api("#{base_url}/issues?labels=#{label.title},foo,bar", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 1 @@ -126,11 +146,43 @@ describe API::API, api: true do end it 'should return an empty array if no project issue matches labels' do - get api("/projects/#{project.id}/issues?labels=foo,bar", user) + get api("#{base_url}/issues?labels=foo,bar", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 0 end + + it 'should return an empty array if no issue matches milestone' do + get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) + response.status.should == 200 + json_response.should be_an Array + json_response.length.should == 0 + end + + it 'should return an empty array if milestone does not exist' do + get api("#{base_url}/issues?milestone=foo", user) + response.status.should == 200 + json_response.should be_an Array + json_response.length.should == 0 + end + + it 'should return an array of issues in given milestone' do + get api("#{base_url}/issues?milestone=#{title}", user) + response.status.should == 200 + json_response.should be_an Array + json_response.length.should == 2 + json_response.first['id'].should == issue.id + json_response.second['id'].should == closed_issue.id + end + + it 'should return an array of issues matching state in milestone' do + get api("#{base_url}/issues?milestone=#{milestone.title}"\ + '&state=closed', user) + response.status.should == 200 + json_response.should be_an Array + json_response.length.should == 1 + json_response.first['id'].should == closed_issue.id + end end describe "GET /projects/:id/issues/:issue_id" do