GitLab wurde erfolgreich aktualisiert. Durch regelmäßige Updates bleibt das THM GitLab sicher. Danke für Ihre Geduld.

Commit c5c9dce2 authored by Felipe Artur's avatar Felipe Artur

Add group milestones API endpoint

parent 05329d4a
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
# #
class IssuableFinder class IssuableFinder
include CreatedAtFilter include CreatedAtFilter
NONE = '0'.freeze NONE = '0'.freeze
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
......
...@@ -29,7 +29,8 @@ following locations: ...@@ -29,7 +29,8 @@ following locations:
- [Keys](keys.md) - [Keys](keys.md)
- [Labels](labels.md) - [Labels](labels.md)
- [Merge Requests](merge_requests.md) - [Merge Requests](merge_requests.md)
- [Milestones](milestones.md) - [Project milestones](milestones.md)
- [Group milestones](group_milestones.md)
- [Namespaces](namespaces.md) - [Namespaces](namespaces.md)
- [Notes](notes.md) (comments) - [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md) - [Notification settings](notification_settings.md)
......
# Group milestones API
## List group milestones
Returns a list of group milestones.
```
GET /groups/:id/milestones
GET /groups/:id/milestones?iids=42
GET /groups/:id/milestones?iids[]=42&iids[]=43
GET /groups/:id/milestones?state=active
GET /groups/:id/milestones?state=closed
GET /groups/:id/milestones?search=version
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` |
| `state` | string | optional | Return only `active` or `closed` milestones` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/milestones
```
Example Response:
```json
[
{
"id": 12,
"iid": 3,
"group_id": 16,
"title": "10.0",
"description": "Version",
"due_date": "2013-11-29",
"start_date": "2013-11-10",
"state": "active",
"updated_at": "2013-10-02T09:24:18Z",
"created_at": "2013-10-02T09:24:18Z"
}
]
```
## Get single milestone
Gets a single group milestone.
```
GET /groups/:id/milestones/:milestone_id
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of the group milestone
## Create new milestone
Creates a new group milestone.
```
POST /groups/:id/milestones
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `title` (required) - The title of an milestone
- `description` (optional) - The description of the milestone
- `due_date` (optional) - The due date of the milestone
- `start_date` (optional) - The start date of the milestone
## Edit milestone
Updates an existing group milestone.
```
PUT /groups/:id/milestones/:milestone_id
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a group milestone
- `title` (optional) - The title of a milestone
- `description` (optional) - The description of a milestone
- `due_date` (optional) - The due date of the milestone
- `start_date` (optional) - The start date of the milestone
- `state_event` (optional) - The state event of the milestone (close|activate)
## Get all issues assigned to a single milestone
Gets all issues assigned to a single group milestone.
```
GET /groups/:id/milestones/:milestone_id/issues
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a group milestone
## Get all merge requests assigned to a single milestone
Gets all merge requests assigned to a single group milestone.
```
GET /groups/:id/milestones/:milestone_id/merge_requests
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a group milestone
...@@ -109,7 +109,8 @@ class API < Grape::API ...@@ -109,7 +109,8 @@ class API < Grape::API
mount ::API::Members mount ::API::Members
mount ::API::MergeRequestDiffs mount ::API::MergeRequestDiffs
mount ::API::MergeRequests mount ::API::MergeRequests
mount ::API::Milestones mount ::API::ProjectMilestones
mount ::API::GroupMilestones
mount ::API::Namespaces mount ::API::Namespaces
mount ::API::Notes mount ::API::Notes
mount ::API::NotificationSettings mount ::API::NotificationSettings
......
...@@ -269,8 +269,8 @@ class RepoDiff < Grape::Entity ...@@ -269,8 +269,8 @@ class RepoDiff < Grape::Entity
class Milestone < Grape::Entity class Milestone < Grape::Entity
expose :id, :iid expose :id, :iid
expose(:project_id) { |entity| entity&.project_id } expose :project_id, if: -> (entity, options) { entity&.project_id }
expose(:group_id) { |entity| entity&.group_id } expose :group_id, if: -> (entity, options) { entity&.group_id }
expose :title, :description expose :title, :description
expose :state, :created_at, :updated_at expose :state, :created_at, :updated_at
expose :due_date expose :due_date
......
module API
class GroupMilestones < Grape::API
include MilestoneResponses
include PaginationParams
before do
authenticate!
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Get a list of group milestones' do
success Entities::Milestone
end
params do
use :list_params
end
get ":id/milestones" do
list_milestones_for(user_group)
end
desc 'Get a single group milestone' do
success Entities::Milestone
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
end
get ":id/milestones/:milestone_id" do
authorize! :read_group, user_group
get_milestone_for(user_group)
end
desc 'Create a new group milestone' do
success Entities::Milestone
end
params do
requires :title, type: String, desc: 'The title of the milestone'
use :optional_params
end
post ":id/milestones" do
authorize! :admin_milestones, user_group
create_milestone_for(user_group)
end
desc 'Update an existing group milestone' do
success Entities::Milestone
end
params do
use :update_params
end
put ":id/milestones/:milestone_id" do
authorize! :admin_milestones, user_group
update_milestone_for(user_group)
end
desc 'Get all issues for a single group milestone' do
success Entities::IssueBasic
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
use :pagination
end
get ":id/milestones/:milestone_id/issues" do
milestone_issuables_for(user_group, :issue)
end
desc 'Get all merge requests for a single group milestone' do
detail 'This feature was introduced in GitLab 9.'
success Entities::MergeRequestBasic
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
use :pagination
end
get ':id/milestones/:milestone_id/merge_requests' do
milestone_issuables_for(user_group, :merge_request)
end
end
end
end
...@@ -25,6 +25,10 @@ def sudo? ...@@ -25,6 +25,10 @@ def sudo?
initial_current_user != current_user initial_current_user != current_user
end end
def user_group
@group ||= find_group!(params[:id])
end
def user_project def user_project
@project ||= find_project!(params[:id]) @project ||= find_project!(params[:id])
end end
......
module API
module MilestoneResponses
extend ActiveSupport::Concern
included do
helpers do
params :optional_params do
optional :description, type: String, desc: 'The description of the milestone'
optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
end
params :list_params do
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination
end
params :update_params do
requires :milestone_id, type: Integer, desc: 'The milestone ID number'
optional :title, type: String, desc: 'The title of the milestone'
optional :state_event, type: String, values: %w[close activate],
desc: 'The state event of the milestone '
use :optional_params
at_least_one_of :title, :description, :due_date, :state_event
end
def list_milestones_for(parent)
milestones = parent.milestones
milestones = Milestone.filter_by_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
milestones = filter_by_search(milestones, params[:search]) if params[:search]
present paginate(milestones), with: Entities::Milestone
end
def get_milestone_for(parent)
milestone = parent.milestones.find(params[:milestone_id])
present milestone, with: Entities::Milestone
end
def create_milestone_for(parent)
milestone = ::Milestones::CreateService.new(parent, current_user, declared_params).execute
if milestone.valid?
present milestone, with: Entities::Milestone
else
render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end
end
def update_milestone_for(parent)
milestone = parent.milestones.find(params.delete(:milestone_id))
milestone_params = declared_params(include_missing: false)
milestone = ::Milestones::UpdateService.new(parent, current_user, milestone_params).execute(milestone)
if milestone.valid?
present milestone, with: Entities::Milestone
else
render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end
end
def milestone_issuables_for(parent, type)
milestone = parent.milestones.find(params[:milestone_id])
finder_klass, entity = get_finder_and_entity(type)
params = build_finder_params(milestone, parent)
issuables = finder_klass.new(current_user, params).execute
present paginate(issuables), with: entity, current_user: current_user
end
def build_finder_params(milestone, parent)
finder_params = { milestone_title: milestone.title, sort: 'label_priority' }
if parent.is_a?(Group)
finder_params.merge(group_id: parent.id)
else
finder_params.merge(project_id: parent.id)
end
end
def get_finder_and_entity(type)
if type == :issue
[IssuesFinder, Entities::IssueBasic]
else
[MergeRequestsFinder, Entities::MergeRequestBasic]
end
end
end
end
end
end
module API module API
class Milestones < Grape::API class ProjectMilestones < Grape::API
include PaginationParams include PaginationParams
include MilestoneResponses
before { authenticate! } before do
authenticate!
helpers do
def filter_milestones_state(milestones, state)
case state
when 'active' then milestones.active
when 'closed' then milestones.closed
else milestones
end
end
params :optional_params do
optional :description, type: String, desc: 'The description of the milestone'
optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
end
end end
params do params do
...@@ -28,21 +15,12 @@ def filter_milestones_state(milestones, state) ...@@ -28,21 +15,12 @@ def filter_milestones_state(milestones, state)
success Entities::Milestone success Entities::Milestone
end end
params do params do
optional :state, type: String, values: %w[active closed all], default: 'all', use :list_params
desc: 'Return "active", "closed", or "all" milestones'
optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination
end end
get ":id/milestones" do get ":id/milestones" do
authorize! :read_milestone, user_project authorize! :read_milestone, user_project
milestones = user_project.milestones list_milestones_for(user_project)
milestones = filter_milestones_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
milestones = filter_by_search(milestones, params[:search]) if params[:search]
present paginate(milestones), with: Entities::Milestone
end end
desc 'Get a single project milestone' do desc 'Get a single project milestone' do
...@@ -54,8 +32,7 @@ def filter_milestones_state(milestones, state) ...@@ -54,8 +32,7 @@ def filter_milestones_state(milestones, state)
get ":id/milestones/:milestone_id" do get ":id/milestones/:milestone_id" do
authorize! :read_milestone, user_project authorize! :read_milestone, user_project
milestone = user_project.milestones.find(params[:milestone_id]) get_milestone_for(user_project)
present milestone, with: Entities::Milestone
end end
desc 'Create a new project milestone' do desc 'Create a new project milestone' do
...@@ -68,38 +45,19 @@ def filter_milestones_state(milestones, state) ...@@ -68,38 +45,19 @@ def filter_milestones_state(milestones, state)
post ":id/milestones" do post ":id/milestones" do
authorize! :admin_milestone, user_project authorize! :admin_milestone, user_project
milestone = ::Milestones::CreateService.new(user_project, current_user, declared_params).execute create_milestone_for(user_project)
if milestone.valid?
present milestone, with: Entities::Milestone
else
render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end
end end
desc 'Update an existing project milestone' do desc 'Update an existing project milestone' do
success Entities::Milestone success Entities::Milestone
end end
params do params do
requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' use :update_params
optional :title, type: String, desc: 'The title of the milestone'
optional :state_event, type: String, values: %w[close activate],
desc: 'The state event of the milestone '
use :optional_params
at_least_one_of :title, :description, :due_date, :state_event
end end
put ":id/milestones/:milestone_id" do put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project authorize! :admin_milestone, user_project
milestone = user_project.milestones.find(params.delete(:milestone_id))
milestone_params = declared_params(include_missing: false) update_milestone_for(user_project)
milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone)
if milestone.valid?
present milestone, with: Entities::Milestone
else
render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end
end end
desc 'Get all issues for a single project milestone' do desc 'Get all issues for a single project milestone' do
...@@ -112,16 +70,7 @@ def filter_milestones_state(milestones, state) ...@@ -112,16 +70,7 @@ def filter_milestones_state(milestones, state)
get ":id/milestones/:milestone_id/issues" do get ":id/milestones/:milestone_id/issues" do
authorize! :read_milestone, user_project authorize! :read_milestone, user_project
milestone = user_project.milestones.find(params[:milestone_id]) milestone_issuables_for(user_project, :issue)
finder_params = {
project_id: user_project.id,
milestone_title: milestone.title,
sort: 'label_priority'
}
issues = IssuesFinder.new(current_user, finder_params).execute
present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
end end
desc 'Get all merge requests for a single project milestone' do desc 'Get all merge requests for a single project milestone' do
...@@ -135,19 +84,7 @@ def filter_milestones_state(milestones, state) ...@@ -135,19 +84,7 @@ def filter_milestones_state(milestones, state)
get ':id/milestones/:milestone_id/merge_requests' do get ':id/milestones/:milestone_id/merge_requests' do
authorize! :read_milestone, user_project authorize! :read_milestone, user_project
milestone = user_project.milestones.find(params[:milestone_id]) milestone_issuables_for(user_project, :merge_request)
finder_params = {
project_id: user_project.id,
milestone_title: milestone.title,
sort: 'label_priority'
}
merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
present paginate(merge_requests),
with: Entities::MergeRequestBasic,
current_user: current_user,
project: user_project
end end
end end
end end
......
require 'spec_helper'
describe API::GroupMilestones do
let(:user) { create(:user) }
let(:group) { create(:group, :private) }
let(:project) { create(:empty_project, namespace: group) }
let!(:group_member) { create(:group_member, group: group, user: user) }
let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') }
it_behaves_like 'group and project milestones', "/groups/:id/milestones" do
let(:route) { "/groups/#{group.id}/milestones" }
end
def setup_for_group
context_group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
context_group.add_developer(user)
public_project.update(namespace: context_group)
context_group.reload
end
end
require 'spec_helper'
describe API::ProjectMilestones do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
before do
project.team << [user, :developer]
end
it_behaves_like 'group and project milestones', "/projects/:id/milestones" do
let(:route) { "/projects/#{project.id}/milestones" }
end
describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
it 'creates an activity event when an milestone is closed' do
expect(Event).to receive(:create)
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
state_event: 'close'
end
end
end
require 'spec_helper' shared_examples_for 'group and project milestones' do |route_definition|
let(:resource_route) { "#{route}/#{milestone.id}" }
describe API::Milestones do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }