Commit afb1bf0b authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into rename-builds-controller

* upstream/master: (63 commits)
  Update docs related to protected actions
  Add changelog for protected branches abilities fix
  Ask for an example project for bug reports
  Center loading spinner in issuable filters
  Fix chat commands specs related to protected actions
  Fix builds controller specs related to protected actions
  Fix pipeline retry specs related to protected actions
  Fix environment model specs related to protected actions
  Fix build factory specs related to protected actions
  Fix job play service specs related to protected actions
  Fix play status specs related to protected actions
  Fix deploy chat command specs for protected actions
  Fix environment specs related to protected actions
  Fix pipeline processing specs related to protected actions
  Fix build entity specs related to protected actions
  Check only a merge ability for protected actions
  Add tag_list param to project api
  Allow PostReceivePack to be enabled with Gitaly
  Remove some deprecated methods
  Add :owned param to ProjectFinder
  ...
parents 258cdd14 4ad85b22
...@@ -20,6 +20,12 @@ Please remove this notice if you're confident your issue isn't a duplicate. ...@@ -20,6 +20,12 @@ Please remove this notice if you're confident your issue isn't a duplicate.
(How one can reproduce the issue - this is very important) (How one can reproduce the issue - this is very important)
### Example Project
(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
### What is the current *bug* behavior? ### What is the current *bug* behavior?
(What actually happens) (What actually happens)
......
...@@ -475,4 +475,5 @@ ...@@ -475,4 +475,5 @@
.filter-dropdown-loading { .filter-dropdown-loading {
padding: 8px 16px; padding: 8px 16px;
text-align: center;
} }
...@@ -24,7 +24,7 @@ class DashboardController < Dashboard::ApplicationController ...@@ -24,7 +24,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events def load_events
projects = projects =
if params[:filter] == "starred" if params[:filter] == "starred"
current_user.viewable_starred_projects ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute
else else
current_user.authorized_projects current_user.authorized_projects
end end
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
# project_ids_relation: int[] - project ids to use # project_ids_relation: int[] - project ids to use
# params: # params:
# trending: boolean # trending: boolean
# owned: boolean
# non_public: boolean # non_public: boolean
# starred: boolean # starred: boolean
# sort: string # sort: string
...@@ -28,13 +29,17 @@ class ProjectsFinder < UnionFinder ...@@ -28,13 +29,17 @@ class ProjectsFinder < UnionFinder
def execute def execute
items = init_collection items = init_collection
items = by_ids(items) items = items.map do |item|
item = by_ids(item)
item = by_personal(item)
item = by_starred(item)
item = by_trending(item)
item = by_visibilty_level(item)
item = by_tags(item)
item = by_search(item)
by_archived(item)
end
items = union(items) items = union(items)
items = by_personal(items)
items = by_visibilty_level(items)
items = by_tags(items)
items = by_search(items)
items = by_archived(items)
sort(items) sort(items)
end end
...@@ -43,10 +48,8 @@ class ProjectsFinder < UnionFinder ...@@ -43,10 +48,8 @@ class ProjectsFinder < UnionFinder
def init_collection def init_collection
projects = [] projects = []
if params[:trending].present? if params[:owned].present?
projects << Project.trending projects << current_user.owned_projects if current_user
elsif params[:starred].present? && current_user
projects << current_user.viewable_starred_projects
else else
projects << current_user.authorized_projects if current_user projects << current_user.authorized_projects if current_user
projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present? projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
...@@ -56,7 +59,7 @@ class ProjectsFinder < UnionFinder ...@@ -56,7 +59,7 @@ class ProjectsFinder < UnionFinder
end end
def by_ids(items) def by_ids(items)
project_ids_relation ? items.map { |item| item.where(id: project_ids_relation) } : items project_ids_relation ? items.where(id: project_ids_relation) : items
end end
def union(items) def union(items)
...@@ -67,6 +70,14 @@ class ProjectsFinder < UnionFinder ...@@ -67,6 +70,14 @@ class ProjectsFinder < UnionFinder
(params[:personal].present? && current_user) ? items.personal(current_user) : items (params[:personal].present? && current_user) ? items.personal(current_user) : items
end end
def by_starred(items)
(params[:starred].present? && current_user) ? items.starred_by(current_user) : items
end
def by_trending(items)
params[:trending].present? ? items.trending : items
end
def by_visibilty_level(items) def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end end
......
...@@ -24,6 +24,10 @@ module Ci ...@@ -24,6 +24,10 @@ module Ci
owner == current_user owner == current_user
end end
def own!(user)
update(owner: user)
end
def inactive? def inactive?
!active? !active?
end end
......
...@@ -242,6 +242,7 @@ class Project < ActiveRecord::Base ...@@ -242,6 +242,7 @@ class Project < ActiveRecord::Base
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) } scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
...@@ -350,10 +351,6 @@ class Project < ActiveRecord::Base ...@@ -350,10 +351,6 @@ class Project < ActiveRecord::Base
where("projects.id IN (#{union.to_sql})") where("projects.id IN (#{union.to_sql})")
end end
def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.string_options[level])
end
def search_by_title(query) def search_by_title(query)
pattern = "%#{query}%" pattern = "%#{query}%"
table = Project.arel_table table = Project.arel_table
......
...@@ -557,12 +557,6 @@ class User < ActiveRecord::Base ...@@ -557,12 +557,6 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).where(id: projects) authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
end end
def viewable_starred_projects
starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (?)",
[Project::PUBLIC, Project::INTERNAL],
authorized_projects.select(:project_id))
end
def owned_projects def owned_projects
@owned_projects ||= @owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?', Project.where('namespace_id IN (?) OR namespace_id = ?',
......
...@@ -23,7 +23,7 @@ module Ci ...@@ -23,7 +23,7 @@ module Ci
!::Gitlab::UserAccess !::Gitlab::UserAccess
.new(user, project: build.project) .new(user, project: build.project)
.can_push_to_branch?(build.ref) .can_merge_to_branch?(build.ref)
end end
end end
end end
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
= icon('caret-down') = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action| - actions.each do |action|
- next unless can?(current_user, :update_build, action)
%li %li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= custom_icon('icon_play') = custom_icon('icon_play')
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= render 'projects/environments/metrics_button', environment: @environment = render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment) - if can?(current_user, :update_environment, @environment)
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
- if can?(current_user, :create_deployment, @environment) && @environment.can_stop? - if can?(current_user, :stop_environment, @environment)
= link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.environments-container .environments-container
......
---
title: Add API support for pipeline schedule
merge_request: 11307
author: dosuken123
---
title: Add tag_list param to project api
merge_request: 11799
author: Ivan Chernov
---
title: Respect merge, instead of push, permissions for protected actions
merge_request: 11648
author:
---
title: Ask for an example project for bug reports
merge_request:
author:
---
title: Improve performance of ProjectFinder used in /projects API endpoint
merge_request: 11666
author:
...@@ -33,6 +33,7 @@ following locations: ...@@ -33,6 +33,7 @@ following locations:
- [Notification settings](notification_settings.md) - [Notification settings](notification_settings.md)
- [Pipelines](pipelines.md) - [Pipelines](pipelines.md)
- [Pipeline Triggers](pipeline_triggers.md) - [Pipeline Triggers](pipeline_triggers.md)
- [Pipeline Schedules](pipeline_schedules.md)
- [Projects](projects.md) including setting Webhooks - [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md) - [Project Access Requests](access_requests.md)
- [Project Members](members.md) - [Project Members](members.md)
......
# Pipeline schedules
You can read more about [pipeline schedules](../user/project/pipelines/schedules.md).
## Get all pipeline schedules
Get a list of the pipeline schedules of a project.
```
GET /projects/:id/pipeline_schedules
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | no | The scope of pipeline schedules, one of: `active`, `inactive` |
```sh
curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules"
```
```json
[
{
"id": 13,
"description": "Test schedule pipeline",
"ref": "master",
"cron": "* * * * *",
"cron_timezone": "Asia/Tokyo",
"next_run_at": "2017-05-19T13:41:00.000Z",
"active": true,
"created_at": "2017-05-19T13:31:08.849Z",
"updated_at": "2017-05-19T13:40:17.727Z",
"owner": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
}
}
]
```
## Get a single pipeline schedule
Get the pipeline schedule of a project.
```
GET /projects/:id/pipeline_schedules/:pipeline_schedule_id
```
| Attribute | Type | required | Description |
|--------------|---------|----------|--------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
```sh
curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13"
```
```json
{
"id": 13,
"description": "Test schedule pipeline",
"ref": "master",
"cron": "* * * * *",
"cron_timezone": "Asia/Tokyo",
"next_run_at": "2017-05-19T13:41:00.000Z",
"active": true,
"created_at": "2017-05-19T13:31:08.849Z",
"updated_at": "2017-05-19T13:40:17.727Z",
"last_pipeline": {
"id": 332,
"sha": "0e788619d0b5ec17388dffb973ecd505946156db",
"ref": "master",
"status": "pending"
},
"owner": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
}
}
```
## Create a new pipeline schedule
Create a new pipeline schedule of a project.
```
POST /projects/:id/pipeline_schedules
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `description` | string | yes | The description of pipeline schedule |
| `ref` | string | yes | The branch/tag name will be triggered |
| `cron ` | string | yes | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) |
| `cron_timezone ` | string | no | The timezone supproted by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) (default: `'UTC'`) |
| `active ` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially (default: `true`) |
```sh
curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form description="Build packages" --form ref="master" --form cron="0 1 * * 5" --form cron_timezone="UTC" --form active="true" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules"
```
```json
{
"id": 14,
"description": "Build packages",
"ref": "master",
"cron": "0 1 * * 5",
"cron_timezone": "UTC",
"next_run_at": "2017-05-26T01:00:00.000Z",
"active": true,
"created_at": "2017-05-19T13:43:08.169Z",
"updated_at": "2017-05-19T13:43:08.169Z",
"last_pipeline": null,
"owner": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
}
}
```
## Edit a pipeline schedule
Updates the pipeline schedule of a project. Once the update is done, it will be rescheduled automatically.
```
PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
| `description` | string | no | The description of pipeline schedule |
| `ref` | string | no | The branch/tag name will be triggered |
| `cron ` | string | no | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) |
| `cron_timezone ` | string | no | The timezone supproted by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) or `TZInfo::Timezone` (e.g. `America/Los_Angeles`) |
| `active ` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially. |
```sh
curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form cron="0 2 * * *" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13"
```
```json
{
"id": 13,
"description": "Test schedule pipeline",
"ref": "master",
"cron": "0 2 * * *",
"cron_timezone": "Asia/Tokyo",
"next_run_at": "2017-05-19T17:00:00.000Z",
"active": true,
"created_at": "2017-05-19T13:31:08.849Z",
"updated_at": "2017-05-19T13:44:16.135Z",
"last_pipeline": {
"id": 332,
"sha": "0e788619d0b5ec17388dffb973ecd505946156db",
"ref": "master",
"status": "pending"
},
"owner": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
}
}
```
## Take ownership of a pipeline schedule
Update the owner of the pipeline schedule of a project.
```
POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
```sh
curl --request POST --header "PRIVATE-TOKEN: hf2CvZXB9w8Uc5pZKpSB" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/take_ownership"
```
```json
{
"id": 13,
"description": "Test schedule pipeline",
"ref": "master",
"cron": "0 2 * * *",
"cron_timezone": "Asia/Tokyo",
"next_run_at": "2017-05-19T17:00:00.000Z",
"active": true,
"created_at": "2017-05-19T13:31:08.849Z",
"updated_at": "2017-05-19T13:46:37.468Z",
"last_pipeline": {
"id": 332,
"sha": "0e788619d0b5ec17388dffb973ecd505946156db",
"ref": "master",
"status": "pending"
},
"owner": {
"name": "shinya",
"username": "maeda",
"id": 50,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/8ca0a796a679c292e3a11da50f99e801?s=80&d=identicon",
"web_url": "https://gitlab.example.com/maeda"
}
}
```
## Delete a pipeline schedule
Delete the pipeline schedule of a project.
```
DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id
```
| Attribute | Type | required | Description |
|----------------|---------|----------|--------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
```sh
curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13"
```
```json
{
"id": 13,
"description": "Test schedule pipeline",
"ref": "master",
"cron": "0 2 * * *",
"cron_timezone": "Asia/Tokyo",
"next_run_at": "2017-05-19T17:00:00.000Z",
"active": true,
"created_at": "2017-05-19T13:31:08.849Z",
"updated_at": "2017-05-19T13:46:37.468Z",
"last_pipeline": {
"id": 332,
"sha": "0e788619d0b5ec17388dffb973ecd505946156db",
"ref": "master",
"status": "pending"
},
"owner": {
"name": "shinya",
"username": "maeda",
"id": 50,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/8ca0a796a679c292e3a11da50f99e801?s=80&d=identicon",
"web_url": "https://gitlab.example.com/maeda"
}
}
```
...@@ -473,6 +473,7 @@ Parameters: ...@@ -473,6 +473,7 @@ Parameters:
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS | | `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access | | `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
### Create project for user ### Create project for user
...@@ -506,6 +507,7 @@ Parameters: ...@@ -506,6 +507,7 @@ Parameters:
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS | | `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access | | `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
### Edit project ### Edit project
...@@ -538,6 +540,7 @@ Parameters: ...@@ -538,6 +540,7 @@ Parameters:
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS | | `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access | | `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
### Fork project ### Fork project
......
...@@ -591,7 +591,7 @@ Optional manual actions have `allow_failure: true` set by default. ...@@ -591,7 +591,7 @@ Optional manual actions have `allow_failure: true` set by default.
**Manual actions are considered to be write actions, so permissions for **Manual actions are considered to be write actions, so permissions for
protected branches are used when user wants to trigger an action. In other protected branches are used when user wants to trigger an action. In other
words, in order to trigger a manual action assigned to a branch that the words, in order to trigger a manual action assigned to a branch that the
pipeline is running for, user needs to have ability to push to this branch.** pipeline is running for, user needs to have ability to merge to this branch.**
### environment ### environment
......
...@@ -110,6 +110,7 @@ module API ...@@ -110,6 +110,7 @@ module API
mount ::API::Notes mount ::API::Notes
mount ::API::NotificationSettings mount ::API::NotificationSettings
mount ::API::Pipelines mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectHooks mount ::API::ProjectHooks
mount ::API::Projects mount ::API::Projects
mount ::API::ProjectSnippets mount ::API::ProjectSnippets
......
...@@ -686,6 +686,17 @@ module API ...@@ -686,6 +686,17 @@ module API
expose :coverage expose :coverage
end end
class PipelineSchedule < Grape::Entity
expose :id
expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active
expose :created_at, :updated_at
expose :owner, using: Entities::UserBasic
end
class PipelineScheduleDetails < PipelineSchedule
expose :last_pipeline, using: Entities::PipelineBasic
end
class EnvironmentBasic < Grape::Entity class EnvironmentBasic < Grape::Entity
expose :id, :name, :slug, :external_url expose :id, :name, :slug, :external_url
end end
......
...@@ -151,8 +151,8 @@ module API ...@@ -151,8 +151,8 @@ module API
end end
get ":id/projects" do get ":id/projects" do
group = find_group!(params[:id]) group = find_group!(params[:id])
projects = GroupProjectsFinder.new(group: group, current_user: current_user).execute projects = GroupProjectsFinder.new(group: group, current_user: current_user, params: project_finder_params).execute
projects = filter_projects(projects) projects = reorder_projects(projects)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project
present paginate(projects), with: entity, current_user: current_user present paginate(projects), with: entity, current_user: current_user
end end
......
...@@ -256,31 +256,21 @@ module API ...@@ -256,31 +256,21 @@ module API
# project helpers # project helpers
def filter_projects(projects) def reorder_projects(projects)
if params[:membership]
projects = projects.merge(current_user.authorized_projects)
end
if params[:owned]
projects = projects.merge(current_user.owned_projects)
end
if params[:starred]
projects = projects.merge(current_user.starred_projects)
end
if params[:search].present?
projects = projects.search(params[:search])
end
if params[:visibility].present?
projects = projects.search_by_visibility(params[:visibility])
end
projects = projects.where(archived: params[:archived])
projects.reorder(params[:order_by] => params[:sort]) projects.reorder(params[:order_by] => params[:sort])
end end
def project_finder_params
finder_params = {}
finder_params[:owned] = true if params[:owned].present?
finder_params[:non_public] = true if params[:membership].present?
finder_params[:starred] = true if params[:starred].present?
finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
finder_params[:archived] = params[:archived]
finder_params[:search] = params[:search] if params[:search]
finder_params
end
# file helpers # file helpers
def uploaded_file(field, uploads_path) def uploaded_file(field, uploads_path)
......
module API
class PipelineSchedules < Grape::API
include PaginationParams
before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all pipeline schedules' do
success Entities::PipelineSchedule
end
params do
use :pagination
optional :scope, type: String, values: %w[active inactive],
desc: 'The scope of pipeline schedules'
end
get ':id/pipeline_schedules' do
authorize! :read_pipeline_schedule, user_project
schedules = PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
.preload([:owner, :last_pipeline])
present paginate(schedules), with: Entities::PipelineSchedule
end
desc 'Get a single pipeline schedule' do
success Entities::PipelineScheduleDetails
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
present pipeline_schedule, with: Entities::PipelineScheduleDetails
end
desc 'Create a new pipeline schedule' do
success Entities::PipelineScheduleDetails
end
params do
requires :description, type: String, desc: 'The description of pipeline schedule'
requires :ref, type: String, desc: 'The branch/tag name will be triggered'
requires :cron, type: String, desc: 'The cron'
optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone'
optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule'
end
post ':id/pipeline_schedules' do
authorize! :create_pipeline_schedule, user_project
pipeline_schedule = Ci::CreatePipelineScheduleService
.new(user_project, current_user, declared_params(include_missing: false))
.execute
if pipeline_schedule.persisted?
present pipeline_schedule, with: Entities::PipelineScheduleDetails
else
render_validation_error!(pipeline_schedule)
end
end
desc 'Edit a pipeline schedule' do
success Entities::PipelineScheduleDetails
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
optional :description, type: String, desc: 'The description of pipeline schedule'
optional :ref, type: String, desc: 'The branch/tag name will be triggered'
optional :cron, type: String, desc: 'The cron'
optional :cron_timezone, type: String, desc: 'The timezone'
optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
authorize! :update_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
present pipeline_schedule, with: Entities::PipelineScheduleDetails
else
render_validation_error!(pipeline_schedule)
end
end
desc 'Take ownership of a pipeline schedule' do
success Entities::PipelineScheduleDetails
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
authorize! :update_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
if pipeline_schedule.own!(current_user)
present pipeline_schedule, with: Entities::PipelineScheduleDetails
else
render_validation_error!(pipeline_schedule)
end
end
desc 'Delete a pipeline schedule' do
success Entities::PipelineScheduleDetails
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
authorize! :admin_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
status :accepted
present pipeline_schedule.destroy, with: Entities::PipelineScheduleDetails
end
end
helpers do
def pipeline_schedule
@pipeline_schedule ||=
user_project.pipeline_schedules
.preload(:owner, :last_pipeline)
.find_by(id: params.delete(:pipeline_schedule_id))
end
end
end
end
...@@ -21,6 +21,7 @@ module API ...@@ -21,6 +21,7 @@ module API
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
end end
params :optional_params do params :optional_params do
...@@ -67,20 +68,19 @@ module API ...@@ -67,20 +68,19 @@ module API
optional :import_url, type: String, desc: 'URL from which the project is imported' optional :import_url, type: String, desc: 'URL from which the project is imported'
end end
def present_projects(projects, options = {}) def present_projects(options = {})
projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
projects = reorder_projects(projects)
projects = projects.with_statistics if params[:statistics]
projects = projects.with_issues_enabled if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
options = options.reverse_merge( options = options.reverse_merge(
with: Entities::Project, with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user, statistics: params[:statistics],
simple: params[:simple], current_user: current_user
with_issues_enabled: params[:with_issues_enabled],
with_merge_requests_enabled: params[:with_merge_requests_enabled]
) )
options[:with] = Entities::BasicProjectDetails if params[:simple]
projects = filter_projects(projects)
projects = projects.with_statistics if options[:statistics]
projects = projects.with_issues_enabled if options[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if options[:with_merge_requests_enabled]
options[:with] = Entities::BasicProjectDetails if options[:simple]
present paginate(projects), options present paginate(projects), options
end end
...@@ -94,8 +94,7 @@ module API ...@@ -94,8 +94,7 @@ module API
use :statistics_params use :statistics_params
end end
get do get do
entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails present_projects
present_projects ProjectsFinder.new(current_user: current_user).execute, with: entity, statistics: params[:statistics]
end end
desc 'Create new project' do desc 'Create new project' do
...@@ -231,6 +230,7 @@ module API ...@@ -231,6 +230,7 @@ module API
:request_access_enabled, :request_access_enabled,
:shared_runners_enabled, :shared_runners_enabled,
:snippets_enabled, :snippets_enabled,
:tag_list,
:visibility, :visibility,
:wiki_enabled :wiki_enabled
] ]
......
...@@ -14,6 +14,33 @@ module API ...@@ -14,6 +14,33 @@ module API
authorize! access_level, merge_request authorize! access_level, merge_request
merge_request merge_request
end end
# project helpers
def filter_projects(projects)
if params[:membership]
projects = projects.merge(current_user.authorized_projects)
end
if params[:owned]
projects = projects.merge(current_user.owned_projects)
end
if params[:starred]
projects = projects.merge(current_user.starred_projects)
end
if params[:search].present?
projects = projects.search(params[:search])
end
if params[:visibility].present?
projects = projects.where(visibility_level: Gitlab::VisibilityLevel.level_value(params[:visibility]))
end
projects = projects.where(archived: params[:archived])
projects.reorder(params[:order_by] => params[:sort])
end
end end
end end
end end
...@@ -147,7 +147,7 @@ module API ...@@ -147,7 +147,7 @@ module API
get '/starred' do get '/starred' do
authenticate! authenticate!
present_projects current_user.viewable_starred_projects present_projects ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute
end end
desc 'Get all projects for admin user' do desc 'Get all projects for admin user' do
......
...@@ -31,8 +31,7 @@ module Gitlab ...@@ -31,8 +31,7 @@ module Gitlab
feature_enabled = case action.to_s feature_enabled = case action.to_s
when 'git_receive_pack' when 'git_receive_pack'
# Disabled for now, see https://gitlab.com/gitlab-org/gitaly/issues/172 Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
false
when 'git_upload_pack' when 'git_upload_pack'
Gitlab::GitalyClient.feature_enabled?(:post_upload_pack) Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
when 'info_refs' when 'info_refs'
......
...@@ -234,7 +234,11 @@ describe Projects::JobsController do ...@@ -234,7 +234,11 @@ describe Projects::JobsController do
describe 'POST play' do describe 'POST play' do
before do before do
project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: 'master', project: project)
sign_in(user) sign_in(user)
post_play post_play
......
...@@ -12,6 +12,7 @@ feature 'Environment', :feature do ...@@ -12,6 +12,7 @@ feature 'Environment', :feature do
feature 'environment details page' do feature 'environment details page' do
given!(:environment) { create(:environment, project: project) } given!(:environment) { create(:environment, project: project) }
given!(:permissions) { }
given!(:deployment) { } given!(:deployment) { }
given!(:action) { } given!(:action) { }
...@@ -62,20 +63,31 @@ feature 'Environment', :feature do ...@@ -62,20 +63,31 @@ feature 'Environment', :feature do
name: 'deploy to production') name: 'deploy to production')
end end
given(:role) { :master } context 'when user has ability to trigger deployment' do
given(:permissions) do
create(:protected_branch, :developers_can_merge,
name: action.ref, project: project)
end
scenario 'does show a play button' do it 'does show a play button' do
expect(page).to have_link(action.name.humanize) expect(page).to have_link(action.name.humanize)
end end
it 'does allow to play manual action' do
expect(action).to be_manual
scenario 'does allow to play manual action' do expect { click_link(action.name.humanize) }
expect(action).to be_manual .not_to change { Ci::Pipeline.count }
expect { click_link(action.name.humanize) } expect(page).to have_content(action.name)
.not_to change { Ci::Pipeline.count } expect(action.reload).to be_pending
end
end
expect(page).to have_content(action.name) context 'when user has no ability to trigger a deployment' do
expect(action.reload).to be_pending it 'does not show a play button' do
expect(page).not_to have_link(action.name.humanize)
end
end end
context 'with external_url' do context 'with external_url' do
...@@ -134,12 +146,23 @@ feature 'Environment', :feature do ...@@ -134,12 +146,23 @@ feature 'Environment', :feature do
on_stop: 'close_app') on_stop: 'close_app')
end end
given(:role) { :master } context 'when user has ability to stop environment' do
given(:permissions) do
create(:protected_branch, :developers_can_merge,
name: action.ref, project: project)
end
scenario 'does allow to stop environment' do it 'allows to stop environment' do
click_link('Stop') click_link('Stop')
expect(page).to have_content('close_app') expect(page).to have_content('close_app')
end
end
context 'when user has no ability to stop environment' do
it 'does not allow to stop environment' do
expect(page).to have_no_link('Stop')
end
end end
context 'for reporter' do context 'for reporter' do
...@@ -150,12 +173,6 @@ feature 'Environment', :feature do ...@@ -150,12 +173,6 @@ feature 'Environment', :feature do
end end
end end
end end
context 'without stop action' do
scenario 'does allow to stop environment' do
click_link('Stop')
end
end
end end
context 'when environment is stopped' do context 'when environment is stopped' do
......
...@@ -137,6 +137,13 @@ describe ProjectsFinder do ...@@ -137,6 +137,13 @@ describe ProjectsFinder do
it { is_expected.to eq([public_project]) } it { is_expected.to eq([public_project]) }
end end
describe 'filter by owned' do
let(:params) { { owned: true } }
let!(:owned_project) { create(:empty_project, :private, namespace: current_user.namespace) }
it { is_expected.to eq([owned_project]) }
end
describe 'filter by non_public' do describe 'filter by non_public' do
let(:params) { { non_public: true } } let(:params) { { non_public: true } }
before do before do
...@@ -146,13 +153,19 @@ describe ProjectsFinder do ...@@ -146,13 +153,19 @@ describe ProjectsFinder do
it { is_expected.to eq([private_project]) } it { is_expected.to eq([private_project]) }
end end
describe 'filter by viewable_starred_projects' do describe 'filter by starred' do
let(:params) { { starred: true } } let(:params) { { starred: true } }
before do before do
current_user.toggle_star(public_project) current_user.toggle_star(public_project)
end end
it { is_expected.to eq([public_project]) } it { is_expected.to eq([public_project]) }
it 'returns only projects the user has access to' do
current_user.toggle_star(private_project)
is_expected.to eq([public_project])
end
end end
describe 'sorting' do describe 'sorting' do
......
{
"type": "object",
"properties" : {
"id": { "type": "integer" },
"description": { "type": "string" },
"ref": { "type": "string" },
"cron": { "type": "string" },
"cron_timezone": { "type": "string" },
"next_run_at": { "type": "date" },
"active": { "type": "boolean" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"last_pipeline": {
"type": ["object", "null"],
"properties": {
"id": { "type": "integer" },
"sha": { "type": "string" },
"ref": { "type": "string" },
"status": { "type": "string" }
},
"additionalProperties": false
},
"owner": {
"type": "object",
"properties": {
"name": { "type": "string" },
"username": { "type": "string" },
"id": { "type": "integer" },
"state": { "type": "string" },
"avatar_url": { "type": "uri" },
"web_url": { "type": "uri" }
},
"additionalProperties": false
}
},
"required": [
"id", "description", "ref", "cron", "cron_timezone", "next_run_at",
"active", "created_at", "updated_at", "owner"
],
"additionalProperties": false
}
{
"type": "array",
"items": { "$ref": "pipeline_schedule.json" }
}
...@@ -58,9 +58,12 @@ describe Gitlab::ChatCommands::Command, service: true do ...@@ -58,9 +58,12 @@ describe Gitlab::ChatCommands::Command, service: true do
end end
end end
context 'and user does have deployment permission' do context 'and user has deployment permission' do
before do before do
build.project.add_master(user) build.project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
end end
it 'returns action' do it 'returns action' do
......
...@@ -7,7 +7,12 @@ describe Gitlab::ChatCommands::Deploy, service: true do ...@@ -7,7 +7,12 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let(:regex_match) { described_class.match('deploy staging to production') } let(:regex_match) { described_class.match('deploy staging to production') }
before do before do
project.add_master(user) # Make it possible to trigger protected manual actions for developers.
#
project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: 'master', project: project)
end end
subject do subject do
......
...@@ -224,7 +224,10 @@ describe Gitlab::Ci::Status::Build::Factory do ...@@ -224,7 +224,10 @@ describe Gitlab::Ci::Status::Build::Factory do
context 'when user has ability to play action' do context 'when user has ability to play action' do
before do before do
build.project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
end end
it 'fabricates status that has action' do it 'fabricates status that has action' do
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { build.project }
let(:build) { create(:ci_build, :manual) } let(:build) { create(:ci_build, :manual) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) } let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
...@@ -15,8 +16,13 @@ describe Gitlab::Ci::Status::Build::Play do ...@@ -15,8 +16,13 @@ describe Gitlab::Ci::Status::Build::Play do
describe '#has_action?' do describe '#has_action?' do
context 'when user is allowed to update build' do context 'when user is allowed to update build' do
context 'when user can push to branch' do context 'when user is allowed to trigger protected action' do
before { build.project.add_master(user) } before do
project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
end
it { is_expected.to have_action } it { is_expected.to have_action }
end end
......
...@@ -244,7 +244,7 @@ describe Gitlab::Workhorse, lib: true do ...@@ -244,7 +244,7 @@ describe Gitlab::Workhorse, lib: true do
context "when git_receive_pack action is passed" do context "when git_receive_pack action is passed" do
let(:action) { 'git_receive_pack' } let(:action) { 'git_receive_pack' }
it { expect(subject).not_to include(gitaly_params) } it { expect(subject).to include(gitaly_params) }
end end
context "when info_refs action is passed" do context "when info_refs action is passed" do
......
...@@ -227,7 +227,10 @@ describe Environment, models: true do ...@@ -227,7 +227,10 @@ describe Environment, models: true do
context 'when user is allowed to stop environment' do context 'when user is allowed to stop environment' do
before do before do
project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: 'master', project: project)
end end
context 'when action did not yet finish' do context 'when action did not yet finish' do
......
...@@ -948,6 +948,20 @@ describe Project, models: true do ...@@ -948,6 +948,20 @@ describe Project, models: true do
end end
end end
describe '.starred_by' do
it 'returns only projects starred by the given user' do
user1 = create(:user)
user2 = create(:user)
project1 = create(:empty_project)
project2 = create(:empty_project)
create(:empty_project)
user1.toggle_star(project1)
user2.toggle_star(project2)
expect(Project.starred_by(user1)).to contain_exactly(project1)
end
end
describe '.visible_to_user' do describe '.visible_to_user' do
let!(:project) { create(:empty_project, :private) } let!(:project) { create(:empty_project, :private) }
let!(:user) { create(:user) } let!(:user) { create(:user) }
......
...@@ -1496,25 +1496,6 @@ describe User, models: true do ...@@ -1496,25 +1496,6 @@ describe User, models: true do
end end
end end
describe '#viewable_starred_projects' do
let(:user) { create(:user) }
let(:public_project) { create(:empty_project, :public) }
let(:private_project) { create(:empty_project, :private) }
let(:private_viewable_project) { create(:empty_project, :private) }
before do
private_viewable_project.team << [user, Gitlab::Access::MASTER]
[public_project, private_project, private_viewable_project].each do |project|
user.toggle_star(project)
end
end
it 'returns only starred projects the user can view' do
expect(user.viewable_starred_projects).not_to include(private_project)
end
end
describe '#projects_with_reporter_access_limited_to' do describe '#projects_with_reporter_access_limited_to' do
let(:project1) { create(:empty_project) } let(:project1) { create(:empty_project) }
let(:project2) { create(:empty_project) } let(:project2) { create(:empty_project) }
......
require 'spec_helper'
describe API::PipelineSchedules do
set(:developer) { create(:user) }
set(:user) { create(:user) }
set(:project) { create(:project) }
before do
project.add_developer(developer)
end
describe 'GET /projects/:id/pipeline_schedules' do
context 'authenticated user with valid permissions' do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
before do
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
it 'returns list of pipeline_schedules' do
get api("/projects/#{project.id}/pipeline_schedules", developer)
expect(response).to have_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('pipeline_schedules')
end
it 'avoids N + 1 queries' do
control_count = ActiveRecord::QueryRecorder.new do
get api("/projects/#{project.id}/pipeline_schedules", developer)
end.count
create_list(:ci_pipeline_schedule, 10, project: project)
.each do |pipeline_schedule|
create(:user).tap do |user|
project.add_developer(user)
pipeline_schedule.update_attributes(owner: user)
end
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
expect do
get api("/projects/#{project.id}/pipeline_schedules", developer)
end.not_to exceed_query_limit(control_count)
end
%w[active inactive].each do |target|
context "when scope is #{target}" do
before do
create(:ci_pipeline_schedule, project: project, active: active?(target))
end
it 'returns matched pipeline schedules' do
get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target)))
end
end
def active?(str)
(str == 'active') ? true : false
end
end
end
context 'authenticated user with invalid permissions' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
before do
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
context 'authenticated user with valid permissions' do
it 'returns pipeline_schedule details' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
end
it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
expect(response).to have_http_status(:not_found)
end
end
context 'authenticated user with invalid permissions' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'POST /projects/:id/pipeline_schedules' do
let(:params) { attributes_for(:ci_pipeline_schedule) }
context 'authenticated user with valid permissions' do
context 'with required parameters' do
it 'creates pipeline_schedule' do
expect do
post api("/projects/#{project.id}/pipeline_schedules", developer),
params
end.to change { project.pipeline_schedules.count }.by(1)
expect(response).to have_http_status(:created)
expect(response).to match_response_schema('pipeline_schedule')
expect(json_response['description']).to eq(params[:description])
expect(json_response['ref']).to eq(params[:ref])
expect(json_response['cron']).to eq(params[:cron])
expect(json_response['cron_timezone']).to eq(params[:cron_timezone])
expect(json_response['owner']['id']).to eq(developer.id)
end
end
context 'without required parameters' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules", developer)
expect(response).to have_http_status(:bad_request)
end
end
context 'when cron has validation error' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules", developer),
params.merge('cron' => 'invalid-cron')
expect(response).to have_http_status(:bad_request)
expect(json_response['message']).to have_key('cron')
end
end
end
context 'authenticated user with invalid permissions' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules", user), params
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules"), params
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
let(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
end
context 'authenticated user with valid permissions' do
it 'updates cron' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
cron: '1 2 3 4 *'
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
expect(json_response['cron']).to eq('1 2 3 4 *')
end
context 'when cron has validation error' do
it 'does not update pipeline_schedule' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
cron: 'invalid-cron'
expect(response).to have_http_status(:bad_request)
expect(json_response['message']).to have_key('cron')
end
end
end
context 'authenticated user with invalid permissions' do
it 'does not update pipeline_schedule' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not update pipeline_schedule' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
let(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
end
context 'authenticated user with valid permissions' do
it 'updates owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
expect(response).to have_http_status(:created)
expect(response).to match_response_schema('pipeline_schedule')
end
end
context 'authenticated user with invalid permissions' do
it 'does not update owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not update owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
let(:master) { create(:user) }
let!(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
end
before do
project.add_master(master)
end
context 'authenticated user with valid permissions' do
it 'deletes pipeline_schedule' do
expect do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master)
end.to change { project.pipeline_schedules.count }.by(-1)
expect(response).to have_http_status(:accepted)
expect(response).to match_response_schema('pipeline_schedule')
end
it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/-5", master)
expect(response).to have_http_status(:not_found)
end
end
context 'authenticated user with invalid permissions' do
it 'does not delete pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
expect(response).to have_http_status(:forbidden)
end
end
context 'unauthenticated user' do
it 'does not delete pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_http_status(:unauthorized)
end
end
end
end
...@@ -390,6 +390,14 @@ describe API::Projects do ...@@ -390,6 +390,14 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private') expect(json_response['visibility']).to eq('private')
end end
it 'sets tag list to a project' do
project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])
post api('/projects', user), project
expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond])
end
it 'sets a project as allowing merge even if build fails' do it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false }) project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
post api('/projects', user), project post api('/projects', user), project
......
...@@ -3,6 +3,7 @@ require 'spec_helper' ...@@ -3,6 +3,7 @@ require 'spec_helper'
describe BuildEntity do describe BuildEntity do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
let(:project) { build.project }
let(:request) { double('request') } let(:request) { double('request') }
before do before do
...@@ -52,7 +53,10 @@ describe BuildEntity do ...@@ -52,7 +53,10 @@ describe BuildEntity do
context 'when user is allowed to trigger action' do context 'when user is allowed to trigger action' do
before do before do
build.project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: 'master', project: project)
end end
it 'contains path to play action' do it 'contains path to play action' do
......
...@@ -13,8 +13,11 @@ describe Ci::PlayBuildService, '#execute', :services do ...@@ -13,8 +13,11 @@ describe Ci::PlayBuildService, '#execute', :services do
context 'when project does not have repository yet' do context 'when project does not have repository yet' do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
it 'allows user with master role to play build' do it 'allows user to play build if protected branch rules are met' do
project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
service.execute(build) service.execute(build)
...@@ -45,7 +48,10 @@ describe Ci::PlayBuildService, '#execute', :services do ...@@ -45,7 +48,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) } let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
before do before do
project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
end end
it 'enqueues the build' do it 'enqueues the build' do
...@@ -64,7 +70,10 @@ describe Ci::PlayBuildService, '#execute', :services do ...@@ -64,7 +70,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) } let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) }
before do before do
project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
end end
it 'duplicates the build' do it 'duplicates the build' do
......
...@@ -333,10 +333,11 @@ describe Ci::ProcessPipelineService, '#execute', :services do ...@@ -333,10 +333,11 @@ describe Ci::ProcessPipelineService, '#execute', :services do
context 'when pipeline is promoted sequentially up to the end' do context 'when pipeline is promoted sequentially up to the end' do
before do before do
# We are using create(:empty_project), and users has to be master in # Users need ability to merge into a branch in order to trigger
# order to execute manual action when repository does not exist. # protected manual actions.
# #
project.add_master(user) create(:protected_branch, :developers_can_merge,
name: 'master', project: project)
end end
it 'properly processes entire pipeline' do it 'properly processes entire pipeline' do
......
...@@ -6,9 +6,12 @@ describe Ci::RetryPipelineService, '#execute', :services do ...@@ -6,9 +6,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:service) { described_class.new(project, user) } let(:service) { described_class.new(project, user) }
context 'when user has ability to modify pipeline' do context 'when user has full ability to modify pipeline' do
before do before do
project.add_master(user) project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: pipeline.ref, project: project)
end end
context 'when there are already retried jobs present' do context 'when there are already retried jobs present' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment