summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/notes_controller.rb25
-rw-r--r--app/helpers/services_helper.rb2
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/concerns/issuable.rb11
-rw-r--r--app/models/project_services/jira_service.rb43
-rw-r--r--app/models/service.rb1
-rw-r--r--app/serializers/issuable_entity.rb16
-rw-r--r--app/serializers/issue_entity.rb9
-rw-r--r--app/serializers/issue_serializer.rb3
-rw-r--r--app/serializers/label_entity.rb11
-rw-r--r--app/serializers/merge_request_entity.rb14
-rw-r--r--app/serializers/merge_request_serializer.rb3
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb7
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--changelogs/unreleased/create-pipeline-endpoint.yml4
-rw-r--r--changelogs/unreleased/dz-fix-group-name-dot.yml4
-rw-r--r--changelogs/unreleased/fix-do-not-add-todo-when-build-allowed-to-fail.yml4
-rw-r--r--changelogs/unreleased/issue_5541.yml4
-rw-r--r--config/routes/group.rb11
-rw-r--r--db/migrate/20161118183841_add_commit_events_to_services.rb15
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/pipelines.md46
-rw-r--r--doc/api/users.md44
-rw-r--r--doc/workflow/todos.md6
-rw-r--r--lib/api/pipelines.rb21
-rw-r--r--lib/api/users.rb506
-rwxr-xr-xscripts/prepare_build.sh9
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml3
-rw-r--r--spec/models/project_services/jira_service_spec.rb6
-rw-r--r--spec/requests/api/pipelines_spec.rb46
-rw-r--r--spec/requests/api/users_spec.rb76
-rw-r--r--spec/routing/routing_spec.rb18
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb33
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb16
-rw-r--r--spec/services/system_note_service_spec.rb28
-rw-r--r--spec/support/jira_service_helper.rb3
40 files changed, 691 insertions, 376 deletions
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index be86fa106f8..0821974aa93 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -12,7 +12,7 @@ module IssuableActions
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
TodoService.new.public_send(destroy_method, issuable, current_user)
- name = issuable.class.name.titleize.downcase
+ name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted."
redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 3f1a1d1c511..4aea7bb62c4 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -69,7 +69,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
- render json: @issue.to_json(include: [:milestone, :labels])
+ render json: IssueSerializer.new.represent(@issue)
end
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 036fde87619..b343ba0b744 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -60,7 +60,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.html { define_discussion_vars }
format.json do
- render json: @merge_request
+ render json: MergeRequestSerializer.new.represent(@merge_request)
end
format.patch do
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 0948ad21649..f029fde2a2f 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -146,24 +146,26 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_json(note)
+ attrs = {
+ award: false,
+ id: note.id
+ }
+
if note.is_a?(AwardEmoji)
- {
+ attrs.merge!(
valid: note.valid?,
award: true,
- id: note.id,
name: note.name
- }
+ )
elsif note.persisted?
Banzai::NoteRenderer.render([note], @project, current_user)
- attrs = {
+ attrs.merge!(
valid: true,
- id: note.id,
discussion_id: note.discussion_id,
html: note_html(note),
- award: false,
note: note.note
- }
+ )
if note.diff_note?
discussion = note.to_discussion
@@ -188,15 +190,14 @@ class Projects::NotesController < Projects::ApplicationController
attrs[:original_discussion_id] = note.original_discussion_id
end
end
-
- attrs
else
- {
+ attrs.merge!(
valid: false,
- award: false,
errors: note.errors
- }
+ )
end
+
+ attrs
end
def authorize_admin_note!
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 3d4abf76419..9bab140e60a 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -17,6 +17,8 @@ module ServicesHelper
"Event will be triggered when a build status changes"
when "wiki_page"
"Event will be triggered when a wiki page is created/updated"
+ when "commit"
+ "Event will be triggered when a commit is created/updated"
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5d2e7d94190..83c0c64e5fb 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -70,7 +70,11 @@ module Ci
environment: build.environment,
status_event: 'enqueue'
)
- MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
+
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(build.project, nil)
+ .close(new_build)
+
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
new_build
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index ec9e7e5ae2b..69d8afc45da 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -251,6 +251,17 @@ module Issuable
self.class.to_ability_name
end
+ # Convert this Issuable class name to a format usable by notifications.
+ #
+ # Examples:
+ #
+ # issuable.class # => MergeRequest
+ # issuable.human_class_name # => "merge request"
+
+ def human_class_name
+ @human_class_name ||= self.class.name.titleize.downcase
+ end
+
# Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes
{
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 8915c06b633..2caf6179ef8 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class JiraService < IssueTrackerService
include Gitlab::Routing.url_helpers
@@ -30,6 +9,10 @@ class JiraService < IssueTrackerService
before_update :reset_password
+ def supported_events
+ %w(commit merge_request)
+ end
+
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def reference_pattern
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
@@ -137,12 +120,16 @@ class JiraService < IssueTrackerService
end
def create_cross_reference_note(mentioned, noteable, author)
+ unless can_cross_reference?(noteable)
+ return "Events for #{noteable.model_name.plural.humanize(capitalize: false)} are disabled."
+ end
+
jira_issue = jira_request { client.Issue.find(mentioned.id) }
- return false unless jira_issue.present?
+ return unless jira_issue.present?
project = self.project
- noteable_name = noteable.class.name.underscore.downcase
+ noteable_name = noteable.model_name.singular
noteable_id = if noteable.is_a?(Commit)
noteable.id
else
@@ -193,8 +180,16 @@ class JiraService < IssueTrackerService
private
+ def can_cross_reference?(noteable)
+ case noteable
+ when Commit then commit_events
+ when MergeRequest then merge_requests_events
+ else true
+ end
+ end
+
def close_issue(entity, issue)
- return if issue.nil? || issue.resolution.present?
+ return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
commit_id = if entity.is_a?(Commit)
entity.id
diff --git a/app/models/service.rb b/app/models/service.rb
index edd6b5329f3..0c36acfc1b7 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -8,6 +8,7 @@ class Service < ActiveRecord::Base
default_value_for :push_events, true
default_value_for :issues_events, true
default_value_for :confidential_issues_events, true
+ default_value_for :commit_events, true
default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true
default_value_for :note_events, true
diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb
new file mode 100644
index 00000000000..17c9160cb19
--- /dev/null
+++ b/app/serializers/issuable_entity.rb
@@ -0,0 +1,16 @@
+class IssuableEntity < Grape::Entity
+ expose :id
+ expose :iid
+ expose :assignee_id
+ expose :author_id
+ expose :description
+ expose :lock_version
+ expose :milestone_id
+ expose :position
+ expose :state
+ expose :title
+ expose :updated_by_id
+ expose :created_at
+ expose :updated_at
+ expose :deleted_at
+end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
new file mode 100644
index 00000000000..6429159ebe1
--- /dev/null
+++ b/app/serializers/issue_entity.rb
@@ -0,0 +1,9 @@
+class IssueEntity < IssuableEntity
+ expose :branch_name
+ expose :confidential
+ expose :due_date
+ expose :moved_to_id
+ expose :project_id
+ expose :milestone, using: API::Entities::Milestone
+ expose :labels, using: LabelEntity
+end
diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb
new file mode 100644
index 00000000000..4fff54a9126
--- /dev/null
+++ b/app/serializers/issue_serializer.rb
@@ -0,0 +1,3 @@
+class IssueSerializer < BaseSerializer
+ entity IssueEntity
+end
diff --git a/app/serializers/label_entity.rb b/app/serializers/label_entity.rb
new file mode 100644
index 00000000000..304fd9de08f
--- /dev/null
+++ b/app/serializers/label_entity.rb
@@ -0,0 +1,11 @@
+class LabelEntity < Grape::Entity
+ expose :id
+ expose :title
+ expose :color
+ expose :description
+ expose :group_id
+ expose :project_id
+ expose :template
+ expose :created_at
+ expose :updated_at
+end
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
new file mode 100644
index 00000000000..7445298c714
--- /dev/null
+++ b/app/serializers/merge_request_entity.rb
@@ -0,0 +1,14 @@
+class MergeRequestEntity < IssuableEntity
+ expose :in_progress_merge_commit_sha
+ expose :locked_at
+ expose :merge_commit_sha
+ expose :merge_error
+ expose :merge_params
+ expose :merge_status
+ expose :merge_user_id
+ expose :merge_when_build_succeeds
+ expose :source_branch
+ expose :source_project_id
+ expose :target_branch
+ expose :target_project_id
+end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
new file mode 100644
index 00000000000..aa6e00dfcb4
--- /dev/null
+++ b/app/serializers/merge_request_serializer.rb
@@ -0,0 +1,3 @@
+class MergeRequestSerializer < BaseSerializer
+ entity MergeRequestEntity
+end
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
index d572a928a42..12a8415d9a5 100644
--- a/app/services/merge_requests/add_todo_when_build_fails_service.rb
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -1,13 +1,18 @@
module MergeRequests
class AddTodoWhenBuildFailsService < MergeRequests::BaseService
# Adds a todo to the parent merge_request when a CI build fails
+ #
def execute(commit_status)
+ return if commit_status.allow_failure?
+
commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_failed(merge_request)
end
end
- # Closes any pending build failed todos for the parent MRs when a build is retried
+ # Closes any pending build failed todos for the parent MRs when a
+ # build is retried
+ #
def close(commit_status)
commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_retried(merge_request)
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index e338792412b..7935fabe2da 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -35,7 +35,7 @@ module Notes
todo_service.new_note(note, current_user)
end
- if command_params && command_params.any?
+ if command_params.present?
slash_commands_service.execute(command_params, note)
# We must add the error after we call #save because errors are reset
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 2fe9e82194b..9b9ad510444 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -125,7 +125,7 @@
- else
.pull-right
- if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
- = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
+ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
diff --git a/changelogs/unreleased/create-pipeline-endpoint.yml b/changelogs/unreleased/create-pipeline-endpoint.yml
new file mode 100644
index 00000000000..c7638b7b7aa
--- /dev/null
+++ b/changelogs/unreleased/create-pipeline-endpoint.yml
@@ -0,0 +1,4 @@
+---
+title: Add api endpoint for creating a pipeline
+merge_request: 7209
+author: Ido Leibovich
diff --git a/changelogs/unreleased/dz-fix-group-name-dot.yml b/changelogs/unreleased/dz-fix-group-name-dot.yml
new file mode 100644
index 00000000000..439cb229e6b
--- /dev/null
+++ b/changelogs/unreleased/dz-fix-group-name-dot.yml
@@ -0,0 +1,4 @@
+---
+title: Fix 404 on some group pages when name contains dot
+merge_request: 7614
+author:
diff --git a/changelogs/unreleased/fix-do-not-add-todo-when-build-allowed-to-fail.yml b/changelogs/unreleased/fix-do-not-add-todo-when-build-allowed-to-fail.yml
new file mode 100644
index 00000000000..6402d0c7ece
--- /dev/null
+++ b/changelogs/unreleased/fix-do-not-add-todo-when-build-allowed-to-fail.yml
@@ -0,0 +1,4 @@
+---
+title: Do not create a new TODO when failed build is allowed to fail
+merge_request: 7618
+author:
diff --git a/changelogs/unreleased/issue_5541.yml b/changelogs/unreleased/issue_5541.yml
new file mode 100644
index 00000000000..cf553cf8d80
--- /dev/null
+++ b/changelogs/unreleased/issue_5541.yml
@@ -0,0 +1,4 @@
+---
+title: Allow enabling and disabling commit and MR events for JIRA
+merge_request:
+author:
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 068e0b6e843..a3a001178b4 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -14,7 +14,9 @@ end
resources :groups, only: [:index, :new, :create]
-scope(path: 'groups/:id', controller: :groups) do
+scope(path: 'groups/:id',
+ controller: :groups,
+ constraints: { id: Gitlab::Regex.namespace_route_regex }) do
get :edit, as: :edit_group
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
@@ -22,7 +24,10 @@ scope(path: 'groups/:id', controller: :groups) do
get :activity, as: :activity_group
end
-scope(path: 'groups/:group_id', module: :groups, as: :group) do
+scope(path: 'groups/:group_id',
+ module: :groups,
+ as: :group,
+ constraints: { group_id: Gitlab::Regex.namespace_route_regex }) do
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member
delete :leave, on: :collection
@@ -37,4 +42,4 @@ scope(path: 'groups/:group_id', module: :groups, as: :group) do
end
# Must be last route in this file
-get 'groups/:id' => 'groups#show', as: :group_canonical
+get 'groups/:id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex }
diff --git a/db/migrate/20161118183841_add_commit_events_to_services.rb b/db/migrate/20161118183841_add_commit_events_to_services.rb
new file mode 100644
index 00000000000..4f9b5dd2281
--- /dev/null
+++ b/db/migrate/20161118183841_add_commit_events_to_services.rb
@@ -0,0 +1,15 @@
+class AddCommitEventsToServices < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:services, :commit_events, :boolean, default: true, allow_null: false)
+ end
+
+ def down
+ remove_column(:services, :commit_events)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index db2ce689afc..6b28e80f01d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161117114805) do
+ActiveRecord::Schema.define(version: 20161118183841) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1031,6 +1031,7 @@ ActiveRecord::Schema.define(version: 20161117114805) do
t.boolean "wiki_page_events", default: true
t.boolean "pipeline_events", default: false, null: false
t.boolean "confidential_issues_events", default: true, null: false
+ t.boolean "commit_events", default: true, null: false
end
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index a29b3eb6f44..6455c333faf 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -114,6 +114,51 @@ Example of response
}
```
+## Create a new pipeline
+
+> [Introduced][ce-7209] in GitLab 8.14
+
+```
+POST /projects/:id/pipeline
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `ref` | string | yes | Reference to commit |
+
+```
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipeline?ref=master"
+```
+
+Example of response
+
+```json
+{
+ "id": 61,
+ "sha": "384c444e840a515b23f21915ee5766b87068a70d",
+ "ref": "master",
+ "status": "pending",
+ "before_sha": "0000000000000000000000000000000000000000",
+ "tag": false,
+ "yaml_errors": null,
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2016-11-04T09:36:13.747Z",
+ "updated_at": "2016-11-04T09:36:13.977Z",
+ "started_at": null,
+ "finished_at": null,
+ "committed_at": null,
+ "duration": null
+}
+```
+
## Retry failed builds in a pipeline
> [Introduced][ce-5837] in GitLab 8.11
@@ -205,3 +250,4 @@ Response:
```
[ce-5837]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5837
+[ce-7209]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7209
diff --git a/doc/api/users.md b/doc/api/users.md
index 041df07c051..b38c335490a 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -369,24 +369,24 @@ Parameters:
Get a list of a specified user's SSH keys. Available only for admin
```
-GET /users/:uid/keys
+GET /users/:id/keys
```
Parameters:
-- `uid` (required) - id of specified user
+- `id` (required) - id of specified user
## Single SSH key
Get a single key.
```
-GET /user/keys/:id
+GET /user/keys/:key_id
```
Parameters:
-- `id` (required) - The ID of an SSH key
+- `key_id` (required) - The ID of an SSH key
```json
{
@@ -458,25 +458,25 @@ This is an idempotent function and calling it on a key that is already deleted
or not available results in `200 OK`.
```
-DELETE /user/keys/:id
+DELETE /user/keys/:key_id
```
Parameters:
-- `id` (required) - SSH key ID
+- `key_id` (required) - SSH key ID
## Delete SSH key for given user
Deletes key owned by a specified user. Available only for admin.
```
-DELETE /users/:uid/keys/:id
+DELETE /users/:id/keys/:key_id
```
Parameters:
-- `uid` (required) - id of specified user
-- `id` (required) - SSH key ID
+- `id` (required) - id of specified user
+- `key_id` (required) - SSH key ID
Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
@@ -510,24 +510,24 @@ Parameters:
Get a list of a specified user's emails. Available only for admin
```
-GET /users/:uid/emails
+GET /users/:id/emails
```
Parameters:
-- `uid` (required) - id of specified user
+- `id` (required) - id of specified user
## Single email
Get a single email.
```
-GET /user/emails/:id
+GET /user/emails/:email_id
```
Parameters:
-- `id` (required) - email ID
+- `email_id` (required) - email ID
```json
{
@@ -590,25 +590,25 @@ This is an idempotent function and calling it on a email that is already deleted
or not available results in `200 OK`.
```
-DELETE /user/emails/:id
+DELETE /user/emails/:email_id
```
Parameters:
-- `id` (required) - email ID
+- `email_id` (required) - email ID
## Delete email for given user
Deletes email owned by a specified user. Available only for admin.
```
-DELETE /users/:uid/emails/:id
+DELETE /users/:id/emails/:email_id
```
Parameters:
-- `uid` (required) - id of specified user
-- `id` (required) - email ID
+- `id` (required) - id of specified user
+- `email_id` (required) - email ID
Will return `200 OK` on success, or `404 Not found` if either user or email cannot be found.
@@ -617,12 +617,12 @@ Will return `200 OK` on success, or `404 Not found` if either user or email cann
Blocks the specified user. Available only for admin.
```
-PUT /users/:uid/block
+PUT /users/:id/block
```
Parameters:
-- `uid` (required) - id of specified user
+- `id` (required) - id of specified user
Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
`403 Forbidden` when trying to block an already blocked user by LDAP synchronization.
@@ -632,12 +632,12 @@ Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
Unblocks the specified user. Available only for admin.
```
-PUT /users/:uid/unblock
+PUT /users/:id/unblock
```
Parameters:
-- `uid` (required) - id of specified user
+- `id` (required) - id of specified user
Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
`403 Forbidden` when trying to unblock a user blocked by LDAP synchronization.
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index a50ba305deb..54e7ae19ea5 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -24,9 +24,11 @@ you still have open.
A Todo appears in your Todos dashboard when:
-- an issue or merge request is assigned to you
+- an issue or merge request is assigned to you,
- you are `@mentioned` in an issue or merge request, be it the description of
- the issue/merge request or in a comment
+ the issue/merge request or in a comment,
+- build in the CI pipeline running for your merge request failed, but this
+ build is not allowed to fail.
>**Note:** Commenting on a commit will _not_ trigger a Todo.
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 2a0c8e1f2c0..e69b0569612 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -22,6 +22,27 @@ module API
pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
present paginate(pipelines), with: Entities::Pipeline
end
+
+ desc 'Create a new pipeline' do
+ detail 'This feature was introduced in GitLab 8.14'
+ success Entities::Pipeline
+ end
+ params do
+ requires :ref, type: String, desc: 'Reference'
+ end
+ post ':id/pipeline' do
+ authorize! :create_pipeline, user_project
+
+ new_pipeline = Ci::CreatePipelineService.new(user_project,
+ current_user,
+ declared_params(include_missing: false))
+ .execute(ignore_skip_ci: true, save_on_errors: false)
+ if new_pipeline.persisted?
+ present new_pipeline, with: Entities::Pipeline
+ else
+ render_validation_error!(new_pipeline)
+ end
+ end
desc 'Gets a specific pipeline for the project' do
detail 'This feature was introduced in GitLab 8.11'
diff --git a/lib/api/users.rb b/lib/api/users.rb
index aea328d2f8f..c07539194ed 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -4,89 +4,93 @@ module API
before { authenticate! }
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
- # Get a users list
- #
- # Example Request:
- # GET /users
- # GET /users?search=Admin
- # GET /users?username=root
- # GET /users?active=true
- # GET /users?external=true
- # GET /users?blocked=true
+ helpers do
+ params :optional_attributes do
+ optional :skype, type: String, desc: 'The Skype username'
+ optional :linkedin, type: String, desc: 'The LinkedIn username'
+ optional :twitter, type: String, desc: 'The Twitter username'
+ optional :website_url, type: String, desc: 'The website of the user'
+ optional :organization, type: String, desc: 'The organization of the user'
+ optional :projects_limit, type: Integer, desc: 'The number of projects a user can create'
+ optional :extern_uid, type: Integer, desc: 'The external authentication provider UID'
+ optional :provider, type: String, desc: 'The external provider'
+ optional :bio, type: String, desc: 'The biography of the user'
+ optional :location, type: String, desc: 'The location of the user'
+ optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
+ optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
+ optional :confirm, type: Boolean, desc: 'Flag indicating the account needs to be confirmed'
+ optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
+ all_or_none_of :extern_uid, :provider
+ end
+ end
+
+ desc 'Get the list of users' do
+ success Entities::UserBasic
+ end
+ params do
+ optional :username, type: String, desc: 'Get a single user with a specific username'
+ optional :search, type: String, desc: 'Search for a username'
+ optional :active, type: Boolean, default: false, desc: 'Filters only active users'
+ optional :external, type: Boolean, default: false, desc: 'Filters only external users'
+ optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
+ end
get do
unless can?(current_user, :read_users_list, nil)
render_api_error!("Not authorized.", 403)
end
if params[:username].present?
- @users = User.where(username: params[:username])
+ users = User.where(username: params[:username])
else
- @users = User.all
- @users = @users.active if to_boolean(params[:active])
- @users = @users.search(params[:search]) if params[:search].present?
- @users = @users.blocked if to_boolean(params[:blocked])
- @users = @users.external if to_boolean(params[:external]) && current_user.is_admin?
- @users = paginate @users
+ users = User.all
+ users = users.active if params[:active]
+ users = users.search(params[:search]) if params[:search].present?
+ users = users.blocked if params[:blocked]
+ users = users.external if params[:external] && current_user.is_admin?
end
- if current_user.is_admin?
- present @users, with: Entities::UserFull
- else
- present @users, with: Entities::UserBasic
- end
+ entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic
+ present paginate(users), with: entity
end
- # Get a single user
- #
- # Parameters:
- # id (required) - The ID of a user
- # Example Request:
- # GET /users/:id
+ desc 'Get a single user' do
+ success Entities::UserBasic
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
get ":id" do
- @user = User.find(params[:id])
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
if current_user && current_user.is_admin?
- present @user, with: Entities::UserFull
- elsif can?(current_user, :read_user, @user)
- present @user, with: Entities::User
+ present user, with: Entities::UserFull
+ elsif can?(current_user, :read_user, user)
+ present user, with: Entities::User
else
render_api_error!("User not found.", 404)
end
end
- # Create user. Available only for admin
- #
- # Parameters:
- # email (required) - Email
- # password (required) - Password
- # name (required) - Name
- # username (required) - Name
- # skype - Skype ID
- # linkedin - Linkedin
- # twitter - Twitter account
- # website_url - Website url
- # organization - Organization
- # projects_limit - Number of projects user can create
- # extern_uid - External authentication provider UID
- # provider - External provider
- # bio - Bio
- # location - Location of the user
- # admin - User is admin - true or false (default)
- # can_create_group - User can create groups - true or false
- # confirm - Require user confirmation - true (default) or false
- # external - Flags the user as external - true or false(default)
- # Example Request:
- # POST /users
+ desc 'Create a user. Available only for admins.' do
+ success Entities::UserFull
+ end
+ params do
+ requires :email, type: String, desc: 'The email of the user'
+ requires :password, type: String, desc: 'The password of the new user'
+ requires :name, type: String, desc: 'The name of the user'
+ requires :username, type: String, desc: 'The username of the user'
+ use :optional_attributes
+ end
post do
authenticated_as_admin!
- required_attributes! [:email, :password, :name, :username]
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external, :organization]
- admin = attrs.delete(:admin)
- confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i)
- user = User.build_user(attrs)
- user.admin = admin unless admin.nil?
+
+ # Filter out params which are used later
+ identity_attrs = params.slice(:provider, :extern_uid)
+ confirm = params.delete(:confirm)
+
+ user = User.build_user(declared_params(include_missing: false))
user.skip_confirmation! unless confirm
- identity_attrs = attributes_for_keys [:provider, :extern_uid]
if identity_attrs.any?
user.identities.build(identity_attrs)
@@ -107,46 +111,40 @@ module API
end
end
- # Update user. Available only for admin
- #
- # Parameters:
- # email - Email
- # name - Name
- # password - Password
- # skype - Skype ID
- # linkedin - Linkedin
- # twitter - Twitter account
- # website_url - Website url
- # organization - Organization
- # projects_limit - Limit projects each user can create
- # bio - Bio
- # location - Location of the user
- # admin - User is admin - true or false (default)
- # can_create_group - User can create groups - true or false
- # external - Flags the user as external - true or false(default)
- # Example Request:
- # PUT /users/:id
+ desc 'Update a user. Available only for admins.' do
+ success Entities::UserFull
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ optional :email, type: String, desc: 'The email of the user'
+ optional :password, type: String, desc: 'The password of the new user'
+ optional :name, type: String, desc: 'The name of the user'
+ optional :username, type: String, desc: 'The username of the user'
+ use :optional_attributes
+ at_least_one_of :email, :password, :name, :username, :skype, :linkedin,
+ :twitter, :website_url, :organization, :projects_limit,
+ :extern_uid, :provider, :bio, :location, :admin,
+ :can_create_group, :confirm, :external
+ end
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external, :organization]
- user = User.find(params[:id])
+ user = User.find_by(id: params.delete(:id))
not_found!('User') unless user
- admin = attrs.delete(:admin)
- user.admin = admin unless admin.nil?
-
- conflict!('Email has already been taken') if attrs[:email] &&
- User.where(email: attrs[:email]).
+ conflict!('Email has already been taken') if params[:email] &&
+ User.where(email: params[:email]).
where.not(id: user.id).count > 0
- conflict!('Username has already been taken') if attrs[:username] &&
- User.where(username: attrs[:username]).
+ conflict!('Username has already been taken') if params[:username] &&
+ User.where(username: params[:username]).
where.not(id: user.id).count > 0
- identity_attrs = attributes_for_keys [:provider, :extern_uid]
+ identity_attrs = params.slice(:provider, :extern_uid)
+
if identity_attrs.any?
identity = user.identities.find_by(provider: identity_attrs[:provider])
+
if identity
identity.update_attributes(identity_attrs)
else
@@ -155,28 +153,33 @@ module API
end
end
- if user.update_attributes(attrs)
+ # Delete already handled parameters
+ params.delete(:extern_uid)
+ params.delete(:provider)
+
+ if user.update_attributes(declared_params(include_missing: false))
present user, with: Entities::UserFull
else
render_validation_error!(user)
end
end
- # Add ssh key to a specified user. Only available to admin users.
- #
- # Parameters:
- # id (required) - The ID of a user
- # key (required) - New SSH Key
- # title (required) - New SSH Key's title
- # Example Request:
- # POST /users/:id/keys
+ desc 'Add an SSH key to a specified user. Available only for admins.' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key, type: String, desc: 'The new SSH key'
+ requires :title, type: String, desc: 'The title of the new SSH key'
+ end
post ":id/keys" do
authenticated_as_admin!
- required_attributes! [:title, :key]
- user = User.find(params[:id])
- attrs = attributes_for_keys [:title, :key]
- key = user.keys.new attrs
+ user = User.find_by(id: params.delete(:id))
+ not_found!('User') unless user
+
+ key = user.keys.new(declared_params(include_missing: false))
+
if key.save
present key, with: Entities::SSHKey
else
@@ -184,55 +187,55 @@ module API
end
end
- # Get ssh keys of a specified user. Only available to admin users.
- #
- # Parameters:
- # uid (required) - The ID of a user
- # Example Request:
- # GET /users/:uid/keys
- get ':uid/keys' do
+ desc 'Get the SSH keys of a specified user. Available only for admins.' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ get ':id/keys' do
authenticated_as_admin!
- user = User.find_by(id: params[:uid])
+
+ user = User.find_by(id: params[:id])
not_found!('User') unless user
present user.keys, with: Entities::SSHKey
end
- # Delete existing ssh key of a specified user. Only available to admin
- # users.
- #
- # Parameters:
- # uid (required) - The ID of a user
- # id (required) - SSH Key ID
- # Example Request:
- # DELETE /users/:uid/keys/:id
- delete ':uid/keys/:id' do
+ desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete ':id/keys/:key_id' do
authenticated_as_admin!
- user = User.find_by(id: params[:uid])
+
+ user = User.find_by(id: params[:id])
not_found!('User') unless user
- begin
- key = user.keys.find params[:id]
- key.destroy
- rescue ActiveRecord::RecordNotFound
- not_found!('Key')
- end
+ key = user.keys.find_by(id: params[:key_id])
+ not_found!('Key') unless key
+
+ present key.destroy, with: Entities::SSHKey
end
- # Add email to a specified user. Only available to admin users.
- #
- # Parameters:
- # id (required) - The ID of a user
- # email (required) - Email address
- # Example Request:
- # POST /users/:id/emails
+ desc 'Add an email address to a specified user. Available only for admins.' do
+ success Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :email, type: String, desc: 'The email of the user'
+ end
post ":id/emails" do
authenticated_as_admin!
- required_attributes! [:email]
- user = User.find(params[:id])
- attrs = attributes_for_keys [:email]
- email = user.emails.new attrs
+ user = User.find_by(id: params.delete(:id))
+ not_found!('User') unless user
+
+ email = user.emails.new(declared_params(include_missing: false))
+
if email.save
NotificationService.new.new_email(email)
present email, with: Entities::Email
@@ -241,98 +244,91 @@ module API
end
end
- # Get emails of a specified user. Only available to admin users.
- #
- # Parameters:
- # uid (required) - The ID of a user
- # Example Request:
- # GET /users/:uid/emails
- get ':uid/emails' do
+ desc 'Get the emails addresses of a specified user. Available only for admins.' do
+ success Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ get ':id/emails' do
authenticated_as_admin!
- user = User.find_by(id: params[:uid])
+ user = User.find_by(id: params[:id])
not_found!('User') unless user
present user.emails, with: Entities::Email
end
- # Delete existing email of a specified user. Only available to admin
- # users.
- #
- # Parameters:
- # uid (required) - The ID of a user
- # id (required) - Email ID
- # Example Request:
- # DELETE /users/:uid/emails/:id
- delete ':uid/emails/:id' do
+ desc 'Delete an email address of a specified user. Available only for admins.' do
+ success Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :email_id, type: Integer, desc: 'The ID of the email'
+ end
+ delete ':id/emails/:email_id' do
authenticated_as_admin!
- user = User.find_by(id: params[:uid])
+ user = User.find_by(id: params[:id])
not_found!('User') unless user
- begin
- email = user.emails.find params[:id]
- email.destroy
+ email = user.emails.find_by(id: params[:email_id])
+ not_found!('Email') unless email
- user.update_secondary_emails!
- rescue ActiveRecord::RecordNotFound
- not_found!('Email')
- end
+ email.destroy
+ user.update_secondary_emails!
end
- # Delete user. Available only for admin
- #
- # Example Request:
- # DELETE /users/:id
+ desc 'Delete a user. Available only for admins.' do
+ success Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
delete ":id" do
authenticated_as_admin!
user = User.find_by(id: params[:id])
+ not_found!('User') unless user
- if user
- DeleteUserService.new(current_user).execute(user)
- else
- not_found!('User')
- end
+ DeleteUserService.new(current_user).execute(user)
end
- # Block user. Available only for admin
- #
- # Example Request:
- # PUT /users/:id/block
+ desc 'Block a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
put ':id/block' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
+ not_found!('User') unless user
- if !user
- not_found!('User')
- elsif !user.ldap_blocked?
+ if !user.ldap_blocked?
user.block
else
forbidden!('LDAP blocked users cannot be modified by the API')
end
end
- # Unblock user. Available only for admin
- #
- # Example Request:
- # PUT /users/:id/unblock
+ desc 'Unblock a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
put ':id/unblock' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
+ not_found!('User') unless user
- if !user
- not_found!('User')
- elsif user.ldap_blocked?
+ if user.ldap_blocked?
forbidden!('LDAP blocked users cannot be unblocked by the API')
else
user.activate
end
end
- desc 'Get contribution events of a specified user' do
+ desc 'Get the contribution events of a specified user' do
detail 'This feature was introduced in GitLab 8.13.'
success Entities::Event
end
params do
- requires :id, type: String, desc: 'The user ID'
+ requires :id, type: Integer, desc: 'The ID of the user'
end
get ':id/events' do
user = User.find_by(id: params[:id])
@@ -349,43 +345,43 @@ module API
end
resource :user do
- # Get currently authenticated user
- #
- # Example Request:
- # GET /user
+ desc 'Get the currently authenticated user' do
+ success Entities::UserFull
+ end
get do
- present @current_user, with: Entities::UserFull
+ present current_user, with: Entities::UserFull
end
- # Get currently authenticated user's keys
- #
- # Example Request:
- # GET /user/keys
+ desc "Get the currently authenticated user's SSH keys" do
+ success Entities::SSHKey
+ end
get "keys" do
present current_user.keys, with: Entities::SSHKey
end
- # Get single key owned by currently authenticated user
- #
- # Example Request:
- # GET /user/keys/:id
- get "keys/:id" do
- key = current_user.keys.find params[:id]
+ desc 'Get a single key owned by currently authenticated user' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ get "keys/:key_id" do
+ key = current_user.keys.find_by(id: params[:key_id])
+ not_found!('Key') unless key
+
present key, with: Entities::SSHKey
end
- # Add new ssh key to currently authenticated user
- #
- # Parameters:
- # key (required) - New SSH Key
- # title (required) - New SSH Key's title
- # Example Request:
- # POST /user/keys
+ desc 'Add a new SSH key to the currently authenticated user' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :key, type: String, desc: 'The new SSH key'
+ requires :title, type: String, desc: 'The title of the new SSH key'
+ end
post "keys" do
- required_attributes! [:title, :key]
+ key = current_user.keys.new(declared_params)
- attrs = attributes_for_keys [:title, :key]
- key = current_user.keys.new attrs
if key.save
present key, with: Entities::SSHKey
else
@@ -393,48 +389,48 @@ module API
end
end
- # Delete existing ssh key of currently authenticated user
- #
- # Parameters:
- # id (required) - SSH Key ID
- # Example Request:
- # DELETE /user/keys/:id
- delete "keys/:id" do
- begin
- key = current_user.keys.find params[:id]
- key.destroy
- rescue
- end
+ desc 'Delete an SSH key from the currently authenticated user' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete "keys/:key_id" do
+ key = current_user.keys.find_by(id: params[:key_id])
+ not_found!('Key') unless key
+
+ present key.destroy, with: Entities::SSHKey
end
- # Get currently authenticated user's emails
- #
- # Example Request:
- # GET /user/emails
+ desc "Get the currently authenticated user's email addresses" do
+ success Entities::Email
+ end
get "emails" do
present current_user.emails, with: Entities::Email
end
- # Get single email owned by currently authenticated user
- #
- # Example Request:
- # GET /user/emails/:id
- get "emails/:id" do
- email = current_user.emails.find params[:id]
+ desc 'Get a single email address owned by the currently authenticated user' do
+ success Entities::Email
+ end
+ params do
+ requires :email_id, type: Integer, desc: 'The ID of the email'
+ end
+ get "emails/:email_id" do
+ email = current_user.emails.find_by(id: params[:email_id])
+ not_found!('Email') unless email
+
present email, with: Entities::Email
end
- # Add new email to currently authenticated user
- #
- # Parameters:
- # email (required) - Email address
- # Example Request:
- # POST /user/emails
+ desc 'Add new email address to the currently authenticated user' do
+ success Entities::Email
+ end
+ params do
+ requires :email, type: String, desc: 'The new email'
+ end
post "emails" do
- required_attributes! [:email]
+ email = current_user.emails.new(declared_params)
- attrs = attributes_for_keys [:email]
- email = current_user.emails.new attrs
if email.save
NotificationService.new.new_email(email)
present email, with: Entities::Email
@@ -443,20 +439,16 @@ module API
end
end
- # Delete existing email of currently authenticated user
- #
- # Parameters:
- # id (required) - EMail ID
- # Example Request:
- # DELETE /user/emails/:id
- delete "emails/:id" do
- begin
- email = current_user.emails.find params[:id]
- email.destroy
+ desc 'Delete an email address from the currently authenticated user'
+ params do
+ requires :email_id, type: Integer, desc: 'The ID of the email'
+ end
+ delete "emails/:email_id" do
+ email = current_user.emails.find_by(id: params[:email_id])
+ not_found!('Email') unless email
- current_user.update_secondary_emails!
- rescue
- end
+ email.destroy
+ current_user.update_secondary_emails!
end
end
end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 1eaafdce389..6e3f76b8399 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
retry() {
if eval "$@"; then
@@ -24,11 +24,12 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
- export FLAGS=(--path vendor --retry 3 --quiet)
+ export FLAGS="--path vendor --retry 3 --quiet"
else
- export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
+ rnd=$(awk 'BEGIN { srand() ; printf("%d\n",rand()*5) }')
+ export PATH="$HOME/bin:/usr/local/bin:/usr/bin:/bin"
cp config/database.yml.mysql config/database.yml
sed "s/username\:.*$/username\: runner/" -i config/database.yml
sed "s/password\:.*$/password\: 'password'/" -i config/database.yml
- sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml
+ sed "s/gitlabhq_test/gitlabhq_test_$rnd/" -i config/database.yml
fi
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 07a2c316899..d6807941b31 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -258,6 +258,7 @@ Service:
- template
- push_events
- issues_events
+- commit_events
- merge_requests_events
- tag_push_events
- note_events
@@ -339,4 +340,4 @@ LabelPriority:
- label_id
- priority
- created_at
-- updated_at \ No newline at end of file
+- updated_at
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index d8c47322220..f5da967cd14 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -83,7 +83,8 @@ describe JiraService, models: true do
url: 'http://jira.example.com',
username: 'gitlab_jira_username',
password: 'gitlab_jira_password',
- project_key: 'GitLabProject'
+ project_key: 'GitLabProject',
+ jira_issue_transition_id: "custom-id"
)
# These stubs are needed to test JiraService#close_issue.
@@ -177,11 +178,10 @@ describe JiraService, models: true do
end
it "calls the api with jira_issue_transition_id" do
- @jira_service.jira_issue_transition_id = 'this-is-a-custom-id'
@jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @transitions_url).with(
- body: /this-is-a-custom-id/
+ body: /custom-id/
).once
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 7011bdc9ec0..d83f7883c78 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -41,6 +41,52 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/pipeline ' do
+ context 'authorized user' do
+ context 'with gitlab-ci.yml' do
+ before { stub_ci_pipeline_to_return_yaml_file }
+
+ it 'creates and returns a new pipeline' do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
+ end.to change { Ci::Pipeline.count }.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to be_a Hash
+ expect(json_response['sha']).to eq project.commit.id
+ end
+
+ it 'fails when using an invalid ref' do
+ post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref'
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['base'].first).to eq 'Reference not found'
+ expect(json_response).not_to be_an Array
+ end
+ end
+
+ context 'without gitlab-ci.yml' do
+ it 'fails to create pipeline' do
+ post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file'
+ expect(json_response).not_to be_an Array
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not create pipeline' do
+ post api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ expect(json_response).not_to be_an Array
+ end
+ end
+ end
+
describe 'GET /projects/:id/pipelines/:pipeline_id' do
context 'authorized user' do
it 'returns project pipelines' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 34d1f557e4b..1a6e7716b2f 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -108,7 +108,7 @@ describe API::API, api: true do
it "returns a 404 error if user id not found" do
get api("/users/9999", user)
expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 User Not Found')
end
it "returns a 404 for invalid ID" do
@@ -359,7 +359,7 @@ describe API::API, api: true do
it "returns 404 for non-existing user" do
put api("/users/999999", admin), { bio: 'update should fail' }
expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 User Not Found')
end
it "returns a 404 if invalid ID" do
@@ -387,6 +387,18 @@ describe API::API, api: true do
to eq([Gitlab::Regex.namespace_regex_message])
end
+ it 'returns 400 if provider is missing for identity update' do
+ put api("/users/#{omniauth_user.id}", admin), extern_uid: '654321'
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 400 if external UID is missing for identity update' do
+ put api("/users/#{omniauth_user.id}", admin), provider: 'ldap'
+
+ expect(response).to have_http_status(400)
+ end
+
context "with existing user" do
before do
post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' }
@@ -414,14 +426,16 @@ describe API::API, api: true do
it "does not create invalid ssh key" do
post api("/users/#{user.id}/keys", admin), { title: "invalid key" }
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('400 (Bad request) "key" not given')
+ expect(json_response['error']).to eq('key is missing')
end
it 'does not create key without title' do
post api("/users/#{user.id}/keys", admin), key: 'some key'
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('400 (Bad request) "title" not given')
+ expect(json_response['error']).to eq('title is missing')
end
it "creates ssh key" do
@@ -437,7 +451,7 @@ describe API::API, api: true do
end
end
- describe 'GET /user/:uid/keys' do
+ describe 'GET /user/:id/keys' do
before { admin }
context 'when unauthenticated' do
@@ -465,7 +479,7 @@ describe API::API, api: true do
end
end
- describe 'DELETE /user/:uid/keys/:id' do
+ describe 'DELETE /user/:id/keys/:key_id' do
before { admin }
context 'when unauthenticated' do
@@ -506,8 +520,9 @@ describe API::API, api: true do
it "does not create invalid email" do
post api("/users/#{user.id}/emails", admin), {}
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('400 (Bad request) "email" not given')
+ expect(json_response['error']).to eq('email is missing')
end
it "creates email" do
@@ -524,7 +539,7 @@ describe API::API, api: true do
end
end
- describe 'GET /user/:uid/emails' do
+ describe 'GET /user/:id/emails' do
before { admin }
context 'when unauthenticated' do
@@ -558,7 +573,7 @@ describe API::API, api: true do
end
end
- describe 'DELETE /user/:uid/emails/:id' do
+ describe 'DELETE /user/:id/emails/:email_id' do
before { admin }
context 'when unauthenticated' do
@@ -673,7 +688,7 @@ describe API::API, api: true do
end
end
- describe "GET /user/keys/:id" do
+ describe "GET /user/keys/:key_id" do
it "returns single key" do
user.keys << key
user.save
@@ -686,7 +701,7 @@ describe API::API, api: true do
get api("/user/keys/42", user)
expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 Key Not Found')
end
it "returns 404 error if admin accesses user's ssh key" do
@@ -695,7 +710,7 @@ describe API::API, api: true do
admin
get api("/user/keys/#{key.id}", admin)
expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 Key Not Found')
end
it "returns 404 for invalid ID" do
@@ -721,14 +736,16 @@ describe API::API, api: true do
it "does not create ssh key without key" do
post api("/user/keys", user), title: 'title'
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('400 (Bad request) "key" not given')
+ expect(json_response['error']).to eq('key is missing')
end
it 'does not create ssh key without title' do
post api('/user/keys', user), key: 'some key'
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('400 (Bad request) "title" not given')
+ expect(json_response['error']).to eq('title is missing')
end
it "does not create ssh key without title" do
@@ -737,7 +754,7 @@ describe API::API, api: true do
end
end
- describe "DELETE /user/keys/:id" do
+ describe "DELETE /user/keys/:key_id" do
it "deletes existed key" do
user.keys << key
user.save
@@ -747,9 +764,11 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
end
- it "returns success if key ID not found" do
+ it "returns 404 if key ID not found" do
delete api("/user/keys/42", user)
- expect(response).to have_http_status(200)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Key Not Found')
end
it "returns 401 error if unauthorized" do
@@ -786,7 +805,7 @@ describe API::API, api: true do
end
end
- describe "GET /user/emails/:id" do
+ describe "GET /user/emails/:email_id" do
it "returns single email" do
user.emails << email
user.save
@@ -798,7 +817,7 @@ describe API::API, api: true do
it "returns 404 Not Found within invalid ID" do
get api("/user/emails/42", user)
expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 Email Not Found')
end
it "returns 404 error if admin accesses user's email" do
@@ -807,7 +826,7 @@ describe API::API, api: true do
admin
get api("/user/emails/#{email.id}", admin)
expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 Email Not Found')
end
it "returns 404 for invalid ID" do
@@ -833,12 +852,13 @@ describe API::API, api: true do
it "does not create email with invalid email" do
post api("/user/emails", user), {}
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('400 (Bad request) "email" not given')
+ expect(json_response['error']).to eq('email is missing')
end
end
- describe "DELETE /user/emails/:id" do
+ describe "DELETE /user/emails/:email_id" do
it "deletes existed email" do
user.emails << email
user.save
@@ -848,9 +868,11 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
end
- it "returns success if email ID not found" do
+ it "returns 404 if email ID not found" do
delete api("/user/emails/42", user)
- expect(response).to have_http_status(200)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Email Not Found')
end
it "returns 401 error if unauthorized" do
@@ -860,10 +882,10 @@ describe API::API, api: true do
expect(response).to have_http_status(401)
end
- it "returns a 404 for invalid ID" do
- delete api("/users/emails/ASDF", admin)
+ it "returns 400 for invalid ID" do
+ delete api("/user/emails/ASDF", admin)
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(400)
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 61dca5d5a62..7aba4f08088 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -261,20 +261,28 @@ describe "Authentication", "routing" do
end
describe "Groups", "routing" do
+ let(:name) { 'complex.group-name' }
+
it "to #show" do
- expect(get("/groups/1")).to route_to('groups#show', id: '1')
+ expect(get("/groups/#{name}")).to route_to('groups#show', id: name)
end
it "also display group#show on the short path" do
allow(Group).to receive(:find_by).and_return(true)
- expect(get('/1')).to route_to('groups#show', id: '1')
+ expect(get("/#{name}")).to route_to('groups#show', id: name)
end
- it "also display group#show with dot in the path" do
- allow(Group).to receive(:find_by).and_return(true)
+ it "to #activity" do
+ expect(get("/groups/#{name}/activity")).to route_to('groups#activity', id: name)
+ end
+
+ it "to #issues" do
+ expect(get("/groups/#{name}/issues")).to route_to('groups#issues', id: name)
+ end
- expect(get('/group.with.dot')).to route_to('groups#show', id: 'group.with.dot')
+ it "to #members" do
+ expect(get("/groups/#{name}/group_members")).to route_to('groups/group_members#index', group_id: name)
end
end
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index dd656c3bbb7..a44312dd363 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -1,13 +1,22 @@
require 'spec_helper'
-# Write specs in this file.
describe MergeRequests::AddTodoWhenBuildFailsService do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { create(:project) }
let(:sha) { '1234567890abcdef1234567890abcdef12345678' }
- let(:pipeline) { create(:ci_pipeline_with_one_job, ref: merge_request.source_branch, project: project, sha: sha) }
- let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user, commit_message: 'Awesome message') }
+ let(:ref) { merge_request.source_branch }
+
+ let(:pipeline) do
+ create(:ci_pipeline_with_one_job, ref: ref,
+ project: project,
+ sha: sha)
+ end
+
+ let(:service) do
+ described_class.new(project, user, commit_message: 'Awesome message')
+ end
+
let(:todo_service) { TodoService.new }
let(:merge_request) do
@@ -23,7 +32,9 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
describe '#execute' do
context 'commit status with ref' do
- let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, pipeline: pipeline) }
+ let(:commit_status) do
+ create(:generic_commit_status, ref: ref, pipeline: pipeline)
+ end
it 'notifies the todo service' do
expect(todo_service).to receive(:merge_request_build_failed).with(merge_request)
@@ -32,7 +43,7 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
end
context 'commit status with non-HEAD ref' do
- let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch) }
+ let(:commit_status) { create(:generic_commit_status, ref: ref) }
it 'does not notify the todo service' do
expect(todo_service).not_to receive(:merge_request_build_failed)
@@ -48,6 +59,18 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
service.execute(commit_status)
end
end
+
+ context 'when commit status is a build allowed to fail' do
+ let(:commit_status) do
+ create(:ci_build, :allowed_to_fail, ref: ref, pipeline: pipeline)
+ end
+
+ it 'does not create todo' do
+ expect(todo_service).not_to receive(:merge_request_build_failed)
+
+ service.execute(commit_status)
+ end
+ end
end
describe '#close' do
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 1fd9f5a4910..7db32a33c93 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -59,10 +59,14 @@ describe MergeRequests::MergeService, services: true do
include JiraServiceHelper
let(:jira_tracker) { project.create_jira_service }
+ let(:jira_issue) { ExternalIssue.new('JIRA-123', project) }
+ let(:commit) { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") }
before do
project.update_attributes!(has_external_issue_tracker: true)
jira_service_settings
+ stub_jira_urls(jira_issue.id)
+ allow(merge_request).to receive(:commits).and_return([commit])
end
it 'closes issues on JIRA issue tracker' do
@@ -76,6 +80,18 @@ describe MergeRequests::MergeService, services: true do
service.execute(merge_request)
end
+ context "when jira_issue_transition_id is not present" do
+ before { allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(nil) }
+
+ it "does not close issue" do
+ allow(jira_tracker).to receive_messages(jira_issue_transition_id: nil)
+
+ expect_any_instance_of(JiraService).not_to receive(:transition_issue)
+
+ service.execute(merge_request)
+ end
+ end
+
context "wrong issue markdown" do
it 'does not close issues on JIRA issue tracker' do
jira_issue = ExternalIssue.new('#JIRA-123', project)
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 56d39e9a005..150e21574a1 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -536,7 +536,7 @@ describe SystemNoteService, services: true do
let(:project) { create(:jira_project) }
let(:author) { create(:user) }
let(:issue) { create(:issue, project: project) }
- let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
+ let(:merge_request) { create(:merge_request, :simple, target_project: project, source_project: project) }
let(:jira_issue) { ExternalIssue.new("JIRA-1", project)}
let(:jira_tracker) { project.jira_service }
let(:commit) { project.commit }
@@ -545,7 +545,31 @@ describe SystemNoteService, services: true do
before { stub_jira_urls(jira_issue.id) }
- context 'in issue' do
+ noteable_types = ["merge_requests", "commit"]
+
+ noteable_types.each do |type|
+ context "when noteable is a #{type}" do
+ it "blocks cross reference when #{type.underscore}_events is false" do
+ jira_tracker.update("#{type}_events" => false)
+
+ noteable = type == "commit" ? commit : merge_request
+ result = described_class.cross_reference(jira_issue, noteable, author)
+
+ expect(result).to eq("Events for #{noteable.class.to_s.underscore.humanize.pluralize.downcase} are disabled.")
+ end
+
+ it "blocks cross reference when #{type.underscore}_events is true" do
+ jira_tracker.update("#{type}_events" => true)
+
+ noteable = type == "commit" ? commit : merge_request
+ result = described_class.cross_reference(jira_issue, noteable, author)
+
+ expect(result).to eq(success_message)
+ end
+ end
+ end
+
+ context 'in JIRA issue tracker' do
before { jira_service_settings }
describe "new reference" do
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
index 7437ba2688d..929fc0c5182 100644
--- a/spec/support/jira_service_helper.rb
+++ b/spec/support/jira_service_helper.rb
@@ -6,7 +6,8 @@ module JiraServiceHelper
properties = {
title: "JIRA tracker",
url: JIRA_URL,
- project_key: "JIRA"
+ project_key: "JIRA",
+ jira_issue_transition_id: '1'
}
jira_tracker.update_attributes(properties: properties, active: true)