Merge remote-tracking branch 'upstream/pipeline-hooks-without-slack' into wall-clock-time-for-showing-pipeline

* upstream/pipeline-hooks-without-slack:
  Make explicit call for all event types for ProjectHook factory
  Capitalise URL on web_hooks/form
  Remove changes not related to this MR
  Added documentation for pipeline hooks
  Rename queue to enqueue in tests
  Instrument Project.visible_to_user
  Fix build play failure
  Update ruby 2.3.1
  Improve transition between states for event `enqueue`
  Use event `enqueue` instead of `queue`
  Fix test failures
  Fix bug where destroying a namespace would not always destroy projects
  Remove unused SpamReport model; this was renamed to SpamLog
  Corrected links/usernames in performance guide
  Add gravatars to build history
  Add deployment ID and gravatar to environments page
  Format environment history page
  Add avatar to commit message; environment style updates to match pipelines page
  Style deploy button
This commit is contained in:
Lin Jen-Shin 2016-08-15 19:02:48 +08:00
commit 0ea81ae50a
37 changed files with 466 additions and 108 deletions

View file

@ -1,7 +1,7 @@
image: "ruby:2.3" image: "ruby:2.3.1"
cache: cache:
key: "ruby-23" key: "ruby-231"
paths: paths:
- vendor/apt - vendor/apt
- vendor/ruby - vendor/ruby

View file

@ -26,6 +26,7 @@ v 8.11.0 (unreleased)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Show wall clock time when showing a pipeline. !5734 - Show wall clock time when showing a pipeline. !5734
- Show member roles to all users on members page - Show member roles to all users on members page
- Project.visible_to_user is instrumented again
- Fix awardable button mutuality loading spinners (ClemMakesApps) - Fix awardable button mutuality loading spinners (ClemMakesApps)
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
- Optimize maximum user access level lookup in loading of notes - Optimize maximum user access level lookup in loading of notes
@ -94,6 +95,7 @@ v 8.11.0 (unreleased)
- Bump gitlab_git to lazy load compare commits - Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs - Reduce number of queries made for merge_requests/:id/diffs
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
- Fix bug where destroying a namespace would not always destroy projects
- Fix RequestProfiler::Middleware error when code is reloaded in development - Fix RequestProfiler::Middleware error when code is reloaded in development
- Catch what warden might throw when profiling requests to re-throw it - Catch what warden might throw when profiling requests to re-throw it
- Avoid commit lookup on diff_helper passing existing local variable to the helper method - Avoid commit lookup on diff_helper passing existing local variable to the helper method

View file

@ -1,5 +1,35 @@
.environments { .environments {
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
.fa-play {
font-size: 14px;
}
.dropdown-new {
color: $table-text-gray;
}
.dropdown-menu {
.fa {
margin-right: 6px;
color: $table-text-gray;
}
}
.branch-name {
color: $gl-dark-link-color;
}
}
.table.builds.environments {
min-width: 500px;
.icon-container {
width: 20px;
text-align: center;
}
} }

View file

@ -48,9 +48,9 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def destroy def destroy
DestroyGroupService.new(@group, current_user).execute DestroyGroupService.new(@group, current_user).async_execute
redirect_to admin_groups_path, notice: 'Group was successfully deleted.' redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end end
private private

View file

@ -87,9 +87,9 @@ class GroupsController < Groups::ApplicationController
end end
def destroy def destroy
DestroyGroupService.new(@group, current_user).execute DestroyGroupService.new(@group, current_user).async_execute
redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted." redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end end
protected protected

View file

@ -7,8 +7,6 @@ module AvatarsHelper
})) }))
end end
private
def user_avatar(options = {}) def user_avatar(options = {})
avatar_size = options[:size] || 16 avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name] user_name = options[:user].try(:name) || options[:user_name]

View file

@ -59,7 +59,7 @@ module Ci
when: build.when, when: build.when,
user: user, user: user,
environment: build.environment, environment: build.environment,
status_event: 'queue' status_event: 'enqueue'
) )
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build new_build
@ -102,7 +102,7 @@ module Ci
def play(current_user = nil) def play(current_user = nil)
# Try to queue a current build # Try to queue a current build
if self.queue if self.enqueue
self.update(user: current_user) self.update(user: current_user)
self self
else else

View file

@ -22,9 +22,9 @@ module Ci
delegate :stages, to: :statuses delegate :stages, to: :statuses
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :queue do event :enqueue do
transition created: :pending transition created: :pending
transition any - [:created, :pending] => :running transition [:success, :failed, :canceled, :skipped] => :running
end end
event :run do event :run do
@ -234,18 +234,12 @@ module Ci
def build_updated def build_updated
case latest_builds_status case latest_builds_status
when 'pending' when 'pending' then enqueue
queue when 'running' then run
when 'running' when 'success' then succeed
run when 'failed' then drop
when 'success' when 'canceled' then cancel
succeed when 'skipped' then skip
when 'failed'
drop
when 'canceled'
cancel
when 'skipped'
skip
end end
end end

View file

@ -27,7 +27,7 @@ class CommitStatus < ActiveRecord::Base
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status do state_machine :status do
event :queue do event :enqueue do
transition [:created, :skipped] => :pending transition [:created, :skipped] => :pending
end end

View file

@ -1,4 +1,6 @@
class Namespace < ActiveRecord::Base class Namespace < ActiveRecord::Base
acts_as_paranoid
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter

View file

@ -1,5 +0,0 @@
class SpamReport < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end

View file

@ -37,7 +37,7 @@ module Ci
return false unless Statuseable::COMPLETED_STATUSES.include?(current_status) return false unless Statuseable::COMPLETED_STATUSES.include?(current_status)
if valid_statuses_for_when(build.when).include?(current_status) if valid_statuses_for_when(build.when).include?(current_status)
build.queue build.enqueue
true true
else else
build.skip build.skip

View file

@ -21,6 +21,11 @@ class DeleteUserService
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
end end
user.destroy # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
namespace = user.namespace
user_data = user.destroy
namespace.really_destroy!
user_data
end end
end end

View file

@ -5,13 +5,23 @@ class DestroyGroupService
@group, @current_user = group, user @group, @current_user = group, user
end end
def async_execute
group.transaction do
# Soft delete via paranoia gem
group.destroy
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
end
def execute def execute
group.projects.each do |project| group.projects.each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
# Skip repository removal because we remove directory with namespace # Skip repository removal because we remove directory with namespace
# that contain all this repositories # that contain all these repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end end
group.destroy group.really_destroy!
end end
end end

View file

@ -2,9 +2,9 @@
.pull-right .pull-right
- actions = deployment.manual_actions - actions = deployment.manual_actions
- if actions.present? - if actions.present?
.btn-group.inline .inline
.btn-group .dropdown
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play") = icon("play")
%b.caret %b.caret
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right

View file

@ -1,12 +1,16 @@
%div.branch-commit %div.branch-commit
- if deployment.ref - if deployment.ref
= link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace" .icon-container
&middot; = deployment.tag? ? icon('tag') : icon('code-fork')
= link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace branch-name"
.icon-container
= custom_icon("icon_commit")
= link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace"
%p.commit-title %p.commit-title
%span %span
- if commit_title = deployment.commit_title - if commit_title = deployment.commit_title
= author_avatar(deployment.commit, size: 20)
= link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message" = link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message"
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch

View file

@ -8,6 +8,7 @@
%td %td
- if deployment.deployable - if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
= user_avatar(user: deployment.user, size: 20)
= "#{deployment.deployable.name} (##{deployment.deployable.id})" = "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td %td

View file

@ -2,8 +2,12 @@
%tr.environment %tr.environment
%td %td
%strong = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment)
= link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment)
%td
- if last_deployment
= user_avatar(user: last_deployment.user, size: 20)
%strong ##{last_deployment.id}
%td %td
- if last_deployment - if last_deployment

View file

@ -23,10 +23,11 @@
New environment New environment
- else - else
.table-holder .table-holder
%table.table.environments %table.table.builds.environments
%tbody %tbody
%th Environment %th Environment
%th Last deployment %th Last Deployment
%th Date %th Commit
%th
%th %th
= render @environments = render @environments

View file

@ -23,13 +23,13 @@
= link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else - else
.table-holder .table-holder
%table.table.environments %table.table.builds.environments
%thead %thead
%tr %tr
%th ID %th ID
%th Commit %th Commit
%th Build %th Build
%th Date %th
%th %th
= render @deployments = render @deployments

View file

@ -29,56 +29,56 @@
= f.label :push_events, class: 'list-label' do = f.label :push_events, class: 'list-label' do
%strong Push events %strong Push events
%p.light %p.light
This url will be triggered by a push to the repository This URL will be triggered by a push to the repository
%li %li
= f.check_box :tag_push_events, class: 'pull-left' = f.check_box :tag_push_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :tag_push_events, class: 'list-label' do = f.label :tag_push_events, class: 'list-label' do
%strong Tag push events %strong Tag push events
%p.light %p.light
This url will be triggered when a new tag is pushed to the repository This URL will be triggered when a new tag is pushed to the repository
%li %li
= f.check_box :note_events, class: 'pull-left' = f.check_box :note_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :note_events, class: 'list-label' do = f.label :note_events, class: 'list-label' do
%strong Comments %strong Comments
%p.light %p.light
This url will be triggered when someone adds a comment This URL will be triggered when someone adds a comment
%li %li
= f.check_box :issues_events, class: 'pull-left' = f.check_box :issues_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :issues_events, class: 'list-label' do = f.label :issues_events, class: 'list-label' do
%strong Issues events %strong Issues events
%p.light %p.light
This url will be triggered when an issue is created/updated/merged This URL will be triggered when an issue is created/updated/merged
%li %li
= f.check_box :merge_requests_events, class: 'pull-left' = f.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :merge_requests_events, class: 'list-label' do = f.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events %strong Merge Request events
%p.light %p.light
This url will be triggered when a merge request is created/updated/merged This URL will be triggered when a merge request is created/updated/merged
%li %li
= f.check_box :build_events, class: 'pull-left' = f.check_box :build_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :build_events, class: 'list-label' do = f.label :build_events, class: 'list-label' do
%strong Build events %strong Build events
%p.light %p.light
This url will be triggered when the build status changes This URL will be triggered when the build status changes
%li %li
= f.check_box :pipeline_events, class: 'pull-left' = f.check_box :pipeline_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :pipeline_events, class: 'list-label' do = f.label :pipeline_events, class: 'list-label' do
%strong Pipeline events %strong Pipeline events
%p.light %p.light
This url will be triggered when the pipeline status changes This URL will be triggered when the pipeline status changes
%li %li
= f.check_box :wiki_page_events, class: 'pull-left' = f.check_box :wiki_page_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :wiki_page_events, class: 'list-label' do = f.label :wiki_page_events, class: 'list-label' do
%strong Wiki Page events %strong Wiki Page events
%p.light %p.light
This url will be triggered when a wiki page is created/updated This URL will be triggered when a wiki page is created/updated
.form-group .form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox' = f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
.checkbox .checkbox

View file

@ -0,0 +1,17 @@
class GroupDestroyWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform(group_id, user_id)
begin
group = Group.with_deleted.find(group_id)
rescue ActiveRecord::RecordNotFound
return
end
user = User.find(user_id)
DestroyGroupService.new(group, user).execute
end
end

View file

@ -148,6 +148,9 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Gitlab::Highlight) config.instrument_methods(Gitlab::Highlight)
config.instrument_instance_methods(Gitlab::Highlight) config.instrument_instance_methods(Gitlab::Highlight)
# This is a Rails scope so we have to instrument it manually.
config.instrument_method(Project, :visible_to_user)
end end
GC::Profiler.enable GC::Profiler.enable

View file

@ -1,8 +1,14 @@
# rubocop:disable all # rubocop:disable all
class FixNamespaces < ActiveRecord::Migration class FixNamespaces < ActiveRecord::Migration
DOWNTIME = false
def up def up
Namespace.where('name <> path and type is null').each do |namespace| namespaces = exec_query('SELECT id, path FROM namespaces WHERE name <> path and type is null')
namespace.update_attribute(:name, namespace.path)
namespaces.each do |row|
id = row['id']
path = row['path']
exec_query("UPDATE namespaces SET name = '#{path}' WHERE id = #{id}")
end end
end end

View file

@ -0,0 +1,12 @@
class AddDeletedAtToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_column :namespaces, :deleted_at, :datetime
add_concurrent_index :namespaces, :deleted_at
end
end

View file

@ -640,9 +640,11 @@ ActiveRecord::Schema.define(version: 20160810142633) do
t.boolean "share_with_group_lock", default: false t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: true, null: false t.boolean "request_access_enabled", default: true, null: false
t.datetime "deleted_at"
end end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
add_index "namespaces", ["deleted_at"], name: "index_namespaces_on_deleted_at", using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree

View file

@ -15,8 +15,8 @@ The process of solving performance problems is roughly as follows:
3. Add your findings based on the measurement period (screenshots of graphs, 3. Add your findings based on the measurement period (screenshots of graphs,
timings, etc) to the issue mentioned in step 1. timings, etc) to the issue mentioned in step 1.
4. Solve the problem. 4. Solve the problem.
5. Create a merge request, assign the "performance" label and ping the right 5. Create a merge request, assign the "Performance" label and assign it to
people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]). [@yorickpeterse][yorickpeterse] for reviewing.
6. Once a change has been deployed make sure to _again_ measure for at least 24 6. Once a change has been deployed make sure to _again_ measure for at least 24
hours to see if your changes have any impact on the production environment. hours to see if your changes have any impact on the production environment.
7. Repeat until you're done. 7. Repeat until you're done.
@ -36,8 +36,8 @@ graphs/dashboards.
GitLab provides two built-in tools to aid the process of improving performance: GitLab provides two built-in tools to aid the process of improving performance:
* [Sherlock](doc/development/profiling.md#sherlock) * [Sherlock](profiling.md#sherlock)
* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md) * [GitLab Performance Monitoring](../monitoring/performance/monitoring.md)
GitLab employees can use GitLab.com's performance monitoring systems located at GitLab employees can use GitLab.com's performance monitoring systems located at
<http://performance.gitlab.net>, this requires you to log in using your <http://performance.gitlab.net>, this requires you to log in using your
@ -254,5 +254,4 @@ referencing an object directly may even slow code down.
[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
[yorickpeterse]: https://gitlab.com/u/yorickpeterse [yorickpeterse]: https://gitlab.com/u/yorickpeterse
[joshfng]: https://gitlab.com/u/joshfng
[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern [anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern

View file

@ -754,6 +754,174 @@ X-Gitlab-Event: Wiki Page Hook
} }
``` ```
## Pipeline events
Triggered on status change of Pipeline.
**Request Header**:
```
X-Gitlab-Event: Pipeline Hook
```
**Request Body**:
```json
{
"object_kind": "pipeline",
"object_attributes":{
"id": 31,
"ref": "master",
"tag": false,
"sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"status": "success",
"stages":[
"build",
"test",
"deploy"
],
"created_at": "2016-08-12 15:23:28 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"duration": 63
},
"user":{
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"project":{
"name": "Gitlab Test",
"description": "Atque in sunt eos similique dolores voluptatem.",
"web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
"avatar_url": null,
"git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
"git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git",
"namespace": "Gitlab Org",
"visibility_level": 20,
"path_with_namespace": "gitlab-org/gitlab-test",
"default_branch": "master"
},
"commit":{
"id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"message": "test\n",
"timestamp": "2016-08-12T17:23:21+02:00",
"url": "http://example.com/gitlab-org/gitlab-test/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2",
"author":{
"name": "User",
"email": "user@gitlab.com"
}
},
"builds":[
{
"id": 380,
"stage": "deploy",
"name": "production",
"status": "skipped",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": null,
"finished_at": null,
"when": "manual",
"manual": true,
"user":{
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file":{
"filename": null,
"size": null
}
},
{
"id": 377,
"stage": "test",
"name": "test-image",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:26:12 UTC",
"finished_at": null,
"when": "on_success",
"manual": false,
"user":{
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file":{
"filename": null,
"size": null
}
},
{
"id": 378,
"stage": "test",
"name": "test-build",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:26:12 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"when": "on_success",
"manual": false,
"user":{
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file":{
"filename": null,
"size": null
}
},
{
"id": 376,
"stage": "build",
"name": "build-image",
"status": "success",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": "2016-08-12 15:24:56 UTC",
"finished_at": "2016-08-12 15:25:26 UTC",
"when": "on_success",
"manual": false,
"user":{
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file":{
"filename": null,
"size": null
}
},
{
"id": 379,
"stage": "deploy",
"name": "staging",
"status": "created",
"created_at": "2016-08-12 15:23:28 UTC",
"started_at": null,
"finished_at": null,
"when": "on_success",
"manual": false,
"user":{
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
"runner": null,
"artifacts_file":{
"filename": null,
"size": null
}
}
]
}
```
#### Example webhook receiver #### Example webhook receiver
If you want to see GitLab's webhooks in action for testing purposes you can use If you want to see GitLab's webhooks in action for testing purposes you can use

View file

@ -0,0 +1,24 @@
require 'spec_helper'
describe Admin::GroupsController do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:admin) { create(:admin) }
before do
sign_in(admin)
Sidekiq::Testing.fake!
end
describe 'DELETE #destroy' do
it 'schedules a group destroy' do
expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
it 'redirects to the admin group path' do
delete :destroy, id: project.group.path
expect(response).to redirect_to(admin_groups_path)
end
end
end

View file

@ -75,4 +75,33 @@ describe GroupsController do
end end
end end
end end
describe 'DELETE #destroy' do
context 'as another user' do
it 'returns 404' do
sign_in(create(:user))
delete :destroy, id: group.path
expect(response.status).to eq(404)
end
end
context 'as the group owner' do
before do
Sidekiq::Testing.fake!
sign_in(user)
end
it 'schedules a group destroy' do
expect { delete :destroy, id: group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
it 'redirects to the root path' do
delete :destroy, id: group.path
expect(response).to redirect_to(root_path)
end
end
end
end end

View file

@ -7,15 +7,13 @@ FactoryGirl.define do
end end
trait :all_events_enabled do trait :all_events_enabled do
%w[push_events push_events true
merge_requests_events merge_requests_events true
tag_push_events tag_push_events true
issues_events issues_events true
note_events note_events true
build_events build_events true
pipeline_events].each do |event| pipeline_events true
send(event, true)
end
end end
end end
end end

View file

@ -887,8 +887,10 @@ describe Ci::Build, models: true do
is_expected.to eq(build) is_expected.to eq(build)
end end
context 'for success build' do context 'for successful build' do
before { build.queue } before do
build.update(status: 'success')
end
it 'creates a new build' do it 'creates a new build' do
is_expected.to be_pending is_expected.to be_pending

View file

@ -145,7 +145,7 @@ describe Ci::Pipeline, models: true do
expect(pipeline.reload.started_at).not_to be_nil expect(pipeline.reload.started_at).not_to be_nil
end end
it 'do not update on transitioning to success' do it 'does not update on transitioning to success' do
build.success build.success
expect(pipeline.reload.started_at).to be_nil expect(pipeline.reload.started_at).to be_nil
@ -159,7 +159,7 @@ describe Ci::Pipeline, models: true do
expect(pipeline.reload.finished_at).not_to be_nil expect(pipeline.reload.finished_at).not_to be_nil
end end
it 'do not update on transitioning to running' do it 'does not update on transitioning to running' do
build.run build.run
expect(pipeline.reload.finished_at).to be_nil expect(pipeline.reload.finished_at).to be_nil
@ -259,14 +259,16 @@ describe Ci::Pipeline, models: true do
subject { pipeline.reload.status } subject { pipeline.reload.status }
context 'on queuing' do context 'on queuing' do
before { build.queue } before do
build.enqueue
end
it { is_expected.to eq('pending') } it { is_expected.to eq('pending') }
end end
context 'on run' do context 'on run' do
before do before do
build.queue build.enqueue
build.run build.run
end end
@ -296,6 +298,19 @@ describe Ci::Pipeline, models: true do
it { is_expected.to eq('canceled') } it { is_expected.to eq('canceled') }
end end
context 'on failure and build retry' do
before do
build.drop
Ci::Build.retry(build)
end
# We are changing a state: created > failed > running
# Instead of: created > failed > pending
# Since the pipeline already run, so it should not be pending anymore
it { is_expected.to eq('running') }
end
end end
describe '#execute_hooks' do describe '#execute_hooks' do
@ -320,8 +335,8 @@ describe Ci::Pipeline, models: true do
context 'with multiple builds' do context 'with multiple builds' do
context 'when build is queued' do context 'when build is queued' do
before do before do
build_a.queue build_a.enqueue
build_b.queue build_b.enqueue
end end
it 'receive a pending event once' do it 'receive a pending event once' do
@ -331,9 +346,9 @@ describe Ci::Pipeline, models: true do
context 'when build is run' do context 'when build is run' do
before do before do
build_a.queue build_a.enqueue
build_a.run build_a.run
build_b.queue build_b.enqueue
build_b.run build_b.run
end end
@ -367,8 +382,8 @@ describe Ci::Pipeline, models: true do
let(:enabled) { false } let(:enabled) { false }
before do before do
build_a.queue build_a.enqueue
build_b.queue build_b.enqueue
end end
it 'did not execute pipeline_hook after touched' do it 'did not execute pipeline_hook after touched' do

View file

@ -564,12 +564,14 @@ describe API::API, api: true do
end end
describe "DELETE /users/:id" do describe "DELETE /users/:id" do
let!(:namespace) { user.namespace }
before { admin } before { admin }
it "deletes user" do it "deletes user" do
delete api("/users/#{user.id}", admin) delete api("/users/#{user.id}", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound
expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound
expect(json_response['email']).to eq(user.email) expect(json_response['email']).to eq(user.email)
end end

View file

@ -9,9 +9,11 @@ describe DeleteUserService, services: true do
context 'no options are given' do context 'no options are given' do
it 'deletes the user' do it 'deletes the user' do
DeleteUserService.new(current_user).execute(user) user_data = DeleteUserService.new(current_user).execute(user)
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) expect { user_data['email'].to eq(user.email) }
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end end
it 'will delete the project in the near future' do it 'will delete the project in the near future' do

View file

@ -7,38 +7,52 @@ describe DestroyGroupService, services: true do
let!(:gitlab_shell) { Gitlab::Shell.new } let!(:gitlab_shell) { Gitlab::Shell.new }
let!(:remove_path) { group.path + "+#{group.id}+deleted" } let!(:remove_path) { group.path + "+#{group.id}+deleted" }
context 'database records' do shared_examples 'group destruction' do |async|
before do context 'database records' do
destroy_group(group, user)
end
it { expect(Group.all).not_to include(group) }
it { expect(Project.all).not_to include(project) }
end
context 'file system' do
context 'Sidekiq inline' do
before do before do
# Run sidekiq immediatly to check that renamed dir will be removed destroy_group(group, user, async)
Sidekiq::Testing.inline! { destroy_group(group, user) }
end end
it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey } it { expect(Group.all).not_to include(group) }
it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey } it { expect(Project.all).not_to include(project) }
end end
context 'Sidekiq fake' do context 'file system' do
before do context 'Sidekiq inline' do
# Dont run sidekiq to check if renamed repository exists before do
Sidekiq::Testing.fake! { destroy_group(group, user) } # Run sidekiq immediatly to check that renamed dir will be removed
Sidekiq::Testing.inline! { destroy_group(group, user, async) }
end
it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey }
end end
it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey } context 'Sidekiq fake' do
it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy } before do
# Dont run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_group(group, user, async) }
end
it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy }
end
end
def destroy_group(group, user, async)
if async
DestroyGroupService.new(group, user).async_execute
else
DestroyGroupService.new(group, user).execute
end
end end
end end
def destroy_group(group, user) describe 'asynchronous delete' do
DestroyGroupService.new(group, user).execute it_behaves_like 'group destruction', true
end
describe 'synchronous delete' do
it_behaves_like 'group destruction', false
end end
end end

View file

@ -0,0 +1,19 @@
require 'spec_helper'
describe GroupDestroyWorker do
let(:group) { create(:group) }
let(:user) { create(:admin) }
let!(:project) { create(:project, namespace: group) }
subject { GroupDestroyWorker.new }
describe "#perform" do
it "deletes the project" do
subject.perform(group.id, user.id)
expect(Group.all).not_to include(group)
expect(Project.all).not_to include(project)
expect(Dir.exist?(project.path)).to be_falsey
end
end
end