From b70d23c25a4bc54fda22135b0a76bae102cfd88b Mon Sep 17 00:00:00 2001 From: Patrick Derichs Date: Fri, 7 Jun 2019 11:15:55 +0200 Subject: [PATCH] Add task count and completed count to responses of Issue and MR Add spec for task_completion_status Add test cases for task_completion_status result Extracted shared samples Add new spec file for task completion status response Fix style errors Add changelog entry Changed samples to Hashes Remove test for successful request Remove not nil expectation Add task_completion_status to api documentation for issues Add task_completion_status to api documentation for merge_requests Refactor spec so it just requests one specific item Add task_completion_status to Taskable Simplified task completion status in entities Refactor spec so it separates status code check and content check Fix spec description text and field name --- app/models/concerns/taskable.rb | 7 ++ .../51636-task-list-api-pderichs.yml | 5 ++ doc/api/issues.md | 50 +++++++++-- doc/api/merge_requests.md | 76 +++++++++++++---- lib/api/entities.rb | 4 + .../api/task_completion_status_spec.rb | 85 +++++++++++++++++++ 6 files changed, 206 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/51636-task-list-api-pderichs.yml create mode 100644 spec/requests/api/task_completion_status_spec.rb diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index 2f0e078c807..b42adad94ba 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -75,4 +75,11 @@ module Taskable def task_status_short task_status(short: true) end + + def task_completion_status + @task_completion_status ||= { + count: tasks.summary.item_count, + completed_count: tasks.summary.complete_count + } + end end diff --git a/changelogs/unreleased/51636-task-list-api-pderichs.yml b/changelogs/unreleased/51636-task-list-api-pderichs.yml new file mode 100644 index 00000000000..f18a0936ab2 --- /dev/null +++ b/changelogs/unreleased/51636-task-list-api-pderichs.yml @@ -0,0 +1,5 @@ +--- +title: Add task count and completed count to responses of Issue and MR +merge_request: 28859 +author: +type: added diff --git a/doc/api/issues.md b/doc/api/issues.md index 4fb3626f637..0d96cfa1b21 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -135,7 +135,11 @@ Example response: "award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji", "project":"http://example.com/api/v4/projects/1" }, - "subscribed": false + "subscribed": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ] ``` @@ -265,7 +269,11 @@ Example response: "award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji", "project":"http://example.com/api/v4/projects/4" }, - "subscribed": false + "subscribed": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ] ``` @@ -403,7 +411,11 @@ Example response: "award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji", "project":"http://example.com/api/v4/projects/4" }, - "subscribed": false + "subscribed": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ] ``` @@ -500,6 +512,10 @@ Example response: "notes": "http://example.com/api/v4/projects/1/issues/2/notes", "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji", "project": "http://example.com/api/v4/projects/1" + }, + "task_completion_status":{ + "count":0, + "completed_count":0 } } ``` @@ -583,6 +599,10 @@ Example response: "notes": "http://example.com/api/v4/projects/1/issues/2/notes", "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji", "project": "http://example.com/api/v4/projects/1" + }, + "task_completion_status":{ + "count":0, + "completed_count":0 } } ``` @@ -674,6 +694,10 @@ Example response: "notes": "http://example.com/api/v4/projects/1/issues/2/notes", "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji", "project": "http://example.com/api/v4/projects/1" + }, + "task_completion_status":{ + "count":0, + "completed_count":0 } } ``` @@ -780,6 +804,10 @@ Example response: "notes": "http://example.com/api/v4/projects/1/issues/2/notes", "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji", "project": "http://example.com/api/v4/projects/1" + }, + "task_completion_status":{ + "count":0, + "completed_count":0 } } ``` @@ -865,6 +893,10 @@ Example response: "notes": "http://example.com/api/v4/projects/1/issues/2/notes", "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji", "project": "http://example.com/api/v4/projects/1" + }, + "task_completion_status":{ + "count":0, + "completed_count":0 } } ``` @@ -931,7 +963,11 @@ Example response: "due_date": null, "web_url": "http://example.com/example/example/issues/12", "confidential": false, - "discussion_locked": false + "discussion_locked": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` @@ -1029,7 +1065,11 @@ Example response: "due_date": null, "web_url": "http://example.com/example/example/issues/110", "confidential": false, - "discussion_locked": false + "discussion_locked": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", "body": "Vel voluptas atque dicta mollitia adipisci qui at.", diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 9529a9ec1f5..96a956ad03a 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -138,7 +138,11 @@ Parameters: "human_time_estimate": null, "human_total_time_spent": null }, - "squash": false + "squash": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ] ``` @@ -280,7 +284,11 @@ Parameters: "human_time_estimate": null, "human_total_time_spent": null }, - "squash": false + "squash": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ] ``` @@ -410,7 +418,11 @@ Parameters: "human_time_estimate": null, "human_total_time_spent": null }, - "squash": false + "squash": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ] ``` @@ -545,7 +557,11 @@ Parameters: "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, "diverged_commits_count": 2, - "rebase_in_progress": false + "rebase_in_progress": false, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` @@ -579,7 +595,7 @@ Parameters: "state": "active", "avatar_url": "http://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80&d=identicon", "web_url": "http://localhost/user2" - }, + } ] ``` @@ -702,7 +718,11 @@ Parameters: "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "task_completion_status":{ + "count":0, + "completed_count":0 + }, "changes": [ { "old_path": "VERSION", @@ -865,7 +885,11 @@ POST /projects/:id/merge_requests "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, - "diverged_commits_count": 2 + "diverged_commits_count": 2, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` @@ -1002,7 +1026,11 @@ Must include at least one non-required attribute from above. "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, - "diverged_commits_count": 2 + "diverged_commits_count": 2, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` @@ -1155,13 +1183,17 @@ Parameters: "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, - "diverged_commits_count": 2 + "diverged_commits_count": 2, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` ## Returns the up to date merge-ref HEAD commit -Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge` +Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge` ref, of the target project repository, if possible. This ref will have the state the target branch would have if a regular merge action was taken. @@ -1309,7 +1341,11 @@ Parameters: "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, - "diverged_commits_count": 2 + "diverged_commits_count": 2, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` @@ -1345,7 +1381,7 @@ If the rebase operation is ongoing, the response will include the following: ```json { - "rebase_in_progress": true + "rebase_in_progress": true, "merge_error": null } ``` @@ -1356,7 +1392,7 @@ the following: ```json { "rebase_in_progress": false, - "merge_error": null, + "merge_error": null } ``` @@ -1365,7 +1401,7 @@ If the rebase operation fails, the response will include the following: ```json { "rebase_in_progress": false, - "merge_error": "Rebase failed. Please rebase locally", + "merge_error": "Rebase failed. Please rebase locally" } ``` @@ -1572,7 +1608,11 @@ Example response: "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, - "diverged_commits_count": 2 + "diverged_commits_count": 2, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` @@ -1701,7 +1741,11 @@ Example response: "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, - "diverged_commits_count": 2 + "diverged_commits_count": 2, + "task_completion_status":{ + "count":0, + "completed_count":0 + } } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index b1b6e7bd7b9..f8b950cb55d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -576,6 +576,8 @@ module API expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue| issue end + + expose :task_completion_status end class Issue < IssueBasic @@ -724,6 +726,8 @@ module API end expose :squash + + expose :task_completion_status end class MergeRequest < MergeRequestBasic diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb new file mode 100644 index 00000000000..ee2531197b1 --- /dev/null +++ b/spec/requests/api/task_completion_status_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'task completion status response' do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + shared_examples 'taskable completion status provider' do |path| + samples = [ + { + description: '', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: 'Lorem ipsum', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: %{- [ ] task 1 + - [x] task 2 }, + expected_count: 2, + expected_completed_count: 1 + }, + { + description: %{- [ ] task 1 + - [ ] task 2 }, + expected_count: 2, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1 + - [x] task 2 }, + expected_count: 2, + expected_completed_count: 2 + }, + { + description: %{- [ ] task 1}, + expected_count: 1, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1}, + expected_count: 1, + expected_completed_count: 1 + } + ] + samples.each do |sample_data| + context "with a description of #{sample_data[:description].inspect}" do + before do + taskable.update!(description: sample_data[:description]) + + get api("#{path}?iids[]=#{taskable.iid}", user) + end + + it { expect(response).to have_gitlab_http_status(200) } + + it 'returns the expected results' do + expect(json_response).to be_an Array + expect(json_response).not_to be_empty + + task_completion_status = json_response.first['task_completion_status'] + expect(task_completion_status['count']).to eq(sample_data[:expected_count]) + expect(task_completion_status['completed_count']).to eq(sample_data[:expected_completed_count]) + end + end + end + end + + context 'task list completion status for issues' do + it_behaves_like 'taskable completion status provider', '/issues' do + let(:taskable) { create(:issue, project: project, author: user) } + end + end + + context 'task list completion status for merge_requests' do + it_behaves_like 'taskable completion status provider', '/merge_requests' do + let(:taskable) { create(:merge_request, source_project: project, target_project: project, author: user) } + end + end +end