Commit 2ec93abe authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into ci/persist-registration-token

* master: (66 commits)
  Fix runners admin view
  Fix migrations
  Rename mention of gitlab-git-http-server to gitlab-workhorse
  Bump Redis requirement to 2.8 for Sidekiq 4 requirements
  Fix wording on runner setup page
  add details on how to change saml button label
  Fix tests
  Move awards back to gray panel and few improvements to sidebar
  Few UI improvements to new sidebar implementation
  Fix tests for new issuable sidebar
  Update changelog
  Implement new sidebar for merge request page
  Make edit link on issuable sidebar works
  Redesign issue page for new sidebar
  Move awards css to separate file
  Implement issuable sidebar partial
  Update CHANGELOG
  Clarify cache behavior
  Run builds from projects with enabled CI
  Use Gitlab::Git instead of Ci::Git
  ...

Conflicts:
	db/schema.rb
parents bc1cfd38 c8102343
...@@ -117,9 +117,10 @@ flay: ...@@ -117,9 +117,10 @@ flay:
- mysql - mysql
bundler:audit: bundler:audit:
script: script:
- "bundle exec bundle-audit update" - "bundle exec bundle-audit update"
- "bundle exec bundle-audit check" - "bundle exec bundle-audit check"
tags: tags:
- ruby - ruby
- mysql - mysql
allow_failure: true
...@@ -735,23 +735,39 @@ Metrics/AbcSize: ...@@ -735,23 +735,39 @@ Metrics/AbcSize:
Description: >- Description: >-
A calculated magnitude based on number of assignments, A calculated magnitude based on number of assignments,
branches, and conditions. branches, and conditions.
Enabled: false Enabled: true
Max: 70
Metrics/CyclomaticComplexity:
Description: >-
A complexity metric that is strongly correlated to the number
of test cases needed to validate a method.
Enabled: true
Max: 17
Metrics/PerceivedComplexity:
Description: >-
A complexity metric geared towards measuring complexity for a
human reader.
Enabled: true
Max: 17
Metrics/ParameterLists:
Description: 'Avoid parameter lists longer than three or four parameters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
Enabled: true
Max: 8
Metrics/BlockNesting: Metrics/BlockNesting:
Description: 'Avoid excessive block nesting' Description: 'Avoid excessive block nesting'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
Enabled: false Enabled: true
Max: 4
Metrics/ClassLength: Metrics/ClassLength:
Description: 'Avoid classes longer than 100 lines of code.' Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false Enabled: false
Metrics/CyclomaticComplexity:
Description: >-
A complexity metric that is strongly correlated to the number
of test cases needed to validate a method.
Enabled: false
Metrics/LineLength: Metrics/LineLength:
Description: 'Limit lines to 80 characters.' Description: 'Limit lines to 80 characters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
...@@ -762,17 +778,6 @@ Metrics/MethodLength: ...@@ -762,17 +778,6 @@ Metrics/MethodLength:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false Enabled: false
Metrics/ParameterLists:
Description: 'Avoid parameter lists longer than three or four parameters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
Enabled: false
Metrics/PerceivedComplexity:
Description: >-
A complexity metric geared towards measuring complexity for a
human reader.
Enabled: false
#################### Lint ################################ #################### Lint ################################
### Warnings ### Warnings
......
...@@ -11,6 +11,7 @@ v 8.3.0 (unreleased) ...@@ -11,6 +11,7 @@ v 8.3.0 (unreleased)
- Update project repositorize size and commit count during import:repos task (Stan Hu) - Update project repositorize size and commit count during import:repos task (Stan Hu)
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu) - Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
- Handle and report SSL errors in Web hook test (Stan Hu) - Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission - Fix 500 error when update group member permission
...@@ -20,6 +21,7 @@ v 8.3.0 (unreleased) ...@@ -20,6 +21,7 @@ v 8.3.0 (unreleased)
- Fire update hook from GitLab - Fire update hook from GitLab
- Style warning about mentioning many people in a comment - Style warning about mentioning many people in a comment
- Fix: sort milestones by due date once again (Greg Smethells) - Fix: sort milestones by due date once again (Greg Smethells)
- Migrate all CI::Services and CI::WebHooks to Services and WebHooks
- Don't show project fork event as "imported" - Don't show project fork event as "imported"
- Add API endpoint to fetch merge request commits list - Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info - Expose events API with comment information and author info
...@@ -43,6 +45,12 @@ v 8.3.0 (unreleased) ...@@ -43,6 +45,12 @@ v 8.3.0 (unreleased)
- Prevent possible XSS attack with award-emoji - Prevent possible XSS attack with award-emoji
- Upgraded Sidekiq to 4.x - Upgraded Sidekiq to 4.x
- Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg) - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg)
- Fix emoji aliases problem
- Fix award-emojis Flash alert's width
- Fix deleting notes on a merge request diff
- Display referenced merge request statuses in the issue description (Greg Smethells)
- Implement new sidebar for issue and merge request pages
- Emoji picker improvements
v 8.2.3 v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu) - Fix application settings cache not expiring after changes (Stan Hu)
......
...@@ -93,6 +93,7 @@ gem 'html-pipeline', '~> 1.11.0' ...@@ -93,6 +93,7 @@ gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1' gem 'github-markup', '~> 1.3.1'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
......
...@@ -2,6 +2,7 @@ GEM ...@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (2.3.2) CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1) ace-rails-ap (2.0.1)
actionmailer (4.2.4) actionmailer (4.2.4)
actionpack (= 4.2.4) actionpack (= 4.2.4)
...@@ -826,6 +827,7 @@ PLATFORMS ...@@ -826,6 +827,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1) ace-rails-ap (~> 2.0.1)
activerecord-deprecated_finders (~> 1.0.3) activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0) activerecord-session_store (~> 0.1.0)
......
...@@ -69,7 +69,7 @@ GitLab is a Ruby on Rails application that runs on the following software: ...@@ -69,7 +69,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL - Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1 - Ruby (MRI) 2.1
- Git 1.7.10+ - Git 1.7.10+
- Redis 2.4+ - Redis 2.8+
- MySQL or PostgreSQL - MySQL or PostgreSQL
For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html). For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
......
class @AwardsHandler class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id) -> constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
addAward: (emoji) -> addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, => @postEmoji emoji, =>
@addAwardToEmojiBar(emoji) @addAwardToEmojiBar(emoji)
addAwardToEmojiBar: (emoji, custom_path = '') -> addAwardToEmojiBar: (emoji, custom_path = '') ->
emoji = @normilizeEmojiName(emoji)
if @exist(emoji) if @exist(emoji)
if @isActive(emoji) if @isActive(emoji)
@decrementCounter(emoji) @decrementCounter(emoji)
...@@ -94,3 +96,6 @@ class @AwardsHandler ...@@ -94,3 +96,6 @@ class @AwardsHandler
$('body, html').animate({ $('body, html').animate({
scrollTop: $('.awards').offset().top - 80 scrollTop: $('.awards').offset().top - 80
}, 200) }, 200)
normilizeEmojiName: (emoji) ->
@aliases[emoji] || emoji
...@@ -12,5 +12,5 @@ class @Flash ...@@ -12,5 +12,5 @@ class @Flash
@flash.click -> $(@).fadeOut() @flash.click -> $(@).fadeOut()
@flash.show() @flash.show()
pin: -> pinTo: (selector) ->
@flash.addClass('flash-pinned flash-raised') @flash.detach().appendTo(selector)
...@@ -5,9 +5,9 @@ class @IssuableContext ...@@ -5,9 +5,9 @@ class @IssuableContext
new UsersSelect() new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$(".context .inline-update").on "change", "select", -> $(".issuable-sidebar .inline-update").on "change", "select", ->
$(this).submit() $(this).submit()
$(".context .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$('.issuable-details').waitForImages -> $('.issuable-details').waitForImages ->
...@@ -18,6 +18,12 @@ class @IssuableContext ...@@ -18,6 +18,12 @@ class @IssuableContext
$('.issuable-affix').affix offset: $('.issuable-affix').affix offset:
top: -> top: ->
@top = ($('.issuable-affix').offset().top - 70) @top = ($('.issuable-affix').offset().top - 60)
bottom: -> bottom: ->
@bottom = $('.footer').outerHeight(true) @bottom = $('.footer').outerHeight(true)
$(".edit-link").click (e) ->
block = $(@).parents('.block')
block.find('.selectbox').show()
block.find('.value').hide()
block.find('.js-select2').select2("open")
...@@ -114,7 +114,7 @@ class @Notes ...@@ -114,7 +114,7 @@ class @Notes
unless note.valid unless note.valid
if note.award if note.award
flash = new Flash('You have already used this award emoji!', 'alert') flash = new Flash('You have already used this award emoji!', 'alert')
flash.pin() flash.pinTo('.header-content')
return return
# render note if it not present in loaded list # render note if it not present in loaded list
...@@ -350,18 +350,26 @@ class @Notes ...@@ -350,18 +350,26 @@ class @Notes
### ###
removeNote: -> removeNote: ->
note = $(this).closest(".note") note = $(this).closest(".note")
notes = note.closest(".notes") note_id = note.attr('id')
# check if this is the last note for this line $('.note[id="' + note_id + '"]').each ->
if notes.find(".note").length is 1 note = $(this)
notes = note.closest(".notes")
count = notes.closest(".notes_holder").find(".discussion-notes-count")
# for discussions # check if this is the last note for this line
notes.closest(".discussion").remove() if notes.find(".note").length is 1
# for diff lines # for discussions
notes.closest("tr").remove() notes.closest(".discussion").remove()
note.remove() # for diff lines
notes.closest("tr").remove()
else
# update notes count
count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
note.remove()
### ###
Called in response to clicking the delete attachment link Called in response to clicking the delete attachment link
......
...@@ -4,8 +4,11 @@ class @Project ...@@ -4,8 +4,11 @@ class @Project
$('.js-protocol-switch').click -> $('.js-protocol-switch').click ->
return if $(@).hasClass('active') return if $(@).hasClass('active')
# Toggle 'active' for both buttons
$('.js-protocol-switch').toggleClass('active') # Remove the active class for all buttons (ssh, http, kerberos if shown)
$('.active').not($(@)).removeClass('active');
# Add the active class for the clicked button
$(@).toggleClass('active')
url = $(@).data('clone') url = $(@).data('clone')
......
...@@ -461,3 +461,9 @@ table { ...@@ -461,3 +461,9 @@ table {
visibility: hidden; visibility: hidden;
} }
} }
.content-separator {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
border-top: 1px solid $border-color;
}
...@@ -15,13 +15,3 @@ ...@@ -15,13 +15,3 @@
@extend .alert-danger; @extend .alert-danger;
} }
} }
.flash-pinned {
position: fixed;
top: 80px;
width: 80%;
}
.flash-raised {
z-index: 1000;
}
...@@ -181,4 +181,4 @@ ...@@ -181,4 +181,4 @@
.ajax-users-dropdown { .ajax-users-dropdown {
min-width: 250px !important; min-width: 250px !important;
} }
\ No newline at end of file
.awards {
@include clearfix;
line-height: 34px;
.award {
@include border-radius(5px);
border: 1px solid;
padding: 0px 10px;
float: left;
margin-right: 5px;
border-color: $border-color;
cursor: pointer;
&:hover {
background-color: #dce0e5;
}
&.active {
border-color: $border-gray-light;
background-color: $gray-light;
&:hover {
background-color: #dce0e5;
}
.counter {
font-weight: bold;
}
}
.icon {
float: left;
margin-right: 10px;
}
.counter {
float: left;
}
}
.awards-controls {
margin-left: 10px;
float: left;
.add-award {
font-size: 24px;
color: $gl-gray;
position: relative;
top: 2px;
&:hover,
&:link {
text-decoration: none;
}
}
.awards-menu {
padding: $gl-padding;
min-width: 214px;
> li {
cursor: pointer;
width: 30px;
height: 30px;
text-align: center;
@include border-radius(5px);
img {
margin-bottom: 2px;
}
&:hover {
background-color: #ccc;
}
}
}
}
.awards-menu{
li {
float: left;
margin: 3px;
}
}
}
...@@ -18,26 +18,12 @@ ...@@ -18,26 +18,12 @@
&.affix { &.affix {
position: fixed; position: fixed;
top: 70px; top: 60px;
margin-right: 35px; margin-right: 35px;
} }
} }
} }
.issuable-context-title {
margin-bottom: 5px;
.avatar {
margin-left: 0;
}
label {
color: $gl-gray;
font-weight: normal;
margin-right: 4px;
}
}
.project-issuable-filter { .project-issuable-filter {
.controls { .controls {
float: right; float: right;
...@@ -50,23 +36,6 @@ ...@@ -50,23 +36,6 @@
} }
.issuable-details { .issuable-details {
.page-title {
margin-top: -$gl-padding;
padding: 7px 0;
margin-bottom: 0;
color: #5c5d5e;
font-size: 16px;
line-height: 42px;
.author {
color: #5c5d5e;
}
.issue-id {
color: #5c5d5e;
}
}
.issue-title { .issue-title {
margin: 0; margin: 0;
font-size: 23px; font-size: 23px;
...@@ -80,6 +49,21 @@ ...@@ -80,6 +49,21 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
section {
border-right: 1px solid #ECEEF1;
> .tab-content {
margin-right: 1px;
}
.issue-discussion > .gray-content-block,
> .gray-content-block {
margin-top: 0;
border-top: none;
margin-right: -15px;
}
}
} }
.issuable-filter-count { .issuable-filter-count {
...@@ -101,84 +85,72 @@ ...@@ -101,84 +85,72 @@
} }
} }
.cross-project-reference { .issuable-sidebar {
text-align: center; .block {
width: 100%; @include clearfix;
padding: $gl-padding 0;
.slead { border-bottom: 1px solid #F0F0F0;
padding: 5px;
}
span, button { &:last-child {
background-color: $background-color; border: none;
}
} }
}
.awards { .title {
@include clearfix; color: $gl-text-color;
line-height: 34px; margin-bottom: 8px;
margin: 2px 0;
.award { .avatar {
@include border-radius(5px); margin-left: 0;
}
border: 1px solid;
padding: 0px 10px;
float: left;
margin: 0 5px;
border-color: $border-color;
cursor: pointer;
&.active {
border-color: $border-gray-light;
background-color: $gray-light;
.counter { label {
font-weight: bold; font-weight: normal;
} margin-right: 4px;
} }
.icon { .edit-link {
float: left; color: $gl-gray;
margin-right: 10px;
} }
}
.cross-project-reference {
font-weight: bold;
color: $gl-link-color;
.counter { button {
float: left; float: right;
} }
} }
.awards-controls { .selectbox {
margin-left: 10px; display: none
float: left; }
.add-award { .btn-clipboard {
font-size: 24px; color: $gl-gray;
color: $gl-gray; }
position: relative;
top: 2px;
&:hover, .participants .avatar {
&:link { margin-top: 6px;
text-decoration: none; margin-right: 2px;
} }
} }
.awards-menu { .issuable-title {
padding: $gl-padding; margin: -$gl-padding;
min-width: 214px; padding: 7px $gl-padding;
margin-bottom: 0px;
border-bottom: 1px solid $border-color;
color: #5c5d5e;
font-size: 16px;
line-height: 42px;
> li { .author {
cursor: pointer; color: #5c5d5e;
margin: 5px;
}
}
} }
.awards-menu{ .issuable-id {
li { color: #5c5d5e;
float: left;
margin: 3px;
}
} }
} }
...@@ -60,6 +60,26 @@ form.edit-issue { ...@@ -60,6 +60,26 @@ form.edit-issue {
margin: 0; margin: 0;
} }
.merge-requests-title {
font-size: 16px;
font-weight: 600;
}
.merge-request-id {
display: inline-block;
width: 3em;
}
.merge-request-info {
padding-left: 5px;
}
.merge-request-status {
color: $gl-gray;
font-size: 15px;
font-weight: bold;
}
.merge-request, .merge-request,
.issue { .issue {
&.today { &.today {
......
...@@ -121,10 +121,6 @@ ...@@ -121,10 +121,6 @@
} }
} }
.merge-request-details {
margin-bottom: $gl-padding;
}
.mr_source_commit, .mr_source_commit,
.mr_target_commit { .mr_target_commit {
.commit { .commit {
......
class Admin::BuildsController < Admin::ApplicationController
def index
@scope = params[:scope]
@all_builds = Ci::Build
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
when 'all'
@builds
when 'finished'
@builds.finished
else
@builds.running_or_pending.reverse_order
end
@builds = @builds.page(params[:page]).per(30)
end
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
redirect_to admin_builds_path
end
end
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
end
end
def destroy
rp = Ci::RunnerProject.find(params[:id])
runner = rp.runner
rp.destroy
redirect_to admin_runner_path(runner)
end
private
def project
@project = Project.find_with_namespace(
[params[:namespace_id], '/', params[:project_id]].join('')
)
@project || render_404
end
end
class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: :index
def index
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.online.count
end
def show
@builds = @runner.builds.order('id DESC').first(30)
@projects =
if params[:search].present?
::Project.search(params[:search])
else
Project.all
end
@projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
def update
@runner.update_attributes(runner_params)
respond_to do |format|
format.js
format.html { redirect_to admin_runner_path(@runner) }
end
end
def destroy
@runner.destroy
redirect_to admin_runners_path
end
def resume
if @runner.update_attributes(active: true)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
end
end
private
def runner
@runner ||= Ci::Runner.find(params[:id])
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
module Ci
module Admin
class ApplicationController < Ci::ApplicationController
before_action :authenticate_user!
before_action :authenticate_admin!
layout "ci/admin"
end
end
end
module Ci
class Admin::ApplicationSettingsController < Ci::Admin::ApplicationController
before_action :set_application_setting
def show
end
def update
if @application_setting.update_attributes(application_setting_params)
redirect_to ci_admin_application_settings_path,
notice: 'Application settings saved successfully'
else
render :show
end
end
private
def set_application_setting
@application_setting = Ci::ApplicationSetting.current
@application_setting ||= Ci::ApplicationSetting.create_from_defaults
end
def application_setting_params
params.require(:application_setting).permit(
:all_broken_builds,
:add_pusher,
)
end
end
end
module Ci
class Admin::BuildsController < Ci::Admin::ApplicationController
def index
@scope = params[:scope]
@builds = Ci::Build.order('created_at DESC').page(params[:page]).per(30)
@builds =
case @scope
when "pending"
@builds.pending
when "running"
@builds.running
else
@builds
end
end
end
end
module Ci
class Admin::EventsController < Ci::Admin::ApplicationController
EVENTS_PER_PAGE = 50
def index
@events = Ci::Event.admin.order('created_at DESC').page(params[:page]).per(EVENTS_PER_PAGE)
end
end
end
module Ci
class Admin::ProjectsController < Ci::Admin::ApplicationController
def index
@projects = Ci::Project.ordered_by_last_commit_date.page(params[:page]).per(30)
end
def destroy
project.destroy
redirect_to ci_projects_url
end
protected
def project
@project ||= Ci::Project.find(params[:id])
end
end
end
module Ci
class Admin::RunnerProjectsController < Ci::Admin::ApplicationController
layout 'ci/project'
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(project, current_user)
redirect_to ci_admin_runner_path(@runner)
else
redirect_to ci_admin_runner_path(@runner), alert: 'Failed adding runner to project'
end
end
def destroy
rp = Ci::RunnerProject.find(params[:id])
runner = rp.runner
rp.destroy
redirect_to ci_admin_runner_path(runner)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
module Ci
class Admin::RunnersController < Ci::Admin::ApplicationController
before_action :runner, except: :index
def index
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.online.count
end
def show
@builds = @runner.builds.order('id DESC').first(30)
@projects = Ci::Project.all
if params[:search].present?
@gl_projects = ::Project.search(params[:search])
@projects = @projects.where(gitlab_id: @gl_projects.select(:id))
end
@projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
@projects = @projects.joins(:gl_project)
@projects = @projects.page(params[:page]).per(30)
end
def update
@runner.update_attributes(runner_params)
respond_to do |format|
format.js
format.html { redirect_to ci_admin_runner_path(@runner) }
end
end
def destroy
@runner.destroy
redirect_to ci_admin_runners_path
end
def resume
if @runner.update_attributes(active: true)
redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
end
end
def assign_all
Ci::Project.unassigned(@runner).all.each do |project|
@runner.assign_to(project, current_user)
end
redirect_to ci_admin_runner_path(@runner), notice: "Runner was assigned to all projects"
end
private
def runner
@runner ||= Ci::Runner.find(params[:id])
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
end
...@@ -4,24 +4,16 @@ module Ci ...@@ -4,24 +4,16 @@ module Ci
"app/helpers/ci" "app/helpers/ci"
end end
helper_method :gl_project
private private
def authenticate_token!
unless project.valid_token?(params[:token])
return head(403)
end
end
def authorize_access_project! def authorize_access_project!
unless can?(current_user, :read_project, gl_project) unless can?(current_user, :read_project, project)
return page_404 return page_404
end end
end end
def authorize_manage_builds! def authorize_manage_builds!
unless can?(current_user, :manage_builds, gl_project) unless can?(current_user, :manage_builds, project)
return page_404 return page_404
end end
end end
...@@ -31,7 +23,7 @@ module Ci ...@@ -31,7 +23,7 @@ module Ci
end end
def authorize_manage_project! def authorize_manage_project!
unless can?(current_user, :admin_project, gl_project) unless can?(current_user, :admin_project, project)
return page_404 return page_404
end end
end end
...@@ -58,9 +50,5 @@ module Ci ...@@ -58,9 +50,5 @@ module Ci
count: count count: count
} }
end end
def gl_project
::Project.find(@project.gitlab_id)
end
end end
end end
module Ci module Ci
class LintsController < Ci::ApplicationController class LintsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def show def show
......
...@@ -3,13 +3,12 @@ module Ci ...@@ -3,13 +3,12 @@ module Ci
before_action :project, except: [:index] before_action :project, except: [:index]
before_action :authenticate_user!, except: [:index, :build, :badge] before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:index, :badge] before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
protect_from_forgery protect_from_forgery
def show def show
# Temporary compatibility with CI badges pointing to CI project page # Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project) redirect_to namespace_project_path(project.namespace, project)
end end
# Project status badge # Project status badge
...@@ -20,16 +19,10 @@ module Ci ...@@ -20,16 +19,10 @@ module Ci
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end end
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
end
protected protected
def project def project
@project ||= Ci::Project.find(params[:id]) @project ||= Project.find_by(ci_id: params[:id].to_i)
end end
def no_cache def no_cache
......
module Ci
class RunnerProjectsController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_manage_project!
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(@project.gl_project)
if @runner.assign_to(project, current_user)
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
end
end
def destroy
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
redirect_to runners_path(@project.gl_project)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
...@@ -31,8 +31,4 @@ class Projects::ApplicationController < ApplicationController ...@@ -31,8 +31,4 @@ class Projects::ApplicationController < ApplicationController
def builds_enabled def builds_enabled
return render_404 unless @project.builds_enabled? return render_404 unless @project.builds_enabled?
end end
def ci_project
@ci_project ||= @project.ensure_gitlab_ci_project
end
end end
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_manage_builds!, except: [:index, :show, :status] before_action :authorize_manage_builds!, except: [:index, :show, :status]
...@@ -9,7 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -9,7 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController
def index def index
@scope = params[:scope] @scope = params[:scope]
@all_builds = project.ci_builds @all_builds = project.builds
@builds = @all_builds.order('created_at DESC') @builds = @all_builds.order('created_at DESC')
@builds = @builds =
case @scope case @scope
...@@ -24,13 +23,13 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -24,13 +23,13 @@ class Projects::BuildsController < Projects::ApplicationController
end end
def cancel_all def cancel_all
@project.ci_builds.running_or_pending.each(&:cancel) @project.builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project) redirect_to namespace_project_builds_path(project.namespace, project)
end end
def show def show
@builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') @builds = @project.ci_commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id) @builds = @builds.where("id not in (?)", @build.id)
@commit = @build.commit @commit = @build.commit
...@@ -77,7 +76,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -77,7 +76,7 @@ class Projects::BuildsController < Projects::ApplicationController
private private
def build def build
@build ||= ci_project.builds.unscoped.find_by!(id: params[:id]) @build ||= project.builds.unscoped.find_by!(id: params[:id])
end end
def artifacts_file def artifacts_file
...@@ -85,7 +84,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -85,7 +84,7 @@ class Projects::BuildsController < Projects::ApplicationController
end end
def build_path(build) def build_path(build)
namespace_project_build_path(build.gl_project.namespace, build.gl_project, build) namespace_project_build_path(build.project.namespace, build.project, build)
end end
def authorize_manage_builds! def authorize_manage_builds!
......
class Projects::CiServicesController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def index
@ci_project.build_missing_services
@services = @ci_project.services.reload
end
def edit
service
end
def update
if service.update_attributes(service_params)
redirect_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param)
else
render 'edit'
end
end
def test
last_build = @project.ci_builds.last
if service.execute(last_build)
message = { notice: 'We successfully tested the service' }
else
message = { alert: 'We tried to test the service but error occurred' }
end
redirect_back_or_default(options: message)
end
private
def service
@service ||= @ci_project.services.find { |service| service.to_param == params[:id] }
end
def service_params
params.require(:service).permit(
:type, :active, :webhook, :notify_only_broken_builds,
:email_recipients, :email_only_broken_builds, :email_add_pusher,
:hipchat_token, :hipchat_room, :hipchat_server
)
end
end
class Projects::CiSettingsController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def edit
end
def update
if ci_project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, ci_project)
redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
else
render action: "edit"
end
end
def destroy
ci_project.destroy
Ci::EventService.new.remove_project(current_user, ci_project)
project.gitlab_ci_service.update_attributes(active: false)
redirect_to project_path(project), notice: "CI was disabled for this project"
end
protected
def project_params
params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
:polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
:email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
{ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
class Projects::CiWebHooksController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def index
@web_hooks = @ci_project.web_hooks
@web_hook = Ci::WebHook.new
end
def create
@web_hook = @ci_project.web_hooks.new(web_hook_params)
@web_hook.save
if @web_hook.valid?
redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
else
@web_hooks = @ci_project.web_hooks.select(&:persisted?)
render :index
end
end
def test
Ci::TestHookService.new.execute(hook, current_user)
redirect_back_or_default(default: { action: 'index' })
end
def destroy
hook.destroy
redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
end
private
def hook
@web_hook ||= @ci_project.web_hooks.find(params[:id])
end
def web_hook_params
params.require(:web_hook).permit(:url)
end
end
...@@ -31,7 +31,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -31,7 +31,6 @@ class Projects::CommitController < Projects::ApplicationController
end end
def builds def builds
@ci_project = @project.gitlab_ci_project
end end
def cancel_builds def cancel_builds
......
...@@ -25,13 +25,11 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -25,13 +25,11 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def ci def ci
ci_project = @project.gitlab_ci_project
@charts = {} @charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(ci_project) @charts[:week] = Ci::Charts::WeekChart.new(project)
@charts[:month] = Ci::Charts::MonthChart.new(ci_project) @charts[:month] = Ci::Charts::MonthChart.new(project)
@charts[:year] = Ci::Charts::YearChart.new(ci_project) @charts[:year] = Ci::Charts::YearChart.new(project)
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project) @charts[:build_times] = Ci::Charts::BuildTime.new(project)
end end
def languages def languages
......
...@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params def hook_params
params.require(:hook).permit(:url, :push_events, :issues_events, params.require(:hook).permit(:url, :push_events, :issues_events,
:merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification) :merge_requests_events, :tag_push_events, :note_events,
:build_events, :enable_ssl_verification)
end end
end end
...@@ -58,10 +58,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -58,10 +58,10 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue @noteable = @issue
@merge_requests = @issue.referenced_merge_requests
respond_with(@issue) respond_with(@issue)
end end
......
...@@ -81,8 +81,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -81,8 +81,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def builds def builds
@ci_project = @merge_request.source_project.gitlab_ci_project
respond_to do |format| respond_to do |format|
format.html { render 'show' } format.html { render 'show' }
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } } format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
...@@ -106,7 +104,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -106,7 +104,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@first_commit = @merge_request.first_commit @first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare_diffs
@ci_project = @source_project.gitlab_ci_project
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit @statuses = @ci_commit.statuses if @ci_commit
...@@ -279,8 +276,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -279,8 +276,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def define_show_vars def define_show_vars
@participants = @merge_request.participants(current_user)
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
......
class Projects::RunnerProjectsController < Projects::ApplicationController
before_action :authorize_admin_project!
layout 'project_settings'
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project)
if @runner.assign_to(project, current_user)
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
end
end
def destroy
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
redirect_to runners_path(project)
end
end
class Projects::RunnersController < Projects::ApplicationController class Projects::RunnersController < Projects::ApplicationController
before_action :ci_project
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show] before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_admin_project! before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
def index def index
@runners = @ci_project.runners.ordered @runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners. @specific_runners = current_user.ci_authorized_runners.
where.not(id: @ci_project.runners). where.not(id: project.runners).
ordered.page(params[:page]).per(20) ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active @shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all) @shared_runners_count = @shared_runners.count(:all)
...@@ -26,7 +25,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -26,7 +25,7 @@ class Projects::RunnersController < Projects::ApplicationController
end end
def destroy def destroy
if @runner.only_for?(@ci_project) if @runner.only_for?(project)
@runner.destroy @runner.destroy
end end
...@@ -52,10 +51,16 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -52,10 +51,16 @@ class Projects::RunnersController < Projects::ApplicationController
def show def show
end end
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to namespace_project_runners_path(project.namespace, project)
end
protected protected
def set_runner def set_runner
@runner ||= @ci_project.runners.find(params[:id]) @runner ||= project.runners.find(params[:id])
end end
def runner_params def runner_params
......
...@@ -6,7 +6,9 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -6,7 +6,9 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels, :colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events, :push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :note_events, :build_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
......
class Projects::TriggersController < Projects::ApplicationController class Projects::TriggersController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project! before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
def index def index
@triggers = @ci_project.triggers @triggers = project.triggers
@trigger = Ci::Trigger.new @trigger = Ci::Trigger.new
end end
def create def create
@trigger = @ci_project.triggers.new @trigger = project.triggers.new
@trigger.save @trigger.save
if @trigger.valid? if @trigger.valid?
redirect_to namespace_project_triggers_path(@project.namespace, @project) redirect_to namespace_project_triggers_path(@project.namespace, @project)
else else
@triggers = @ci_project.triggers.select(&:persisted?) @triggers = project.triggers.select(&:persisted?)
render :index render :index
end end
end end
...@@ -30,6 +29,6 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -30,6 +29,6 @@ class Projects::TriggersController < Projects::ApplicationController
private private
def trigger def trigger
@trigger ||= @ci_project.triggers.find(params[:id]) @trigger ||= project.triggers.find(params[:id])
end end
end end
class Projects::VariablesController < Projects::ApplicationController class Projects::VariablesController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project! before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
...@@ -8,9 +7,7 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -8,9 +7,7 @@ class Projects::VariablesController < Projects::ApplicationController
end end
def update def update
if ci_project.update_attributes(project_params) if project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, ci_project)
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.' redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
else else
render action: 'show' render action: 'show'
......
...@@ -210,10 +210,10 @@ class ProjectsController < ApplicationController ...@@ -210,10 +210,10 @@ class ProjectsController < ApplicationController
def project_params def project_params
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
) )
end end
......
module Ci
module GitlabHelper
def no_turbolink
{ :"data-no-turbolink" => "data-no-turbolink" }
end
def yaml_web_editor_link(project)
commits = project.commits
if commits.any? && commits.last.ci_yaml_file
"#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
else
"#{project.gitlab_url}/new/master"
end
end
end
end
module Ci
module ProjectsHelper
def ref_tab_class ref = nil
'active' if ref == @ref
end
def success_ratio(success_builds, failed_builds)
failed_builds = failed_builds.count(:all)
success_builds = success_builds.count(:all)
return 100 if failed_builds.zero?
ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
ratio.to_i
end
def markdown_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
"[![build status](#{url})](#{ci_project_url(project, ref: ref)})"
end
def html_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
"<a href='#{ci_project_url(project, ref: ref)}'><img src='#{url}' /></a>"
end
def project_uses_specific_runner?(project)
project.runners.any?
end
def no_runners_for_project?(project)
project.runners.blank? &&
Ci::Runner.shared.blank?
end
end
end
module CiBadgeHelper
def markdown_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
link = namespace_project_commits_path(project.namespace, project, ref)
"[![build status](#{url})](#{link})"
end
def html_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
link = namespace_project_commits_path(project.namespace, project, ref)
"<a href='#{link}'><img src='#{url}' /></a>"
end
end
module CiStatusHelper module CiStatusHelper
def ci_status_path(ci_commit) def ci_status_path(ci_commit)
project = ci_commit.gl_project project = ci_commit.project
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end end
...@@ -52,7 +52,7 @@ module CiStatusHelper ...@@ -52,7 +52,7 @@ module CiStatusHelper
'circle' 'circle'
end end
icon(icon_name) icon(icon_name + ' fw')
end end
def render_ci_status(ci_commit) def render_ci_status(ci_commit)
...@@ -63,4 +63,9 @@ module CiStatusHelper ...@@ -63,4 +63,9 @@ module CiStatusHelper
ci_status_icon(ci_commit) ci_status_icon(ci_commit)
end end
end end
def no_runners_for_project?(project)
project.runners.blank? &&
Ci::Runner.shared.blank?
end
end end
...@@ -16,4 +16,14 @@ module GraphHelper ...@@ -16,4 +16,14 @@ module GraphHelper
ids = parents.map { |p| p.id } ids = parents.map { |p| p.id }
ids.zip(parent_spaces) ids.zip(parent_spaces)
end end
def success_ratio(success_builds, failed_builds)
failed_builds = failed_builds.count(:all)
success_builds = success_builds.count(:all)
return 100 if failed_builds.zero?
ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
ratio.to_i
end
end end
...@@ -19,7 +19,7 @@ module RunnersHelper ...@@ -19,7 +19,7 @@ module RunnersHelper
id = "\##{runner.id}" id = "\##{runner.id}"
if current_user && current_user.admin if current_user && current_user.admin
link_to ci_admin_runner_path(runner) do link_to admin_runner_path(runner) do
display_name + id display_name + id
end end
else else
......
module TriggersHelper module TriggersHelper
def ci_build_trigger_url(project_id, ref_name) def builds_trigger_url(project_id)
"#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger" "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
end end
end end
module Ci
module Emails
module Builds
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
end
end
end
module Ci
class Notify < ActionMailer::Base
include Ci::Emails::Builds
add_template_helper Ci::GitlabHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Gitlab.config.gitlab.email_from
# Just send email with 3 seconds delay
def self.delay
delay_for(2.seconds)
end
private
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
#
# Examples
#
# >> subject('Lorem ipsum')
# => "GitLab-CI | Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
# => "GitLab-CI | Ruby on Rails | Lorem ipsum "
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab-CI | Lorem ipsum | Dolor sit amet"
def subject(*extra)
subject = "GitLab-CI"
subject << (@project ? " | #{@project.name}" : "")
subject << " | " + extra.join(' | ') if extra.present?
subject
end
end
end
module Emails
module Builds
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
end
end
...@@ -59,85 +59,17 @@ module Emails ...@@ -59,85 +59,17 @@ module Emails
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
def repository_push_email(project_id, recipient, author_id: nil, def repository_push_email(project_id, recipient, opts = {})
ref: nil, @message =
action: nil, Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
compare: nil,
reverse_compare: false,
send_from_committer_email: false,
disable_diffs: false)
unless author_id && ref && action
raise ArgumentError, "missing keywords: author_id, ref, action"
end
@project = Project.find(project_id) # used in notify layout
@current_user = @author = User.find(author_id) @target_url = @message.target_url
@reverse_compare = reverse_compare
@compare = compare mail(from: sender(@message.author_id, @message.send_from_committer_email?),
@ref_name = Gitlab::Git.ref_name(ref) reply_to: @message.reply_to,
@ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch" to: @message.recipient,
@action = action subject: @message.subject)
@disable_diffs = disable_diffs
if @compare
@commits = Commit.decorate(compare.commits, @project)
@diffs = compare.diffs
end
@action_name =
case action
when :create
"pushed new"
when :delete
"deleted"
else
"pushed to"
end
@subject = "[Git]"
@subject << "[#{@project.path_with_namespace}]"
@subject << "[#{@ref_name}]" if action == :push
@subject << " "
if action == :push
if @commits.length > 1
@target_url = namespace_project_compare_url(@project.namespace,
@project,
from: Commit.new(@compare.base, @project),
to: Commit.new(@compare.head, @project))
@subject << "Deleted " if @reverse_compare
@subject << "#{@commits.length} commits: #{@commits.first.title}"
else
@target_url = namespace_project_commit_url(@project.namespace,
@project, @commits.first)
@subject << "Deleted 1 commit: " if @reverse_compare
@subject << @commits.first.title
end
else
unless action == :delete
@target_url = namespace_project_tree_url(@project.namespace,
@project, @ref_name)
end
subject_action = @action_name.dup
subject_action[0] = subject_action[0].capitalize
@subject << "#{subject_action} #{@ref_type} #{@ref_name}"
end
@disable_footer = true
reply_to =
if send_from_committer_email && can_send_from_user_email?(@author)
@author.email
else
Gitlab.config.gitlab.email_reply_to
end
mail(from: sender(author_id, send_from_committer_email),
reply_to: reply_to,
to: recipient,
subject: @subject)
end end
end end
end end
...@@ -7,6 +7,7 @@ class Notify < BaseMailer ...@@ -7,6 +7,7 @@ class Notify < BaseMailer
include Emails::Projects include Emails::Projects
include Emails::Profile include Emails::Profile
include Emails::Groups include Emails::Groups
include Emails::Builds
add_template_helper MergeRequestsHelper add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper add_template_helper EmailsHelper
...@@ -33,13 +34,13 @@ class Notify < BaseMailer ...@@ -33,13 +34,13 @@ class Notify < BaseMailer
allowed_domains allowed_domains
end end
private
def can_send_from_user_email?(sender) def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain) self.class.allowed_email_domains.include?(sender_domain)
end end
private
# Return an email address that displays the name of the sender. # Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same. # Only the displayed name changes; the actual email address is always the same.
def sender(sender_id, send_from_user_email = false) def sender(sender_id, send_from_user_email = false)
......
# == Schema Information
#
# Table name: ci_application_settings
#
# id :integer not null, primary key
# all_broken_builds :boolean
# add_pusher :boolean
# created_at :datetime
# updated_at :datetime
#
module Ci
class ApplicationSetting < ActiveRecord::Base
extend Ci::Model
CACHE_KEY = 'ci_application_setting.last'
after_commit do
Rails.cache.write(CACHE_KEY, self)
end
def self.expire
Rails.cache.delete(CACHE_KEY)
end
def self.current
Rails.cache.fetch(CACHE_KEY) do
Ci::ApplicationSetting.last
end
end
def self.create_from_defaults
create(
all_broken_builds: Settings.gitlab_ci['all_broken_builds'],
add_pusher: Settings.gitlab_ci['add_pusher'],
)
end
end
end
...@@ -84,6 +84,7 @@ module Ci ...@@ -84,6 +84,7 @@ module Ci
new_build.options = build.options new_build.options = build.options
new_build.commands = build.commands new_build.commands = build.commands
new_build.tag_list = build.tag_list new_build.tag_list = build.tag_list
new_build.gl_project_id = build.gl_project_id
new_build.commit_id = build.commit_id new_build.commit_id = build.commit_id
new_build.name = build.name new_build.name = build.name
new_build.allow_failure = build.allow_failure new_build.allow_failure = build.allow_failure
...@@ -96,21 +97,16 @@ module Ci ...@@ -96,21 +97,16 @@ module Ci
end end
state_machine :status, initial: :pending do state_machine :status, initial: :pending do
after_transition any => [:success, :failed, :canceled] do |build, transition| after_transition pending: :running do |build, transition|
return unless build.gl_project build.execute_hooks
end
project = build.project
if project.web_hooks? after_transition any => [:success, :failed, :canceled] do |build, transition|
Ci::WebHookService.new.build_end(build) return unless build.project
end
build.update_coverage
build.commit.create_next_builds(build) build.commit.create_next_builds(build)
project.execute_services(build) build.execute_hooks
if project.coverage_enabled?
build.update_coverage
end
end end
end end
...@@ -119,7 +115,7 @@ module Ci ...@@ -119,7 +115,7 @@ module Ci
end end
def retryable? def retryable?
commands.present? project.builds_enabled? && commands.present?
end end
def retried? def retried?
...@@ -132,7 +128,7 @@ module Ci ...@@ -132,7 +128,7 @@ module Ci
end end
def timeout def timeout
project.timeout project.build_timeout
end end
def variables def variables
...@@ -151,26 +147,21 @@ module Ci ...@@ -151,26 +147,21 @@ module Ci
project.name project.name
end end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && user.present? && user.notification_email.present?
recipients << user.notification_email
end
recipients.uniq
end
def repo_url def repo_url
project.repo_url_with_auth auth = "gitlab-ci-token:#{token}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth
end
end end
def allow_git_fetch def allow_git_fetch
project.allow_git_fetch project.build_allow_git_fetch
end end
def update_coverage def update_coverage
coverage = extract_coverage(trace, project.coverage_regex) coverage_regex = project.build_coverage_regex
return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex)
if coverage.is_a? Numeric if coverage.is_a? Numeric
update_attributes(coverage: coverage) update_attributes(coverage: coverage)
...@@ -203,7 +194,7 @@ module Ci ...@@ -203,7 +194,7 @@ module Ci
def trace def trace
trace = raw_trace trace = raw_trace
if project && trace.present? if project && trace.present?
trace.gsub(project.token, 'xxxxxx') trace.gsub(project.runners_token, 'xxxxxx')
else else
trace trace
end end
...@@ -230,29 +221,29 @@ module Ci ...@@ -230,29 +221,29 @@ module Ci
end end
def token def token
project.token project.runners_token
end end
def valid_token? token def valid_token? token
project.valid_token? token project.valid_runners_token? token
end end
def target_url def target_url
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
namespace_project_build_url(gl_project.namespace, gl_project, self) namespace_project_build_url(project.namespace, project, self)
end end
def cancel_url def cancel_url
if active? if active?
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self) cancel_namespace_project_build_path(project.namespace, project, self)
end end
end end
def retry_url def retry_url
if retryable? if retryable?
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
retry_namespace_project_build_path(gl_project.namespace, gl_project, self) retry_namespace_project_build_path(project.namespace, project, self)
end end
end end
...@@ -271,10 +262,18 @@ module Ci ...@@ -271,10 +262,18 @@ module Ci
def download_url def download_url
if artifacts_file.exists? if artifacts_file.exists?
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
download_namespace_project_build_path(gl_project.namespace, gl_project, self) download_namespace_project_build_path(project.namespace, project, self)
end end
end end
def execute_hooks
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
end
private private
def yaml_variables def yaml_variables
......
...@@ -20,8 +20,8 @@ module Ci ...@@ -20,8 +20,8 @@ module Ci
class Commit < ActiveRecord::Base class Commit < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, dependent: :destroy, class_name: 'CommitStatus' has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
...@@ -38,10 +38,6 @@ module Ci ...@@ -38,10 +38,6 @@ module Ci
sha sha
end end
def project
@project ||= gl_project.ensure_gitlab_ci_project
end
def project_id def project_id
project.id project.id
end end
...@@ -57,7 +53,7 @@ module Ci ...@@ -57,7 +53,7 @@ module Ci
end end
def valid_commit_sha def valid_commit_sha
if self.sha == Ci::Git::BLANK_SHA if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)") self.errors.add(:sha, " cant be 00000000 (branch removal)")
end end
end end
...@@ -79,7 +75,7 @@ module Ci ...@@ -79,7 +75,7 @@ module Ci
end end
def commit_data def commit_data
@commit ||= gl_project.commit(sha) @commit ||= project.commit(sha)
rescue rescue
nil nil
end end
...@@ -178,16 +174,18 @@ module Ci ...@@ -178,16 +174,18 @@ module Ci
duration_array.reduce(:+).to_i duration_array.reduce(:+).to_i
end end
def started_at
@started_at ||= statuses.order('started_at ASC').first.try(:started_at)
end
def finished_at def finished_at
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end end
def coverage def coverage
if project.coverage_enabled? coverage_array = latest_builds.map(&:coverage).compact
coverage_array = latest_builds.map(&:coverage).compact if coverage_array.size >= 1
if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
end end
end end
...@@ -197,7 +195,7 @@ module Ci ...@@ -197,7 +195,7 @@ module Ci
def config_processor def config_processor
return nil unless ci_yaml_file return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace) @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message) save_yaml_error(e.message)
nil nil
...@@ -207,7 +205,7 @@ module Ci ...@@ -207,7 +205,7 @@ module Ci
end end
def ci_yaml_file def ci_yaml_file
@ci_yaml_file ||= gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data @ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
rescue rescue
nil nil
end end
......
# == Schema Information
#
# Table name: ci_events
#
# id :integer not null, primary key
# project_id :integer
# user_id :integer
# is_admin :integer
# description :text
# created_at :datetime
# updated_at :datetime
#
module Ci
class Event < ActiveRecord::Base
extend Ci::Model
belongs_to :project, class_name: 'Ci::Project'
validates :description,
presence: true,
length: { in: 5..200 }
scope :admin, ->(){ where(is_admin: true) }
scope :project_wide, ->(){ where(is_admin: false) }
end
end
# == Schema Information
#
# Table name: ci_projects
#
# id :integer not null, primary key
# name :string(255)
# timeout :integer default(3600), not null
# created_at :datetime
# updated_at :datetime
# token :string(255)
# default_ref :string(255)
# path :string(255)
# always_build :boolean default(FALSE), not null
# polling_interval :integer
# public :boolean default(FALSE), not null
# ssh_url_to_repo :string(255)
# gitlab_id :integer
# allow_git_fetch :boolean default(TRUE), not null
# email_recipients :string(255) default(""), not null
# email_add_pusher :boolean default(TRUE), not null
# email_only_broken_builds :boolean default(TRUE), not null
# skip_refs :string(255)
# coverage_regex :string(255)
# shared_runners_enabled :boolean default(FALSE)
# generated_yaml_config :text
#
module Ci
class Project < ActiveRecord::Base
extend Ci::Model
include Ci::ProjectStatus
belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
has_many :events, dependent: :destroy, class_name: 'Ci::Event'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
# Project services
has_many :services, dependent: :destroy, class_name: 'Ci::Service'
has_one :hip_chat_service, dependent: :destroy, class_name: 'Ci::HipChatService'
has_one :slack_service, dependent: :destroy, class_name: 'Ci::SlackService'
has_one :mail_service, dependent: :destroy, class_name: 'Ci::MailService'
accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
#
# Validations
#
validates_presence_of :timeout, :token, :default_ref, :gitlab_id
validates_uniqueness_of :gitlab_id
validates :polling_interval,
presence: true,
if: ->(project) { project.always_build.present? }
before_validation :set_default_values
class << self
include Ci::CurrentSettings
def unassigned(runner)
joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
"AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
where("#{Ci::RunnerProject.table_name}.project_id" => nil)
end
def ordered_by_last_commit_date
last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
joins(:gl_project).
order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
end
end
def name
name_with_namespace
end
def path
path_with_namespace
end
def gitlab_url
web_url
end
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
self.default_ref ||= 'master'
end
def tracked_refs
@tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
end
def valid_token? token
self.token && self.token == token
end
def no_running_builds?
# Get running builds not later than 3 days ago to ignore hangs
builds.running.where("updated_at > ?", 3.days.ago).empty?
end
def email_notification?
email_add_pusher || email_recipients.present?
end
def web_hooks?
web_hooks.any?
end
def services?
services.any?
end
def timeout_in_minutes
timeout / 60
end
def timeout_in_minutes=(value)
self.timeout = value.to_i * 60
end
def coverage_enabled?
coverage_regex.present?
end
# Build a clone-able repo url
# using http and basic auth
def repo_url_with_auth
auth = "gitlab-ci-token:#{token}@"
http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth
end
end
def available_services_names
%w(slack mail hip_chat)
end
def build_missing_services
available_services_names.each do |service_name|
service = services.find { |service| service.to_param == service_name }
# If service is available but missing in db
# we should create an instance. Ex `create_gitlab_ci_service`
self.send :"create_#{service_name}_service" if service.nil?
end
end
def execute_services(data)
services.each do |service|
# Call service hook only if it is active
begin
service.execute(data) if service.active && service.can_execute?(data)
rescue => e
logger.error(e)
end
end
end
def setup_finished?
commits.any?
end
def commits
gl_project.ci_commits.ordered
end
def builds
gl_project.ci_builds
end
end
end
module Ci
module ProjectStatus
def status
last_commit.status if last_commit
end
def broken?
last_commit.failed? if last_commit
end
def success?
last_commit.success? if last_commit
end
def broken_or_success?
broken? || success?
end
def last_commit
@last_commit ||= commits.last if commits.any?
end
def last_commit_date
last_commit.try(:created_at)
end
def human_status
status
end
end
end
...@@ -25,7 +25,7 @@ module Ci ...@@ -25,7 +25,7 @@ module Ci
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :projects, through: :runner_projects, class_name: 'Ci::Project' has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
...@@ -45,10 +45,6 @@ module Ci ...@@ -45,10 +45,6 @@ module Ci
query: "%#{query.try(:downcase)}%") query: "%#{query.try(:downcase)}%")
end end
def gl_projects_ids
projects.select(:gitlab_id)
end
def set_default_values def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank? self.token = SecureRandom.hex(15) if self.token.blank?
end end
......
...@@ -14,8 +14,8 @@ module Ci ...@@ -14,8 +14,8 @@ module Ci
extend Ci::Model extend Ci::Model
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :project, class_name: 'Ci::Project' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
validates_uniqueness_of :runner_id, scope: :project_id validates_uniqueness_of :runner_id, scope: :gl_project_id
end end
end end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
# To add new service you should build a class inherited from Service
# and implement a set of methods
module Ci
class Service < ActiveRecord::Base
extend Ci::Model
serialize :properties, JSON
default_value_for :active, false
after_initialize :initialize_properties
belongs_to :project, class_name: 'Ci::Project'
validates :project_id, presence: true
def activated?
active
end
def category
:common
end
def initialize_properties
self.properties = {} if properties.nil?
end
def title
# implement inside child
end
def description
# implement inside child
end
def help
# implement inside child
end
def to_param
# implement inside child
end
def fields
# implement inside child
[]
end
def can_test?
project.builds.any?
end
def can_execute?(build)
true
end
def execute(build)
# implement inside child
end
# Provide convenient accessor methods
# for each serialized property.
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}
(properties || {})['#{arg}']
end
def #{arg}=(value)
self.properties ||= {}
self.properties['#{arg}'] = value
end
}
end
end
def self.boolean_accessor(*args)
self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}?
ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
end
}
end
end
end
end
...@@ -16,7 +16,7 @@ module Ci ...@@ -16,7 +16,7 @@ module Ci
acts_as_paranoid acts_as_paranoid
belongs_to :project, class_name: 'Ci::Project' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :token validates_presence_of :token
......
...@@ -15,10 +15,10 @@ module Ci ...@@ -15,10 +15,10 @@ module Ci
class Variable < ActiveRecord::Base class Variable < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :project, class_name: 'Ci::Project' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
validates_presence_of :key validates_presence_of :key
validates_uniqueness_of :key, scope: :project_id validates_uniqueness_of :key, scope: :gl_project_id
attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
end end
......
# == Schema Information
#
# Table name: ci_web_hooks
#
# id :integer not null, primary key
# url :string(255) not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
module Ci
class WebHook < ActiveRecord::Base
extend Ci::Model
include HTTParty
belongs_to :project, class_name: 'Ci::Project'
# HTTParty timeout
default_timeout 10
validates :url, presence: true, url: true
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
Ci::WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
}
Ci::WebHook.post(post_url,
body: data.to_json,
headers: { "Content-Type" => "application/json" },
verify: false,
basic_auth: auth)
end
end
end
end
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
class CommitStatus < ActiveRecord::Base class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :commit, class_name: 'Ci::Commit' belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :user belongs_to :user
...@@ -76,7 +77,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -76,7 +77,7 @@ class CommitStatus < ActiveRecord::Base
end end
after_transition [:pending, :running] => :success do |build, transition| after_transition [:pending, :running] => :success do |build, transition|
MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.gl_project, nil).trigger(build) MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
end end
state :pending, value: 'pending' state :pending, value: 'pending'
...@@ -86,8 +87,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -86,8 +87,7 @@ class CommitStatus < ActiveRecord::Base
state :canceled, value: 'canceled' state :canceled, value: 'canceled'
end end
delegate :sha, :short_sha, :gl_project, delegate :sha, :short_sha, to: :commit, prefix: false
to: :commit, prefix: false
# TODO: this should be removed with all references # TODO: this should be removed with all references
def before_sha def before_sha
......
...@@ -25,4 +25,5 @@ class ProjectHook < WebHook ...@@ -25,4 +25,5 @@ class ProjectHook < WebHook
scope :issue_hooks, -> { where(issues_events: true) } scope :issue_hooks, -> { where(issues_events: true) }
scope :note_hooks, -> { where(note_events: true) } scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) }
end end
...@@ -26,6 +26,7 @@ class WebHook < ActiveRecord::Base ...@@ -26,6 +26,7 @@ class WebHook < ActiveRecord::Base
default_value_for :note_events, false default_value_for :note_events, false
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false default_value_for :tag_push_events, false
default_value_for :build_events, false
default_value_for :enable_ssl_verification, true default_value_for :enable_ssl_verification, true
# HTTParty timeout # HTTParty timeout
......
...@@ -83,6 +83,14 @@ class Issue < ActiveRecord::Base ...@@ -83,6 +83,14 @@ class Issue < ActiveRecord::Base
reference reference
end end
def referenced_merge_requests
references = [self, *notes].flat_map do |note|
note.all_references(load_lazy_references: false).merge_requests
end.uniq
Gitlab::Markdown::ReferenceFilter::LazyReference.load(references).uniq.sort_by(&:iid)
end
# Reset issue events cache # Reset issue events cache
# #
# Since we do cache @event we need to reset cache in special cases: # Since we do cache @event we need to reset cache in special cases:
......
...@@ -377,6 +377,7 @@ class Note < ActiveRecord::Base ...@@ -377,6 +377,7 @@ class Note < ActiveRecord::Base
end end
def award_emoji_name def award_emoji_name
note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1] original_name = note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
AwardEmoji.normilize_emoji_name(original_name)
end end
end end
...@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base ...@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base
default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :wall_enabled, false default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
after_create :set_last_activity_at after_create :set_last_activity_at
...@@ -77,10 +78,10 @@ class Project < ActiveRecord::Base ...@@ -77,10 +78,10 @@ class Project < ActiveRecord::Base
# Project services # Project services
has_many :services has_many :services
has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy has_one :campfire_service, dependent: :destroy
has_one :drone_ci_service, dependent: :destroy has_one :drone_ci_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy
has_one :builds_email_service, dependent: :destroy
has_one :irker_service, dependent: :destroy has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy
...@@ -121,14 +122,21 @@ class Project < ActiveRecord::Base ...@@ -121,14 +122,21 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user has_many :starrers, through: :users_star_projects, source: :user
has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :releases, dependent: :destroy has_many :releases, dependent: :destroy
has_many :lfs_objects_projects, dependent: :destroy has_many :lfs_objects_projects, dependent: :destroy
has_many :lfs_objects, through: :lfs_objects_projects has_many :lfs_objects, through: :lfs_objects_projects
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
...@@ -161,6 +169,11 @@ class Project < ActiveRecord::Base ...@@ -161,6 +169,11 @@ class Project < ActiveRecord::Base
if: ->(project) { project.avatar.present? && project.avatar_changed? } if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :set_runners_token_token
def set_runners_token_token
self.runners_token = SecureRandom.hex(15) if self.runners_token.blank?
end
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
# Scopes # Scopes
...@@ -256,6 +269,10 @@ class Project < ActiveRecord::Base ...@@ -256,6 +269,10 @@ class Project < ActiveRecord::Base
projects.iwhere('projects.path' => project_path).take projects.iwhere('projects.path' => project_path).take
end end
def find_by_ci_id(id)
find_by(ci_id: id.to_i)
end
def visibility_levels def visibility_levels
Gitlab::VisibilityLevel.options Gitlab::VisibilityLevel.options
end end
...@@ -790,28 +807,6 @@ class Project < ActiveRecord::Base ...@@ -790,28 +807,6 @@ class Project < ActiveRecord::Base
ci_commit(sha) || ci_commits.create(sha: sha) ci_commit(sha) || ci_commits.create(sha: sha)
end end
def ensure_gitlab_ci_project
gitlab_ci_project || create_gitlab_ci_project(
shared_runners_enabled: current_application_settings.shared_runners_enabled
)
end
# TODO: this should be migrated to Project table,
# the same as issues_enabled
def builds_enabled
gitlab_ci_service && gitlab_ci_service.active
end
def builds_enabled?
builds_enabled
end
def builds_enabled=(value)
service = gitlab_ci_service || create_gitlab_ci_service
service.active = value
service.save
end
def enable_ci def enable_ci
self.builds_enabled = true self.builds_enabled = true
end end
...@@ -825,4 +820,34 @@ class Project < ActiveRecord::Base ...@@ -825,4 +820,34 @@ class Project < ActiveRecord::Base
forked_project_link.destroy forked_project_link.destroy
end end
end end
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
end
def valid_runners_token? token
self.runners_token && self.runners_token == token
end
# TODO (ayufan): For now we use runners_token (backward compatibility)
# In 8.4 every build will have its own individual token valid for time of build
def valid_build_token? token
self.builds_enabled? && self.runners_token && self.runners_token == token
end
def build_coverage_enabled?
build_coverage_regex.present?
end
def build_timeout_in_minutes
build_timeout / 60
end
def build_timeout_in_minutes=(value)
self.build_timeout = value.to_i * 60
end
end end
...@@ -18,40 +18,73 @@ ...@@ -18,40 +18,73 @@
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# #
require 'spec_helper' class BuildsEmailService < Service
prop_accessor :recipients
describe GitlabCiService, models: true do boolean_accessor :add_pusher
describe 'associations' do boolean_accessor :notify_only_broken_builds
it { is_expected.to belong_to(:project) } validates :recipients, presence: true, if: :activated?
it { is_expected.to have_one(:service_hook) }
end def initialize_properties
if properties.nil?
describe 'commits methods' do self.properties = {}
before do self.notify_only_broken_builds = true
@ci_project = create(:ci_project)
@service = GitlabCiService.new
allow(@service).to receive_messages(
service_hook: true,
project_url: 'http://ci.gitlab.org/projects/2',
token: 'verySecret',
project: @ci_project.gl_project
)
end end
end
def title
'Builds emails'
end
describe :build_page do def description
it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/builds")} 'Email the builds status to a list of recipients.'
end
def to_param
'builds_email'
end
def supported_events
%w(build)
end
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
if should_build_be_notified?(push_data)
BuildEmailWorker.perform_async(
push_data[:build_id],
all_recipients(push_data),
push_data,
)
end end
end
describe "execute" do def fields
let(:user) { create(:user, username: 'username') } [
let(:project) { create(:project, name: 'project') } { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by comma' },
let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } { type: 'checkbox', name: 'add_pusher', label: 'Add pusher to recipients list' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
]
end
it "calls CreateCommitService" do def should_build_be_notified?(data)
expect_any_instance_of(Ci::CreateCommitService).to receive(:execute).with(@ci_project, user, push_sample_data) case data[:build_status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
@service.execute(push_sample_data) def all_recipients(data)
end all_recipients = recipients.split(',')
if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}"
end end
all_recipients
end end
end end
module Ci
class HipChatMessage
include Gitlab::Application.routes.url_helpers
attr_reader :build
def initialize(build)
@build = build
end
def to_s
lines = Array.new
lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
lines.push("<a href=\"#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}\">Commit ##{commit.id}</a></br>")
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
lines.join('')
end
def status_color(build_or_commit=nil)
build_or_commit ||= commit_status
case build_or_commit
when :success
'green'
when :failed, :canceled
'red'
else # :pending, :running or unknown
'yellow'
end
end
def notify?
[:failed, :canceled].include?(commit_status)
end
private
def commit
build.commit
end
def project
commit.project
end
def build_status
build.status.to_sym
end
def commit_status
commit.status.to_sym
end
def humanized_status(build_or_commit=nil)
build_or_commit ||= commit_status
case build_or_commit
when :pending
"Pending"
when :running
"Running"
when :failed
"Failed"
when :success
"Successful"
when :canceled
"Canceled"
else
"Unknown"
end
end
end
end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
module Ci
class HipChatService < Ci::Service
prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
boolean_accessor :notify_only_broken_builds
validates :hipchat_token, presence: true, if: :activated?
validates :hipchat_room, presence: true, if: :activated?
default_value_for :notify_only_broken_builds, true
def title
"HipChat"
end
def description
"Private group chat, video chat, instant messaging for teams"
end
def help
end
def to_param
'hip_chat'
end
def fields
[
{ type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' },
{ type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' },
{ type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
{ type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
]
end
def can_execute?(build)
return if build.allow_failure?
commit = build.commit
return unless commit
return unless commit.latest_builds.include? build
case commit.status.to_sym
when :failed
true
when :success
true unless notify_only_broken_builds?
else
false
end
end
def execute(build)
msg = Ci::HipChatMessage.new(build)
opts = default_options.merge(
token: hipchat_token,
room: hipchat_room,
server: server_url,
color: msg.status_color,
notify: msg.notify?
)
Ci::HipChatNotifierWorker.perform_async(msg.to_s, opts)
end
private
def default_options
{
service_name: 'GitLab CI',
message_format: 'html'
}
end
def server_url
if hipchat_server.blank?
'https://api.hipchat.com'
else
hipchat_server
end
end
end
end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
module Ci
class MailService < Ci::Service
delegate :email_recipients, :email_recipients=,
:email_add_pusher, :email_add_pusher=,
:email_only_broken_builds, :email_only_broken_builds=, to: :project, prefix: false
before_save :update_project
default_value_for :active, true
def title
'Mail'
end
def description
'Email notification'
end
def to_param
'mail'
end
def fields
[
{ type: 'text', name: 'email_recipients', label: 'Recipients', help: 'Whitespace-separated list of recipient addresses' },
{ type: 'checkbox', name: 'email_add_pusher', label: 'Add pusher to recipients list' },
{ type: 'checkbox', name: 'email_only_broken_builds', label: 'Notify only broken builds' }
]
end
def can_execute?(build)
return if build.allow_failure?
# it doesn't make sense to send emails for retried builds
commit = build.commit
return unless commit
return unless commit.latest_builds.include?(build)
case build.status.to_sym
when :failed
true
when :success
true unless email_only_broken_builds
else
false
end
end
def execute(build)
build.project_recipients.each do |recipient|
case build.status.to_sym
when :success
mailer.build_success_email(build.id, recipient).deliver_later
when :failed
mailer.build_fail_email(build.id, recipient).deliver_later
end
end
end
private
def update_project
project.save!
end
def mailer
Ci::Notify
end
end
end
require 'slack-notifier'
module Ci
class SlackMessage
include Gitlab::Application.routes.url_helpers
def initialize(commit)
@commit = commit
end
def pretext
''
end
def color
attachment_color
end
def fallback
format(attachment_message)
end
def attachments
fields = []
commit.latest_builds.each do |build|
next if build.allow_failure?
next unless build.failed?
fields << {
title: build.name,
value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
}
end
[{
text: attachment_message,
color: attachment_color,
fields: fields
}]
end
private
attr_reader :commit
def attachment_message
out = "<#{ci_project_url(project)}|#{project_name}>: "
out << "Commit <#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> "
out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
out << "of <#{commit_ref_link}|#{commit.ref}> "
out << "by #{commit.git_author_name} " if commit.git_author_name
out << "#{commit_status} in "
out << "#{commit.duration} second(s)"
end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end
def project
commit.project
end
def project_name
project.name
end
def commit_sha_link
"#{project.gitlab_url}/commit/#{commit.sha}"
end
def commit_ref_link
"#{project.gitlab_url}/commits/#{commit.ref}"
end
def attachment_color
if commit.success?
'good'
else
'danger'
end
end
def commit_status
if commit.success?
'succeeded'
else
'failed'
end
end
end
end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
module Ci
class SlackService < Ci::Service
prop_accessor :webhook
boolean_accessor :notify_only_broken_builds
validates :webhook, presence: true, if: :activated?
default_value_for :notify_only_broken_builds, true
def title
'Slack'
end
def description
'A team communication tool for the 21st century'
end
def to_param
'slack'
end
def help
'Visit https://www.slack.com/services/new/incoming-webhook. Then copy link and save project!' unless webhook.present?
end
def fields
[
{ type: 'text', name: 'webhook', label: 'Webhook URL', placeholder: '' },
{ type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
]
end
def can_execute?(build)
return if build.allow_failure?
commit = build.commit
return unless commit
return unless commit.latest_builds.include?(build)
case commit.status.to_sym
when :failed
true
when :success
true unless notify_only_broken_builds?
else
false
end
end
def execute(build)
message = Ci::SlackMessage.new(build.commit)
options = default_options.merge(
color: message.color,
fallback: message.fallback,
attachments: message.attachments
)
Ci::SlackNotifierWorker.perform_async(webhook, message.pretext, options)
end
private
def default_options
{
username: 'GitLab CI'
}
end
end
end
...@@ -19,76 +19,5 @@ ...@@ -19,76 +19,5 @@
# #
class GitlabCiService < CiService class GitlabCiService < CiService
include Gitlab::Application.routes.url_helpers # this is no longer used
after_save :compose_service_hook, if: :activated?
after_save :ensure_gitlab_ci_project, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def ensure_gitlab_ci_project
return unless project
project.ensure_gitlab_ci_project
end
def supported_events
%w(push tag_push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
ci_project = project.gitlab_ci_project
if ci_project
current_user = User.find_by(id: data[:user_id])
Ci::CreateCommitService.new.execute(ci_project, current_user, data)
end
end
def token
if project.gitlab_ci_project.present?
project.gitlab_ci_project.token
end
end
def get_ci_commit(sha, ref)
Ci::Project.find(project.gitlab_ci_project.id).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
get_ci_commit(sha, ref).status
rescue ActiveRecord::RecordNotFound
:error
end
def commit_coverage(sha, ref)
get_ci_commit(sha, ref).coverage
rescue ActiveRecord::RecordNotFound
:error
end
def build_page(sha, ref)
if project.gitlab_ci_project.present?
builds_namespace_project_commit_url(project.namespace, project, sha)
end
end
def title
'GitLab CI'
end
def description
'Continuous integration server from GitLab'
end
def to_param
'gitlab_ci'
end
def fields
[]
end
end end
...@@ -22,8 +22,16 @@ class HipchatService < Service ...@@ -22,8 +22,16 @@ class HipchatService < Service
MAX_COMMITS = 3 MAX_COMMITS = 3
prop_accessor :token, :room, :server, :notify, :color, :api_version prop_accessor :token, :room, :server, :notify, :color, :api_version
boolean_accessor :notify_only_broken_builds
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
end
end
def title def title
'HipChat' 'HipChat'
end end
...@@ -45,12 +53,13 @@ class HipchatService < Service ...@@ -45,12 +53,13 @@ class HipchatService < Service
{ type: 'text', name: 'api_version', { type: 'text', name: 'api_version',
placeholder: 'Leave blank for default (v2)' }, placeholder: 'Leave blank for default (v2)' },
{ type: 'text', name: 'server', { type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://hipchat.example.com' } placeholder: 'Leave blank for default. https://hipchat.example.com' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
] ]
end end
def supported_events def supported_events
%w(push issue merge_request note tag_push) %w(push issue merge_request note tag_push build)
end end
def execute(data) def execute(data)
...@@ -94,6 +103,8 @@ class HipchatService < Service ...@@ -94,6 +103,8 @@ class HipchatService < Service
create_merge_request_message(data) unless is_update?(data) create_merge_request_message(data) unless is_update?(data)
when "note" when "note"
create_note_message(data) create_note_message(data)
when "build"
create_build_message(data) if should_build_be_notified?(data)
end end
end end
...@@ -235,6 +246,20 @@ class HipchatService < Service ...@@ -235,6 +246,20 @@ class HipchatService < Service
message message
end end
def create_build_message(data)
ref_type = data[:tag] ? 'tag' : 'branch'
ref = data[:ref]
sha = data[:sha]
user_name = data[:commit][:author_name]
status = data[:commit][:status]
duration = data[:commit][:duration]
branch_link = "<a href=\"#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"
commit_link = "<a href=\"#{project_url}/commit/#{URI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
def project_name def project_name
project.name_with_namespace.gsub(/\s/, '') project.name_with_namespace.gsub(/\s/, '')
end end
...@@ -250,4 +275,24 @@ class HipchatService < Service ...@@ -250,4 +275,24 @@ class HipchatService < Service
def is_update?(data) def is_update?(data)
data[:object_attributes][:action] == 'update' data[:object_attributes][:action] == 'update'
end end
def humanized_status(status)
case status
when 'success'
'passed'
else
status
end
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
end end
...@@ -20,8 +20,16 @@ ...@@ -20,8 +20,16 @@
class SlackService < Service class SlackService < Service
prop_accessor :webhook, :username, :channel prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
validates :webhook, presence: true, if: :activated? validates :webhook, presence: true, if: :activated?
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
end
end
def title def title
'Slack' 'Slack'
end end
...@@ -45,12 +53,13 @@ class SlackService < Service ...@@ -45,12 +53,13 @@ class SlackService < Service
{ type: 'text', name: 'webhook', { type: 'text', name: 'webhook',
placeholder: 'https://hooks.slack.com/services/...' }, placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'channel', placeholder: '#channel' } { type: 'text', name: 'channel', placeholder: '#channel' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
] ]
end end
def supported_events def supported_events
%w(push issue merge_request note tag_push) %w(push issue merge_request note tag_push build)
end end
def execute(data) def execute(data)
...@@ -78,6 +87,8 @@ class SlackService < Service ...@@ -78,6 +87,8 @@ class SlackService < Service
MergeMessage.new(data) unless is_update?(data) MergeMessage.new(data) unless is_update?(data)
when "note" when "note"
NoteMessage.new(data) NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
end end
opt = {} opt = {}
...@@ -86,7 +97,7 @@ class SlackService < Service ...@@ -86,7 +97,7 @@ class SlackService < Service
if message if message
notifier = Slack::Notifier.new(webhook, opt) notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments) notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
end end
end end
...@@ -103,9 +114,21 @@ class SlackService < Service ...@@ -103,9 +114,21 @@ class SlackService < Service
def is_update?(data) def is_update?(data)
data[:object_attributes][:action] == 'update' data[:object_attributes][:action] == 'update'
end end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
end end
require "slack_service/issue_message" require "slack_service/issue_message"
require "slack_service/push_message" require "slack_service/push_message"
require "slack_service/merge_message" require "slack_service/merge_message"
require "slack_service/note_message" require "slack_service/note_message"
require "slack_service/build_message"
...@@ -10,6 +10,9 @@ class SlackService ...@@ -10,6 +10,9 @@ class SlackService
format(message) format(message)
end end
def fallback
end
def attachments def attachments
raise NotImplementedError raise NotImplementedError
end end
......
class SlackService
class BuildMessage < BaseMessage
attr_reader :sha
attr_reader :ref_type
attr_reader :ref
attr_reader :status
attr_reader :project_name
attr_reader :project_url
attr_reader :user_name
attr_reader :duration
def initialize(params, commit = true)
@sha = params[:sha]
@ref_type = params[:tag] ? 'tag' : 'branch'
@ref = params[:ref]
@project_name = params[:project_name]
@project_url = params[:project_url]
@status = params[:commit][:status]
@user_name = params[:commit][:author_name]
@duration = params[:commit][:duration]
end
def pretext
''
end
def fallback
format(message)
end
def attachments
[{ text: format(message), color: attachment_color }]
end
private
def message
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} second(s)"
end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end
def humanized_status
case status
when 'success'
'passed'
else
status
end
end
def attachment_color
if status == 'success'
'good'
else
'danger'
end
end
def branch_url
"#{project_url}/commits/#{ref}"
end
def branch_link
"[#{ref}](#{branch_url})"
end
def project_link
"[#{project_name}](#{project_url})"
end
def commit_url
"#{project_url}/commit/#{sha}/builds"
end
def commit_link
"[#{Commit.truncate_sha(sha)}](#{commit_url})"
end
end
end
...@@ -30,6 +30,7 @@ class Service < ActiveRecord::Base ...@@ -30,6 +30,7 @@ class Service < ActiveRecord::Base
default_value_for :merge_requests_events, true default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true default_value_for :tag_push_events, true
default_value_for :note_events, true default_value_for :note_events, true
default_value_for :build_events, true
after_initialize :initialize_properties after_initialize :initialize_properties
...@@ -40,13 +41,14 @@ class Service < ActiveRecord::Base ...@@ -40,13 +41,14 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? } validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :push_hooks, -> { where(push_events: true, active: true) } scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
scope :issue_hooks, -> { where(issues_events: true, active: true) } scope :issue_hooks, -> { where(issues_events: true, active: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
def activated? def activated?
active active
...@@ -133,6 +135,21 @@ class Service < ActiveRecord::Base ...@@ -133,6 +135,21 @@ class Service < ActiveRecord::Base
end end
end end
# Provide convenient boolean accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.boolean_accessor(*args)
self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}?
ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
end
}
end
end
# Returns a hash of the properties that have been assigned a new value since last save, # Returns a hash of the properties that have been assigned a new value since last save,
# indicating their original values (attr => original value). # indicating their original values (attr => original value).
# ActiveRecord does not provide a mechanism to track changes in serialized keys, # ActiveRecord does not provide a mechanism to track changes in serialized keys,
...@@ -163,6 +180,7 @@ class Service < ActiveRecord::Base ...@@ -163,6 +180,7 @@ class Service < ActiveRecord::Base
assembla assembla
bamboo bamboo
buildkite buildkite
builds_email
campfire campfire
custom_issue_tracker custom_issue_tracker
drone_ci drone_ci
...@@ -170,7 +188,6 @@ class Service < ActiveRecord::Base ...@@ -170,7 +188,6 @@ class Service < ActiveRecord::Base
external_wiki external_wiki
flowdock flowdock
gemnasium gemnasium
gitlab_ci
hipchat hipchat
irker irker
jira jira
......
...@@ -136,7 +136,7 @@ class User < ActiveRecord::Base ...@@ -136,7 +136,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy has_one :abuse_report, dependent: :destroy
has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
# #
...@@ -771,10 +771,9 @@ class User < ActiveRecord::Base ...@@ -771,10 +771,9 @@ class User < ActiveRecord::Base
def ci_authorized_runners def ci_authorized_runners
@ci_authorized_runners ||= begin @ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject.joins(:project). runner_ids = Ci::RunnerProject.
where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})"). where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
select(:runner_id) select(:runner_id)
Ci::Runner.specific.where(id: runner_ids) Ci::Runner.specific.where(id: runner_ids)
end end
end end
......
...@@ -29,9 +29,11 @@ module Ci ...@@ -29,9 +29,11 @@ module Ci
build_attrs.merge!(ref: ref, build_attrs.merge!(ref: ref,
tag: tag, tag: tag,
trigger_request: trigger_request, trigger_request: trigger_request,
user: user) user: user,
project: commit.project)
commit.builds.create!(build_attrs) build = commit.builds.create!(build_attrs)
build.execute_hooks
end end
end end
end end
......
module Ci
class CreateCommitService
def execute(project, user, params)
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
unless origin_ref && sha.present?
return false
end
ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '')
# Skip branch removal
if sha == Ci::Git::BLANK_SHA
return false
end
tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha)
unless commit.skip_ci?
commit.update_committed!
commit.create_builds(ref, tag, user)
end
commit
end
end
end
module Ci module Ci
class CreateTriggerRequestService class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil) def execute(project, trigger, ref, variables = nil)
commit = project.gl_project.commit(ref) commit = project.commit(ref)
return unless commit return unless commit
# check if ref is tag # check if ref is tag
tag = project.gl_project.repository.find_tag(ref).present? tag = project.repository.find_tag(ref).present?
ci_commit = project.gl_project.ensure_ci_commit(commit.sha) ci_commit = project.ensure_ci_commit(commit.sha)
trigger_request = trigger.trigger_requests.create!( trigger_request = trigger.trigger_requests.create!(
variables: variables, variables: variables,
......
module Ci
class EventService
def remove_project(user, project)
create(
description: "Project \"#{project.name}\" has been removed by #{user.username}",
user_id: user.id,
is_admin: true
)
end
def create_project(user, project)
create(
description: "Project \"#{project.name}\" has been created by #{user.username}",
user_id: user.id,
is_admin: true
)
end
def change_project_settings(user, project)
create(
project_id: project.id,
user_id: user.id,
description: "User \"#{user.username}\" updated projects settings"
)
end
def create(*args)
Ci::Event.create!(*args)
end
end
end
...@@ -4,10 +4,10 @@ module Ci ...@@ -4,10 +4,10 @@ module Ci
sha = params[:sha] sha = params[:sha]
sha ||= sha ||=
if params[:ref] if params[:ref]
project.gl_project.commit(params[:ref]).try(:sha) project.commit(params[:ref]).try(:sha)
end end
commit = project.commits.ordered.find_by(sha: sha) commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit) image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name) image_path = Rails.root.join('public/ci', image_name)
......
...@@ -8,10 +8,10 @@ module Ci ...@@ -8,10 +8,10 @@ module Ci
builds = builds =
if current_runner.shared? if current_runner.shared?
# don't run projects which have not enables shared runners # don't run projects which have not enables shared runners
builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true }) builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true })
else else
# do run projects which are only assigned to this runner # do run projects which are only assigned to this runner
builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids }) builds.where(project: current_runner.projects.where(builds_enabled: true))
end end
builds = builds.order('created_at ASC') builds = builds.order('created_at ASC')
...@@ -20,10 +20,9 @@ module Ci ...@@ -20,10 +20,9 @@ module Ci
build.can_be_served?(current_runner) build.can_be_served?(current_runner)
end end
if build if build
# In case when 2 runners try to assign the same build, second runner will be declined # In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachine::InvalidTransition in run! method. # with StateMachines::InvalidTransition in run! method.
build.with_lock do build.with_lock do
build.runner_id = current_runner.id build.runner_id = current_runner.id
build.save! build.save!
...@@ -33,7 +32,7 @@ module Ci ...@@ -33,7 +32,7 @@ module Ci
build build
rescue StateMachine::InvalidTransition rescue StateMachines::InvalidTransition
nil nil
end end
end end
......
module Ci
class TestHookService
def execute(hook, current_user)
Ci::WebHookService.new.build_end(hook.project.commits.last.last_build)
end
end
end
class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
unless origin_ref && sha.present?
return false
end
ref = Gitlab::Git.ref_name(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
tag = Gitlab::Git.tag_ref?(origin_ref)
commit = project.ensure_ci_commit(sha)
unless commit.skip_ci?
commit.update_committed!
commit.create_builds(ref, tag, user)
end
commit
end
end
...@@ -61,6 +61,7 @@ class GitPushService ...@@ -61,6 +61,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks) project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(project, @user, @push_data)
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment