From e84c155f092600b90be291f0f7bb649811fa53fb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 16:16:39 +0200 Subject: WIP --- app/controllers/projects/pipelines_controller.rb | 102 +++++++++++++++++++++ app/models/ability.rb | 7 +- app/views/layouts/nav/_project.html.haml | 7 ++ app/views/projects/ci/commits/_commit.html.haml | 73 +++++++++++++++ .../projects/ci_commits/_header_title.html.haml | 1 + app/views/projects/ci_commits/index.html.haml | 65 +++++++++++++ app/views/projects/ci_commits/new.html.haml | 25 +++++ config/routes.rb | 7 ++ 8 files changed, 286 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index e69de29bb2d..764c8cc9cca 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -0,0 +1,102 @@ +class Projects::PipelineController < Projects::ApplicationController + before_action :ci_commit, except: [:index, :new, :create] + before_action :authorize_read_pipeline! + before_action :authorize_create_pipeline!, only: [:new, :create] + before_action :authorize_update_pipeline!, only: [:retry, :cancel] + layout 'project' + + def index + @scope = params[:scope] + @all_commits = project.ci_commits + @commits = @all_commits.order(id: :desc) + @commits = + case @scope + when 'latest' + @commits + when 'running' + @commits.running_or_pending + when 'branches' + refs = project.repository.branches.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + when 'tags' + refs = project.repository.tags.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + else + @commits + end + @commits = @commits.page(params[:page]).per(30) + end + + def new + end + + def create + ref_names = project.repository.ref_names + unless ref_names.include?(params[:ref]) + @error = 'Reference not found' + render action: 'new' + return + end + + commit = project.commit(params[:ref]) + unless commit + @error = 'Commit not found' + render action: 'new' + return + end + + ci_commit = project.ci_commit(commit.id, params[:ref]) + if ci_commit + @error = 'Pipeline already created' + render action: 'new' + return + end + + # Skip creating ci_commit when no gitlab-ci.yml is found + commit = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) + unless commit.config_processor + @error = commit.yaml_errors || 'Missing .gitlab-ci.yml file' + render action: 'new' + return + end + + Ci::Commit.transaction do + commit.save! + commit.create_builds(params[:ref], false, current_user) + end + + redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id) + end + + def show + @commit = @ci_commit.commit + @builds = @ci_commit.builds + @statuses = @ci_commit.statuses + + respond_to do |format| + format.html + end + end + + def retry + ci_commit.builds.latest.failed.select(&:retryable?).each(&:retry) + + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + def cancel + ci_commit.builds.running_or_pending.each(&:cancel) + + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + def retry_builds + end + private + + def ci_commit + @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index c0bf6def7c5..ec5ac54c277 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -195,6 +195,7 @@ class Ability :admin_label, :read_commit_status, :read_build, + :read_pipeline, ] end @@ -206,6 +207,8 @@ class Ability :update_commit_status, :create_build, :update_build, + :create_pipeline, + :update_pipeline, :create_merge_request, :create_wiki, :push_code @@ -234,7 +237,8 @@ class Ability :admin_wiki, :admin_project, :admin_commit_status, - :admin_build + :admin_build, + :admin_pipeline ] end @@ -277,6 +281,7 @@ class Ability unless project.builds_enabled rules += named_abilities('build') + rules += named_abilities('pipeline') end rules diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75e..fcce1b1dc98 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -39,6 +39,13 @@ Commits - if project_nav_tab? :builds + = nav_link(controller: %w(ci_commits)) do + = link_to project_ci_commits_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do + = icon('ship fw') + %span + Pipelines + %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all)) + = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = icon('cubes fw') diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index e69de29bb2d..29efcc9cfdd 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -0,0 +1,73 @@ +- status = commit.status +%tr.commit + %td.commit-link + = link_to namespace_project_commit_url(@project.namespace, @project, commit), class: "ci-status ci-#{status}" do + = ci_icon_for_status(status) + %strong ##{commit.id} + + %td + %div + - if commit.ref + = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref) +   + - if commit.tag? + %span.label.label-primary tag + - if commit.branch? + %span.label.label-primary branch + - if commit.trigger_requests.any? + %span.label.label-primary triggered + - if commit.yaml_errors.present? + %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid + - if commit.builds.any?(&:stuck?) + %span.label.label-warning stuck + + - if commit_data = commit.commit_data + = render 'projects/branches/commit', commit: commit_data, project: @project + - else + %p + Cant find HEAD commit for this branch + + - stages.each do |stage| + %td + - status = commit.statuses.latest.where(stage: stage).status + %span.has-tooltip(title="#{status || "missing"}"){class: "ci-status-icon-#{status || "skipped"}"} + = ci_icon_for_status(status || "missing") + -#- if status + -# = ci_status_with_icon(status) + -#- else + -# = ci_status_with_icon('missing') + + %td + - if commit.started_at && commit.finished_at + %p + #{duration_in_words(commit.finished_at, commit.started_at)} + - if commit.finished_at + %p + #{time_ago_with_tooltip(commit.finished_at)} + + %td.content + .controls.hidden-xs.pull-right + - artifacts = commit.builds.latest.select { |status| status.artifacts? } + - if artifacts.present? + .dropdown.inline + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + = icon('download') + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right + - artifacts.each do |build| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do + %i.fa.fa-download + %span #{build.name} +   + + - if can?(current_user, :update_pipeline, @project) + - if commit.retryable? + = link_to retry_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = icon("repeat") + +   + + - if commit.active? + = link_to cancel_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do + = icon("remove cred") diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml index e69de29bb2d..27c125ca40f 100644 --- a/app/views/projects/ci_commits/_header_title.html.haml +++ b/app/views/projects/ci_commits/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Pipelines", project_ci_commits_path(@project)) diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml index e69de29bb2d..0347c220382 100644 --- a/app/views/projects/ci_commits/index.html.haml +++ b/app/views/projects/ci_commits/index.html.haml @@ -0,0 +1,65 @@ +- page_title "Pipelines" += render "header_title" + +.top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_ci_commits_path(@project) do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_commits.count(:id)) + + %li{class: ('active' if @scope == 'branches')} + = link_to project_ci_commits_path(@project, scope: :branches) do + Branches + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'tags')} + = link_to project_ci_commits_path(@project, scope: :tags) do + Tags + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'running')} + = link_to project_ci_commits_path(@project, scope: :running) do + Failed + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + .nav-controls + - if can? current_user, :create_pipeline, @project + = link_to new_namespace_project_ci_commit_path(@project.namespace, @project), class: 'btn btn-create' do + = icon('plus') + New + + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + + = link_to ci_lint_path, class: 'btn btn-default' do + = icon('wrench') + %span CI Lint + +.gray-content-block + Pipelines for #{(@scope || 'changes')} on this project + +%ul.content-list + - stages = @commits.stages + - if @commits.blank? + %li + .nothing-here-block No pipelines to show + - else + .table-holder + %table.table.builds + %tbody + %th Pipeline ID + %th Commit + - @commits.stages.each do |stage| + %th + = stage.titleize + %th + %th + = render @commits.includes(:statuses).includes(:builds), commit_sha: true, stage: true, allow_retry: true, stages: stages + + = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml index e69de29bb2d..e9a22bbb157 100644 --- a/app/views/projects/ci_commits/new.html.haml +++ b/app/views/projects/ci_commits/new.html.haml @@ -0,0 +1,25 @@ +- page_title "New Pipeline" += render "header_title" + +- if @error + .alert.alert-danger + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + = @error +%h3.page-title + New Pipeline +%hr + += form_tag namespace_project_ci_commits_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do + .form-group + = label_tag :ref, 'Create for', class: 'control-label' + .col-sm-10 + = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control' + .help-block Existing branch name, tag + .form-actions + = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3 + = link_to 'Cancel', namespace_project_ci_commits_path(@project.namespace, @project), class: 'btn btn-cancel' + +:javascript + var availableRefs = #{@project.repository.ref_names.to_json}; + + new NewBranchForm($('.js-create-branch-form'), availableRefs) diff --git a/config/routes.rb b/config/routes.rb index 842fbb99843..841b3f26272 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -654,6 +654,13 @@ Rails.application.routes.draw do resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] + resources :pipelines, only: [:index, :new, :create] do + member do + post :cancel + post :retry + end + end + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all -- cgit v1.2.3 From 406a796f76824e18f4dca2d29c41dcc3d2e4d457 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 19:57:22 +0200 Subject: Make Pipeline view work --- app/assets/stylesheets/framework/tables.scss | 19 +++++++ app/controllers/projects/pipelines_controller.rb | 2 +- app/helpers/ci_status_helper.rb | 2 +- app/helpers/gitlab_routing_helper.rb | 2 +- app/models/ci/commit.rb | 4 ++ app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/ci/commits/_commit.html.haml | 21 ++++--- .../projects/ci_commits/_header_title.html.haml | 1 - app/views/projects/ci_commits/index.html.haml | 65 ---------------------- app/views/projects/ci_commits/new.html.haml | 25 --------- .../projects/pipelines/_header_title.html.haml | 1 + app/views/projects/pipelines/index.html.haml | 65 ++++++++++++++++++++++ app/views/projects/pipelines/new.html.haml | 25 +++++++++ spec/models/project_spec.rb | 2 +- 14 files changed, 129 insertions(+), 107 deletions(-) delete mode 100644 app/views/projects/ci_commits/_header_title.html.haml delete mode 100644 app/views/projects/ci_commits/index.html.haml delete mode 100644 app/views/projects/ci_commits/new.html.haml create mode 100644 app/views/projects/pipelines/_header_title.html.haml create mode 100644 app/views/projects/pipelines/index.html.haml create mode 100644 app/views/projects/pipelines/new.html.haml diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 75b770ae5a2..3a7f5bb932e 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -34,6 +34,25 @@ table { font-weight: normal; font-size: 15px; border-bottom: 1px solid $border-color; + + .rotate { + height: 140px; + white-space: nowrap; + } + + .rotate > div { + transform: + /* Magic Numbers */ + translate(25px, 51px) + /* 45 is really 360 - 45 */ + rotate(315deg); + width: 30px; + } + + .rotate > div > span { + border-bottom: 1px solid #ccc; + padding: 5px 10px; + } } td { diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 764c8cc9cca..a3e72fbdef1 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,4 +1,4 @@ -class Projects::PipelineController < Projects::ApplicationController +class Projects::PipelinesController < Projects::ApplicationController before_action :ci_commit, except: [:index, :new, :create] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index effa7ce77e1..3f7282d0c6c 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -37,7 +37,7 @@ module CiStatusHelper return unless ci_commit.is_a?(Commit) || ci_commit.is_a?(Ci::Commit) link_to ci_icon_for_status(ci_commit.status), - project_ci_commit_path(ci_commit.project, ci_commit), + project_pipeline_path(ci_commit.project, ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f1af8e163cd..ed0db04e069 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -37,7 +37,7 @@ module GitlabRoutingHelper builds_namespace_project_commit_path(project.namespace, project, commit.id) end - def project_ci_commit_path(project, ci_commit) + def project_pipeline_path(project, ci_commit) builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 8865bd76bd2..687654d3c89 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -94,6 +94,10 @@ module Ci end end + def triggered? + trigger_requests.any? + end + def invalidate write_attribute(:status, nil) write_attribute(:started_at, nil) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index fcce1b1dc98..b58d8270230 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -40,7 +40,7 @@ - if project_nav_tab? :builds = nav_link(controller: %w(ci_commits)) do - = link_to project_ci_commits_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do = icon('ship fw') %span Pipelines diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 29efcc9cfdd..7c6ba216386 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -1,7 +1,7 @@ - status = commit.status %tr.commit %td.commit-link - = link_to namespace_project_commit_url(@project.namespace, @project, commit), class: "ci-status ci-#{status}" do + = link_to namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "ci-status ci-#{status}" do = ci_icon_for_status(status) %strong ##{commit.id} @@ -14,7 +14,7 @@ %span.label.label-primary tag - if commit.branch? %span.label.label-primary branch - - if commit.trigger_requests.any? + - if commit.triggered? %span.label.label-primary triggered - if commit.yaml_errors.present? %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid @@ -27,22 +27,21 @@ %p Cant find HEAD commit for this branch + - stages_status = commit.statuses.stages_status - stages.each do |stage| %td - - status = commit.statuses.latest.where(stage: stage).status - %span.has-tooltip(title="#{status || "missing"}"){class: "ci-status-icon-#{status || "skipped"}"} - = ci_icon_for_status(status || "missing") - -#- if status - -# = ci_status_with_icon(status) - -#- else - -# = ci_status_with_icon('missing') + - if status = stages_status[stage] + %span.has-tooltip(title="#{status}"){class: "ci-status-icon-#{status}"} + = ci_icon_for_status(status) %td - if commit.started_at && commit.finished_at %p + %i.fa.fa-late-o #{duration_in_words(commit.finished_at, commit.started_at)} - if commit.finished_at %p + %i.fa.fa-date-o #{time_ago_with_tooltip(commit.finished_at)} %td.content @@ -63,11 +62,11 @@ - if can?(current_user, :update_pipeline, @project) - if commit.retryable? - = link_to retry_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat")   - if commit.active? - = link_to cancel_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do + = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do = icon("remove cred") diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml deleted file mode 100644 index 27c125ca40f..00000000000 --- a/app/views/projects/ci_commits/_header_title.html.haml +++ /dev/null @@ -1 +0,0 @@ -- header_title project_title(@project, "Pipelines", project_ci_commits_path(@project)) diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml deleted file mode 100644 index 0347c220382..00000000000 --- a/app/views/projects/ci_commits/index.html.haml +++ /dev/null @@ -1,65 +0,0 @@ -- page_title "Pipelines" -= render "header_title" - -.top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to project_ci_commits_path(@project) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_commits.count(:id)) - - %li{class: ('active' if @scope == 'branches')} - = link_to project_ci_commits_path(@project, scope: :branches) do - Branches - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) - - %li{class: ('active' if @scope == 'tags')} - = link_to project_ci_commits_path(@project, scope: :tags) do - Tags - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to project_ci_commits_path(@project, scope: :running) do - Failed - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) - - .nav-controls - - if can? current_user, :create_pipeline, @project - = link_to new_namespace_project_ci_commit_path(@project.namespace, @project), class: 'btn btn-create' do - = icon('plus') - New - - - if can?(current_user, :update_build, @project) - - unless @repository.gitlab_ci_yml - = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' - - = link_to ci_lint_path, class: 'btn btn-default' do - = icon('wrench') - %span CI Lint - -.gray-content-block - Pipelines for #{(@scope || 'changes')} on this project - -%ul.content-list - - stages = @commits.stages - - if @commits.blank? - %li - .nothing-here-block No pipelines to show - - else - .table-holder - %table.table.builds - %tbody - %th Pipeline ID - %th Commit - - @commits.stages.each do |stage| - %th - = stage.titleize - %th - %th - = render @commits.includes(:statuses).includes(:builds), commit_sha: true, stage: true, allow_retry: true, stages: stages - - = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml deleted file mode 100644 index e9a22bbb157..00000000000 --- a/app/views/projects/ci_commits/new.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -- page_title "New Pipeline" -= render "header_title" - -- if @error - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - = @error -%h3.page-title - New Pipeline -%hr - -= form_tag namespace_project_ci_commits_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do - .form-group - = label_tag :ref, 'Create for', class: 'control-label' - .col-sm-10 - = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control' - .help-block Existing branch name, tag - .form-actions - = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', namespace_project_ci_commits_path(@project.namespace, @project), class: 'btn btn-cancel' - -:javascript - var availableRefs = #{@project.repository.ref_names.to_json}; - - new NewBranchForm($('.js-create-branch-form'), availableRefs) diff --git a/app/views/projects/pipelines/_header_title.html.haml b/app/views/projects/pipelines/_header_title.html.haml new file mode 100644 index 00000000000..faf63d64a79 --- /dev/null +++ b/app/views/projects/pipelines/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Pipelines", project_pipelines_path(@project)) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml new file mode 100644 index 00000000000..b9877cd37be --- /dev/null +++ b/app/views/projects/pipelines/index.html.haml @@ -0,0 +1,65 @@ +- page_title "Pipelines" += render "header_title" + +.top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_pipelines_path(@project) do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_commits.count(:id)) + + %li{class: ('active' if @scope == 'branches')} + = link_to project_pipelines_path(@project, scope: :branches) do + Branches + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'tags')} + = link_to project_pipelines_path(@project, scope: :tags) do + Tags + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'running')} + = link_to project_pipelines_path(@project, scope: :running) do + Failed + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + .nav-controls + - if can? current_user, :create_pipeline, @project + = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do + = icon('plus') + New + + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + + = link_to ci_lint_path, class: 'btn btn-default' do + = icon('wrench') + %span CI Lint + +.gray-content-block + Pipelines for #{(@scope || 'changes')} on this project + +%ul.content-list + - stages = @commits.stages + - if @commits.blank? + %li + .nothing-here-block No pipelines to show + - else + .table-holder + %table.table.builds + %tbody + %th Pipeline ID + %th Commit + - @commits.stages.each do |stage| + %th.rotate + = stage.titleize + %th + %th + = render @commits, commit_sha: true, stage: true, allow_retry: true, stages: stages + + = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml new file mode 100644 index 00000000000..39b1571b9cf --- /dev/null +++ b/app/views/projects/pipelines/new.html.haml @@ -0,0 +1,25 @@ +- page_title "New Pipeline" += render "header_title" + +- if @error + .alert.alert-danger + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + = @error +%h3.page-title + New Pipeline +%hr + += form_tag namespace_project_pipelines_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do + .form-group + = label_tag :ref, 'Create for', class: 'control-label' + .col-sm-10 + = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control' + .help-block Existing branch name, tag + .form-actions + = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3 + = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel' + +:javascript + var availableRefs = #{@project.repository.ref_names.to_json}; + + new NewBranchForm($('.js-create-branch-form'), availableRefs) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1688e91ca62..59df2c5cb87 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -62,7 +62,7 @@ describe Project, models: true do it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_many(:commit_statuses) } - it { is_expected.to have_many(:ci_commits) } + it { is_expected.to have_many(:pipelines) } it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runners) } -- cgit v1.2.3 From f5d24e60f842096f670593fb4dd0d29c3f5d4fcc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 13:01:08 +0200 Subject: Pipeline view --- app/assets/stylesheets/framework/tables.scss | 3 -- app/controllers/projects/pipelines_controller.rb | 56 +++++++++--------------- app/models/ci/commit.rb | 7 +++ app/views/projects/ci/builds/_build.html.haml | 4 +- app/views/projects/ci/commits/_commit.html.haml | 41 +++++++++-------- app/views/projects/commit/_ci_commit.html.haml | 30 +++---------- app/views/projects/pipelines/index.html.haml | 33 +++++++------- app/views/projects/pipelines/show.html.haml | 3 ++ config/routes.rb | 2 +- 9 files changed, 79 insertions(+), 100 deletions(-) create mode 100644 app/views/projects/pipelines/show.html.haml diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 3a7f5bb932e..9d6a6c5b237 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -38,9 +38,6 @@ table { .rotate { height: 140px; white-space: nowrap; - } - - .rotate > div { transform: /* Magic Numbers */ translate(25px, 51px) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index a3e72fbdef1..b2ee5573bfc 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,5 @@ class Projects::PipelinesController < Projects::ApplicationController - before_action :ci_commit, except: [:index, :new, :create] + before_action :pipeline, except: [:index, :new, :create] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] @@ -7,26 +7,24 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - @all_commits = project.ci_commits - @commits = @all_commits.order(id: :desc) - @commits = + @all_pipelines = project.ci_commits + @pipelines = @all_pipelines.order(id: :desc) + @pipelines = case @scope - when 'latest' - @commits when 'running' - @commits.running_or_pending + @pipelines.running_or_pending when 'branches' - refs = project.repository.branches.map(&:name) - ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') - @commits.where(id: ids) + @branches = project.repository.branches.map(&:name) + @branches_ids = @all_pipelines.where(ref: @branches).group(:ref).select('max(id)') + @pipelines.where(id: @branches_ids) when 'tags' - refs = project.repository.tags.map(&:name) - ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') - @commits.where(id: ids) + @tags = project.repository.tags.map(&:name) + @tags_ids = @all_pipelines.where(ref: @tags).group(:ref).select('max(id)') + @pipelines.where(id: @tags_ids) else - @commits + @pipelines end - @commits = @commits.page(params[:page]).per(30) + @pipelines = @pipelines.page(params[:page]).per(30) end def new @@ -47,56 +45,44 @@ class Projects::PipelinesController < Projects::ApplicationController return end - ci_commit = project.ci_commit(commit.id, params[:ref]) - if ci_commit - @error = 'Pipeline already created' - render action: 'new' - return - end + pipeline = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) # Skip creating ci_commit when no gitlab-ci.yml is found - commit = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) - unless commit.config_processor - @error = commit.yaml_errors || 'Missing .gitlab-ci.yml file' + unless pipeline.config_processor + @error = pipeline.yaml_errors || 'Missing .gitlab-ci.yml file' render action: 'new' return end Ci::Commit.transaction do commit.save! - commit.create_builds(params[:ref], false, current_user) + commit.create_builds(current_user) end redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id) end def show - @commit = @ci_commit.commit - @builds = @ci_commit.builds - @statuses = @ci_commit.statuses - respond_to do |format| format.html end end def retry - ci_commit.builds.latest.failed.select(&:retryable?).each(&:retry) + pipeline.builds.latest.failed.select(&:retryable?).each(&:retry) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end def cancel - ci_commit.builds.running_or_pending.each(&:cancel) + pipeline.builds.running_or_pending.each(&:cancel) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end - def retry_builds - end private - def ci_commit - @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + def pipeline + @pipeline ||= project.ci_commits.find_by!(id: params[:id]) end end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 687654d3c89..7991b987e35 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -94,6 +94,13 @@ module Ci end end + def latest? + return false unless ref + commit = project.commit(ref) + return false unless commit + commit.sha == sha + end + def triggered? trigger_requests.any? end diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 218d396b898..7ded4828b2f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -13,7 +13,9 @@ %strong ##{build.id} - if build.stuck? - %i.fa.fa-warning.text-warning + %i.fa.fa-warning.text-warning.has-tooltip(title="Build is stuck. Check runners.") + - if defined?(retried) && retried + %i.fa.fa-warning.has-tooltip(title="Build was retried") - if defined?(commit_sha) && commit_sha %td diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 7c6ba216386..32f85cb8f8c 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -1,19 +1,21 @@ - status = commit.status %tr.commit %td.commit-link - = link_to namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "ci-status ci-#{status}" do + = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do = ci_icon_for_status(status) %strong ##{commit.id} %td - %div + %div.branch-commit - if commit.ref - = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref) + = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace" + · + = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"   + - if commit.latest? + %span.label.label-success latest - if commit.tag? %span.label.label-primary tag - - if commit.branch? - %span.label.label-primary branch - if commit.triggered? %span.label.label-primary triggered - if commit.yaml_errors.present? @@ -21,32 +23,36 @@ - if commit.builds.any?(&:stuck?) %span.label.label-warning stuck - - if commit_data = commit.commit_data - = render 'projects/branches/commit', commit: commit_data, project: @project - - else - %p - Cant find HEAD commit for this branch + %p + %span + - if commit_data = commit.commit_data + = link_to_gfm commit_data.title, namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message" + - else + Cant find HEAD commit for this branch + - stages_status = commit.statuses.stages_status - stages.each do |stage| %td - if status = stages_status[stage] - %span.has-tooltip(title="#{status}"){class: "ci-status-icon-#{status}"} + %span.has-tooltip(title="#{stage.titleize}: #{status}"){class: "ci-status-icon-#{status}"} = ci_icon_for_status(status) %td - if commit.started_at && commit.finished_at %p - %i.fa.fa-late-o + %i.fa.fa-clock-o +   #{duration_in_words(commit.finished_at, commit.started_at)} - if commit.finished_at %p - %i.fa.fa-date-o + %i.fa.fa-calendar +   #{time_ago_with_tooltip(commit.finished_at)} - %td.content + %td .controls.hidden-xs.pull-right - - artifacts = commit.builds.latest.select { |status| status.artifacts? } + - artifacts = commit.builds.latest - if artifacts.present? .dropdown.inline %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} @@ -58,15 +64,12 @@ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do %i.fa.fa-download %span #{build.name} -   - if can?(current_user, :update_pipeline, @project) - - if commit.retryable? + - if commit.retryable? && commit.builds.failed.any? = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat") -   - - if commit.active? = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do = icon("remove cred") diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 06520e40bd9..582ce61a64a 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -2,12 +2,15 @@ .pull-right - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post + = link_to "Retry failed", retry_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: 'btn btn-grouped btn-primary', method: :post - if ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + = link_to "Cancel running", cancel_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post .oneline + Pipeline + = link_to "##{ci_commit.id}", namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: "monospace" + with = pluralize ci_commit.statuses.count(:id), "build" - if ci_commit.ref for @@ -17,7 +20,7 @@ for commit = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" - if ci_commit.duration > 0 - in + took = time_interval_in_words ci_commit.duration - if ci_commit.yaml_errors.present? @@ -47,23 +50,4 @@ %th - builds = ci_commit.statuses.latest.ordered = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true - -- if ci_commit.retried.any? - .gray-content-block.second-block - Retried builds - - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @project.build_coverage_enabled? - %th Coverage - %th - = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false + = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false, retried: true diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index b9877cd37be..838b2986d4f 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -7,25 +7,21 @@ = link_to project_pipelines_path(@project) do All %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_commits.count(:id)) + = number_with_delimiter(@all_pipelines.count) + + %li{class: ('active' if @scope == 'running')} + = link_to project_pipelines_path(@project, scope: :running) do + Running + %span.badge.js-running-count + = number_with_delimiter(@all_pipelines.running_or_pending.count) %li{class: ('active' if @scope == 'branches')} = link_to project_pipelines_path(@project, scope: :branches) do Branches - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'tags')} = link_to project_pipelines_path(@project, scope: :tags) do Tags - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to project_pipelines_path(@project, scope: :running) do - Failed - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) .nav-controls - if can? current_user, :create_pipeline, @project @@ -45,8 +41,8 @@ Pipelines for #{(@scope || 'changes')} on this project %ul.content-list - - stages = @commits.stages - - if @commits.blank? + - stages = @pipelines.stages + - if @pipelines.blank? %li .nothing-here-block No pipelines to show - else @@ -55,11 +51,12 @@ %tbody %th Pipeline ID %th Commit - - @commits.stages.each do |stage| - %th.rotate - = stage.titleize + - @pipelines.stages.each do |stage| + %th + %span.has-tooltip(title="#{stage.titleize}") + = truncate(stage.titleize.pluralize, length: 8) %th %th - = render @commits, commit_sha: true, stage: true, allow_retry: true, stages: stages + = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages - = paginate @commits, theme: 'gitlab' + = paginate @pipelines, theme: 'gitlab' diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml new file mode 100644 index 00000000000..9f33e2ad624 --- /dev/null +++ b/app/views/projects/pipelines/show.html.haml @@ -0,0 +1,3 @@ +- page_title "Pipeline" += render "header_title" += render "projects/commit/ci_commit", ci_commit: @pipeline diff --git a/config/routes.rb b/config/routes.rb index 841b3f26272..6384757835a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -654,7 +654,7 @@ Rails.application.routes.draw do resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] - resources :pipelines, only: [:index, :new, :create] do + resources :pipelines, only: [:index, :new, :create, :show] do member do post :cancel post :retry -- cgit v1.2.3 From 410f2b40f2579b2e6a77591157900ce07512ee36 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:39:23 +0200 Subject: Remove unneeded changes --- app/assets/stylesheets/framework/tables.scss | 16 ---------------- app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/commit/_ci_commit.html.haml | 2 +- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 9d6a6c5b237..75b770ae5a2 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -34,22 +34,6 @@ table { font-weight: normal; font-size: 15px; border-bottom: 1px solid $border-color; - - .rotate { - height: 140px; - white-space: nowrap; - transform: - /* Magic Numbers */ - translate(25px, 51px) - /* 45 is really 360 - 45 */ - rotate(315deg); - width: 30px; - } - - .rotate > div > span { - border-bottom: 1px solid #ccc; - padding: 5px 10px; - } } td { diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index b58d8270230..f4797a85bb7 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -39,7 +39,7 @@ Commits - if project_nav_tab? :builds - = nav_link(controller: %w(ci_commits)) do + = nav_link(controller: %w(pipelines)) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do = icon('ship fw') %span diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 2ec3c809e1c..cf101acbb53 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -20,7 +20,7 @@ for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - if ci_commit.duration > 0 - took + in = time_interval_in_words ci_commit.duration - if ci_commit.yaml_errors.present? -- cgit v1.2.3 From c351b9f599aa1af693435738d2c897cc2a954fe7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:51:52 +0200 Subject: Improve rendered CI statuses --- app/helpers/ci_status_helper.rb | 27 ++++++++++++++-------- app/views/projects/commits/_commit.html.haml | 2 +- .../projects/issues/_merge_requests.html.haml | 2 +- .../projects/issues/_related_branches.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 2 +- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 417050b4132..acc01b008bf 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -38,15 +38,24 @@ module CiStatusHelper icon(icon_name + ' fw') end - def render_ci_status(ci_commit, tooltip_placement: 'auto left') - # TODO: split this method into - # - render_commit_status - # - render_pipeline_status - link_to ci_icon_for_status(ci_commit.status), - ci_status_path(ci_commit), - class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", - title: "Build #{ci_label_for_status(ci_commit.status)}", - data: { toggle: 'tooltip', placement: tooltip_placement } + def render_commit_status(commit, tooltip_placement: 'auto left') + project = commit.project + path = builds_namespace_project_commit_path(project.namespace, project, commit) + render_status_with_link('commit', commit.status, path, tooltip_placement) + end + + def render_pipeline_status(pipeline, tooltip_placement: 'auto left') + project = pipeline.project + path = namespace_project_pipeline_path(project.namespace, project, pipeline) + render_status_with_link('pipeline', pipeline.status, path, tooltip_placement) + end + + def render_status_with_link(type, status, path, tooltip_placement) + link_to ci_icon_for_status(status), + path, + class: "ci-status-link ci-status-icon-#{status.dasherize}", + title: "#{type.titleize}: #{ci_label_for_status(status)}", + data: { toggle: 'tooltip', placement: tooltip_placement } end def no_runners_for_project?(project) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index f7c8647ac0e..b231b584eab 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -17,7 +17,7 @@ .pull-right - if commit.status - = render_ci_status(commit) + = render_commit_status(commit) = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d6b38b327ff..e953353567e 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -7,7 +7,7 @@ %li %span.merge-request-ci-status - if merge_request.ci_commit - = render_ci_status(merge_request.ci_commit) + = render_pipeline_status(merge_request.ci_commit) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index bdfa0c7009e..5f9d2919982 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -8,7 +8,7 @@ - ci_commit = @project.ci_commit(sha, branch) if sha - if ci_commit %span.related-branch-ci-status - = render_ci_status(ci_commit) + = render_pipeline_status(ci_commit) %span.related-branch-info %strong = link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "label-branch" do diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 391193eed6c..7bfde8b1c57 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -13,7 +13,7 @@ - if merge_request.ci_commit %li - = render_ci_status(merge_request.ci_commit) + = render_pipeline_status(merge_request.ci_commit) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index ab8b022411d..9ef021747a5 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -17,7 +17,7 @@ = project.main_language - if project.commit.try(:status) %span - = render_ci_status(project.commit) + = render_commit_status(project.commit) - if forks %span = icon('code-fork') -- cgit v1.2.3 From cb6f035141d2e7792d9594e5d664d1a305b728cf Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 17:05:17 +0200 Subject: Improve pipeline view --- app/controllers/projects/pipelines_controller.rb | 5 +++++ app/views/projects/commit/_ci_commit.html.haml | 3 +-- app/views/projects/commit/_commit_box.html.haml | 11 +++++++++-- app/views/projects/pipelines/show.html.haml | 6 ++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index b2ee5573bfc..aba64e4a730 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,6 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :pipeline, except: [:index, :new, :create] + before_action :commit, only: [:show] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] @@ -85,4 +86,8 @@ class Projects::PipelinesController < Projects::ApplicationController def pipeline @pipeline ||= project.ci_commits.find_by!(id: params[:id]) end + + def commit + @commit ||= @pipeline.commit_data + end end diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index cf101acbb53..782ea341daf 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -14,8 +14,7 @@ = pluralize ci_commit.statuses.count(:id), "build" - if ci_commit.ref for - %span.label.label-info - = ci_commit.ref + = link_to ci_commit.ref, namespace_project_commits_path(@project.namespace, @project, ci_commit.ref), class: "monospace" - if defined?(link_to_commit) && link_to_commit for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 0908e830f83..9cb14b6a90f 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,6 +1,6 @@ .pull-right %div - - if @notes_count > 0 + - if defined?(@notes_count) && @notes_count > 0 %span.btn.disabled.btn-grouped %i.fa.fa-comment = @notes_count @@ -42,7 +42,14 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @commit.status +- if defined?(pipeline) && pipeline + .pull-right + = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline), class: "ci-status ci-#{pipeline.status}" do + = ci_icon_for_status(pipeline.status) + pipeline: + = ci_label_for_status(pipeline.status) + +- elsif @commit.status .pull-right = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do = ci_icon_for_status(@commit.status) diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 9f33e2ad624..8a2e14d8d87 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,3 +1,9 @@ - page_title "Pipeline" + = render "header_title" +.prepend-top-default + - if @commit + = render "projects/commit/commit_box", pipeline: @pipeline + %div.block-connector + = render "projects/commit/ci_commit", ci_commit: @pipeline -- cgit v1.2.3 From 21136baa77369d5990ef5db4af26d688aedc8320 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 20:51:03 +0200 Subject: Update handling of skipped status --- app/models/ci/build.rb | 2 +- app/models/ci/commit.rb | 23 ++++++++--------------- app/models/concerns/ci_status.rb | 10 +++++++++- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 085ecc6951c..c0b334d3600 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -116,7 +116,7 @@ module Ci end def retried? - !self.commit.latest.include?(self) + !self.commit.statuses.latest.include?(self) end def retry diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index e2bf4d62541..00a95dd05be 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -125,16 +125,12 @@ module Ci end end - def latest - statuses.latest - end - def retried @retried ||= (statuses.order(id: :desc) - statuses.latest) end def coverage - coverage_array = latest.map(&:coverage).compact + coverage_array = statuses.latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -169,18 +165,15 @@ module Ci private def update_state - reload - self.status = if yaml_errors.present? - 'failed' + statuses.reload + self.status = if yaml_errors.blank? + statuses.latest.status || 'skipped' else - latest.status + 'failed' end - self.started_at = statuses.minimum(:started_at) - self.finished_at = statuses.maximum(:finished_at) - self.duration = begin - duration_array = latest.map(&:duration).compact - duration_array.reduce(:+).to_i - end + self.started_at = statuses.started_at + self.finished_at = statuses.finished_at + self.duration = statuses.latest.duration save end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index fd86d2f7553..8190b2a20c6 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -15,7 +15,7 @@ module CiStatus skipped = all.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' WHEN (#{builds})=(#{canceled}) THEN 'canceled' @@ -35,6 +35,14 @@ module CiStatus duration_array = all.map(&:duration).compact duration_array.reduce(:+).to_i end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.minimum(:finished_at) + end end included do -- cgit v1.2.3 From c6f19aed51736e5945283a611eae09f32a9b5aeb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 26 Apr 2016 16:01:00 +0200 Subject: Fix builds rendering bug --- app/views/projects/commit/_ci_commit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 782ea341daf..21a30080868 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -18,7 +18,7 @@ - if defined?(link_to_commit) && link_to_commit for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - - if ci_commit.duration > 0 + - if ci_commit.duration in = time_interval_in_words ci_commit.duration -- cgit v1.2.3 From 1ad0c968d579fe6ac0b2fc00a1dae32449ceb2c3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 23:39:48 +0300 Subject: Make a build views nicer --- app/models/commit_status.rb | 2 +- app/views/projects/ci/builds/_build.html.haml | 52 +++++++++++++++++-------- app/views/projects/ci/commits/_commit.html.haml | 6 ++- app/views/projects/commit/_ci_commit.html.haml | 28 ++++++------- app/views/projects/pipelines/index.html.haml | 7 +++- 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index aa56314aa16..c0ae30820ee 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -89,7 +89,7 @@ class CommitStatus < ActiveRecord::Base def self.stages order_by = 'max(stage_idx)' - group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact + CommitStatus.where(id: all).group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact end def self.stages_status diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 745ff461d76..7e7e54c68a3 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -42,25 +42,43 @@ %td = build.name - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail - - if defined?(retried) && retried - %span.label.label-warning retried + .pull-right + .label-container + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %span.label.label-info triggered + - if build.try(:allow_failure) + %span.label.label-danger allowed to fail + - if defined?(retried) && retried + %span.label.label-warning retried - %td.duration - - if build.duration - #{duration_in_words(build.finished_at, build.started_at)} + - if defined?(new_duration) && new_duration + %td.duration + - if build.duration + %p + %i.fa.fa-clock-o +   + #{duration_in_words(build.finished_at, build.started_at)} + - if build.finished_at + %p + %i.fa.fa-calendar +   + #{time_ago_with_tooltip(build.finished_at)} + - else + %td.duration + - if build.duration + %i.fa.fa-clock-o +   + #{duration_in_words(build.finished_at, build.started_at)} - %td.timestamp - - if build.finished_at - %span #{time_ago_with_tooltip(build.finished_at)} + %td.timestamp + - if build.finished_at + %i.fa.fa-calendar +   + %span #{time_ago_with_tooltip(build.finished_at)} - if defined?(coverage) && coverage %td.coverage diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 32f85cb8f8c..d5dd6c7b0aa 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -35,7 +35,8 @@ - stages.each do |stage| %td - if status = stages_status[stage] - %span.has-tooltip(title="#{stage.titleize}: #{status}"){class: "ci-status-icon-#{status}"} + - tooltip = "#{stage.titleize}: #{status}" + %span.has-tooltip(title="#{tooltip}"){class: "ci-status-icon-#{status}"} = ci_icon_for_status(status) %td @@ -52,7 +53,7 @@ %td .controls.hidden-xs.pull-right - - artifacts = commit.builds.latest + - artifacts = commit.builds.latest.select { |b| b.artifacts? } - if artifacts.present? .dropdown.inline %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} @@ -66,6 +67,7 @@ %span #{build.name} - if can?(current_user, :update_pipeline, @project) +   - if commit.retryable? && commit.builds.failed.any? = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat") diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 21a30080868..ba0baa1e2a2 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -36,17 +36,19 @@ .table-holder %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Stage - %th Name - %th Duration - %th Finished at - - if @project.build_coverage_enabled? - %th Coverage - %th - builds = ci_commit.statuses.latest.ordered - = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true - = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false, retried: true + - CommitStatus.where(id: builds).stages.each do |stage| + - stage_builds = builds.where(stage: stage) + %tr + %th{colspan: 10} + %strong + - stage_status = CommitStatus.where(id: stage_builds.ids).status + %span{class: "ci-status-link ci-status-icon-#{stage_status}"} + = ci_icon_for_status(stage_status) +   + = stage.titleize.pluralize + = render stage_builds, coverage: @project.build_coverage_enabled?, tage: false, ref: false, allow_retry: true + = render ci_commit.retried.select { |build| build.stage == stage }, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true + %tr + %td{colspan: 10} +   diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 838b2986d4f..0e08753fd3b 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -38,7 +38,12 @@ %span CI Lint .gray-content-block - Pipelines for #{(@scope || 'changes')} on this project + - if @scope == 'running' + Running pipelines for this project + - elsif @scope.nil? + Pipelines for this project + - else + #{@scope.titleize} for this project %ul.content-list - stages = @pipelines.stages -- cgit v1.2.3 From 504a1fac95a2af2cf90f00f310d244d8d37f0015 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 May 2016 00:58:53 +0300 Subject: Fix SQL queries for calculating stages status --- app/models/commit_status.rb | 12 +++++++----- app/views/projects/commit/_ci_commit.html.haml | 5 ++--- app/views/projects/commit/_ci_stage.html.haml | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index b031259e252..0c0c3d38f94 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -51,7 +51,7 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } - scope :ordered, -> { order(:ref, :stage_idx, :name) } + scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } state_machine :status, initial: :pending do @@ -91,13 +91,15 @@ class CommitStatus < ActiveRecord::Base end def self.stages - order_by = 'max(stage_idx)' - CommitStatus.where(id: all).group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact + # We group by stage name, but order stages by their's index + unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') end def self.stages_status - all.stages.inject({}) do |h, stage| - h[stage] = all.where(stage: stage).status + # We execute subquery for each of the stages which calculates an Stage Status + statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) + statuses.inject({}) do |h, k| + h[k.first] = k.last h end end diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 1a17d1b4ed6..c8443763613 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -36,6 +36,5 @@ .table-holder %table.table.builds - - stages = ci_commit.statuses.latest.stages_status - - stages.each do |stage, status| - = render 'ci_stage', stage: stage, status: status + - ci_commit.statuses.stages.each do |stage| + = render 'projects/commit/ci_stage', stage: stage, statuses: ci_commit.statuses.where(stage: stage) diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml index 0fb428dcd26..f9f2757dc34 100644 --- a/app/views/projects/commit/_ci_stage.html.haml +++ b/app/views/projects/commit/_ci_stage.html.haml @@ -1,15 +1,15 @@ -- statuses = ci_commit.statuses.where(stage: stage).to_a -- retried = statuses.select(&:retried?) -- latest = statuses - retried +- latest = statuses.latest +- retried = statuses.where.not(id: latest) %tr %th{colspan: 10} %strong + - status = latest.status %span{class: "ci-status-link ci-status-icon-#{status}"} = ci_icon_for_status(status)   = stage.titleize.pluralize - = render latest, coverage: @project.build_coverage_enabled?, tage: false, ref: false, allow_retry: true - = render retried, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true + = render latest.ordered, coverage: @project.build_coverage_enabled?, tage: false, ref: false, allow_retry: true + = render retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true %tr %td{colspan: 10}   -- cgit v1.2.3 From fe2137d871b978033007a3375cf6f1edf0f04cd4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 May 2016 02:26:13 +0300 Subject: Improve pipelines design --- app/controllers/projects/pipelines_controller.rb | 65 +++++++----------------- app/finders/pipelines_finder.rb | 38 ++++++++++++++ app/helpers/ci_status_helper.rb | 12 +++-- app/models/ci/commit.rb | 8 +++ app/services/ci/create_pipeline_service.rb | 42 +++++++++++++++ app/views/layouts/nav/_project.html.haml | 6 +-- app/views/projects/ci/builds/_build.html.haml | 18 +++---- app/views/projects/ci/commits/_commit.html.haml | 8 +-- spec/models/project_spec.rb | 2 +- 9 files changed, 129 insertions(+), 70 deletions(-) create mode 100644 app/finders/pipelines_finder.rb create mode 100644 app/services/ci/create_pipeline_service.rb diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index aba64e4a730..a261d03f4d0 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -4,63 +4,28 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] - layout 'project' def index @scope = params[:scope] @all_pipelines = project.ci_commits - @pipelines = @all_pipelines.order(id: :desc) - @pipelines = - case @scope - when 'running' - @pipelines.running_or_pending - when 'branches' - @branches = project.repository.branches.map(&:name) - @branches_ids = @all_pipelines.where(ref: @branches).group(:ref).select('max(id)') - @pipelines.where(id: @branches_ids) - when 'tags' - @tags = project.repository.tags.map(&:name) - @tags_ids = @all_pipelines.where(ref: @tags).group(:ref).select('max(id)') - @pipelines.where(id: @tags_ids) - else - @pipelines - end - @pipelines = @pipelines.page(params[:page]).per(30) + @pipelines = PipelinesFinder.new(project).execute(@all_pipelines, @scope) + @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30) end def new end def create - ref_names = project.repository.ref_names - unless ref_names.include?(params[:ref]) - @error = 'Reference not found' - render action: 'new' - return + begin + pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute + redirect_to namespace_project_pipeline_path(project.namespace, project, pipeline) + rescue ArgumentError => e + @error = e.message + render 'new' + rescue => e + @error = 'Undefined error' + render 'new' end - - commit = project.commit(params[:ref]) - unless commit - @error = 'Commit not found' - render action: 'new' - return - end - - pipeline = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) - - # Skip creating ci_commit when no gitlab-ci.yml is found - unless pipeline.config_processor - @error = pipeline.yaml_errors || 'Missing .gitlab-ci.yml file' - render action: 'new' - return - end - - Ci::Commit.transaction do - commit.save! - commit.create_builds(current_user) - end - - redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id) end def show @@ -70,19 +35,23 @@ class Projects::PipelinesController < Projects::ApplicationController end def retry - pipeline.builds.latest.failed.select(&:retryable?).each(&:retry) + pipeline.retry_failed redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end def cancel - pipeline.builds.running_or_pending.each(&:cancel) + pipeline.cancel_running redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end private + def create_params + params.permit(:ref) + end + def pipeline @pipeline ||= project.ci_commits.find_by!(id: params[:id]) end diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb new file mode 100644 index 00000000000..c19a795d467 --- /dev/null +++ b/app/finders/pipelines_finder.rb @@ -0,0 +1,38 @@ +class PipelinesFinder + attr_reader :project + + def initialize(project) + @project = project + end + + def execute(pipelines, scope) + case scope + when 'running' + pipelines.running_or_pending + when 'branches' + from_ids(pipelines, ids_for_ref(pipelines, branches)) + when 'tags' + from_ids(pipelines, ids_for_ref(pipelines, tags)) + else + pipelines + end + end + + private + + def ids_for_ref(pipelines, refs) + pipelines.where(ref: refs).group(:ref).select('max(id)') + end + + def from_ids(pipelines, ids) + pipelines.unscoped.where(id: ids) + end + + def branches + project.repository.branches.map(&:name) + end + + def tags + project.repository.tags.map(&:name) + end +end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index acc01b008bf..cfad17dcacf 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -50,6 +50,13 @@ module CiStatusHelper render_status_with_link('pipeline', pipeline.status, path, tooltip_placement) end + def no_runners_for_project?(project) + project.runners.blank? && + Ci::Runner.shared.blank? + end + + private + def render_status_with_link(type, status, path, tooltip_placement) link_to ci_icon_for_status(status), path, @@ -57,9 +64,4 @@ module CiStatusHelper title: "#{type.titleize}: #{ci_label_for_status(status)}", data: { toggle: 'tooltip', placement: tooltip_placement } end - - def no_runners_for_project?(project) - project.runners.blank? && - Ci::Runner.shared.blank? - end end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 759c31b0055..71696a3923c 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -89,6 +89,14 @@ module Ci end end + def cancel_running + builds.running_or_pending.each(&:cancel) + end + + def retry_failed + builds.latest.failed.select(&:retryable?).each(&:retry) + end + def latest? return false unless ref commit = project.commit(ref) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb new file mode 100644 index 00000000000..40414b49864 --- /dev/null +++ b/app/services/ci/create_pipeline_service.rb @@ -0,0 +1,42 @@ +module Ci + class CreatePipelineService < BaseService + def execute + unless ref_names.include?(params[:ref]) + raise ArgumentError, 'Reference not found' + end + + unless commit + raise ArgumentError, 'Commit not found' + end + + unless can?(current_user, :create_pipeline, project) + raise RuntimeError, 'Insufficient permissions to create a new pipeline' + end + + Ci::Commit.transaction do + unless pipeline.config_processor + raise ArgumentError, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file' + end + + pipeline.save! + pipeline.create_builds(current_user) + end + + pipeline + end + + private + + def ref_names + @ref_names ||= project.repository.ref_names + end + + def commit + @commit ||= project.commit(params[:ref]) + end + + def pipeline + @pipeline ||= project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) + end + end +end \ No newline at end of file diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 6c3f8efc74e..8f5739cf92e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -39,12 +39,12 @@ Commits - if project_nav_tab? :builds - = nav_link(controller: %w(pipelines)) do - = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do + = nav_link(controller: :pipelines) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = icon('ship fw') %span Pipelines - %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all)) + %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count) = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 161436e5333..7384bfa1a7a 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -13,9 +13,9 @@ %strong ##{build.id} - if build.stuck? - %i.fa.fa-warning.text-warning.has-tooltip(title="Build is stuck. Check runners.") + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - if defined?(retried) && retried - %i.fa.fa-warning.has-tooltip(title="Build was retried") + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - if defined?(commit_sha) && commit_sha %td @@ -59,24 +59,24 @@ %td.duration - if build.duration %p - %i.fa.fa-clock-o + = icon("clock-o")   #{duration_in_words(build.finished_at, build.started_at)} - if build.finished_at %p - %i.fa.fa-calendar + = icon("calendar")   #{time_ago_with_tooltip(build.finished_at)} - else %td.duration - if build.duration - %i.fa.fa-clock-o + = icon("clock-o")   #{duration_in_words(build.finished_at, build.started_at)} %td.timestamp - if build.finished_at - %i.fa.fa-calendar + = icon("calendar")   %span #{time_ago_with_tooltip(build.finished_at)} @@ -89,11 +89,11 @@ .pull-right - if can?(current_user, :read_build, build) && build.artifacts? = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do - %i.fa.fa-download + = icon('download') - if can?(current_user, :update_build, build) - if build.active? = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do - %i.fa.fa-remove.cred + = icon('remove', class: 'cred') - elsif defined?(allow_retry) && allow_retry && build.retryable? = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - %i.fa.fa-refresh + = icon('refresh') diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index d5dd6c7b0aa..c6359c7c981 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -42,12 +42,12 @@ %td - if commit.started_at && commit.finished_at %p - %i.fa.fa-clock-o + = icon("clock-o")   #{duration_in_words(commit.finished_at, commit.started_at)} - if commit.finished_at %p - %i.fa.fa-calendar + = icon("calendar")   #{time_ago_with_tooltip(commit.finished_at)} @@ -63,7 +63,7 @@ - artifacts.each do |build| %li = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do - %i.fa.fa-download + = icon("download") %span #{build.name} - if can?(current_user, :update_pipeline, @project) @@ -74,4 +74,4 @@   - if commit.active? = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do - = icon("remove cred") + = icon("remove", class: "cred") diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 970cc95ad35..5b1cf71337e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -62,7 +62,7 @@ describe Project, models: true do it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_many(:commit_statuses) } - it { is_expected.to have_many(:pipelines) } + it { is_expected.to have_many(:ci_commits) } it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runners) } -- cgit v1.2.3 From 3953e5fcd7060e77405327e670f92ebd7d0ee5a0 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 May 2016 02:38:25 +0300 Subject: Update generic commit status to make it look like a build --- app/models/commit_status.rb | 4 +-- app/views/projects/ci/builds/_build.html.haml | 33 +++++++--------------- .../_generic_commit_status.html.haml | 9 ++++++ 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 0c0c3d38f94..2d09edf3ca1 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -91,12 +91,12 @@ class CommitStatus < ActiveRecord::Base end def self.stages - # We group by stage name, but order stages by their's index + # We group by stage name, but order stages by theirs' index unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') end def self.stages_status - # We execute subquery for each of the stages which calculates an Stage Status + # We execute subquery for each stage to calculate a stage status statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) statuses.inject({}) do |h, k| h[k.first] = k.last diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 7384bfa1a7a..962b9fb2595 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -55,30 +55,17 @@ - if defined?(retried) && retried %span.label.label-warning retried - - if defined?(new_duration) && new_duration - %td.duration - - if build.duration - %p - = icon("clock-o") -   - #{duration_in_words(build.finished_at, build.started_at)} - - if build.finished_at - %p - = icon("calendar") -   - #{time_ago_with_tooltip(build.finished_at)} - - else - %td.duration - - if build.duration - = icon("clock-o") -   - #{duration_in_words(build.finished_at, build.started_at)} + %td.duration + - if build.duration + = icon("clock-o") +   + #{duration_in_words(build.finished_at, build.started_at)} - %td.timestamp - - if build.finished_at - = icon("calendar") -   - %span #{time_ago_with_tooltip(build.finished_at)} + %td.timestamp + - if build.finished_at + = icon("calendar") +   + %span #{time_ago_with_tooltip(build.finished_at)} - if defined?(coverage) && coverage %td.coverage diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index f21c864e35c..8129514964a 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -12,6 +12,9 @@ - else %strong ##{generic_commit_status.id} + - if defined?(retried) && retried + = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.') + - if defined?(commit_sha) && commit_sha %td = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" @@ -42,13 +45,19 @@ - generic_commit_status.tags.each do |tag| %span.label.label-primary = tag + - if defined?(retried) && retried + %span.label.label-warning retried %td.duration - if generic_commit_status.duration + = icon("clock-o") +   #{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)} %td.timestamp - if generic_commit_status.finished_at + = icon("calendar") +   %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} - if defined?(coverage) && coverage -- cgit v1.2.3 From 86cedb082576ceca3522c33c10d367d765e02cf8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 May 2016 02:41:18 +0300 Subject: Use pipeline permissions instead of build --- app/views/projects/commit/_ci_commit.html.haml | 2 +- app/views/projects/pipelines/index.html.haml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index c8443763613..0ff679d0e3d 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .row-content-block.build-content.middle-block .pull-right - - if can?(current_user, :update_build, @project) + - if can?(current_user, :update_pipeline, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: 'btn btn-grouped btn-primary', method: :post diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 0e08753fd3b..d247ea445ea 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -29,7 +29,6 @@ = icon('plus') New - - if can?(current_user, :update_build, @project) - unless @repository.gitlab_ci_yml = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' @@ -54,7 +53,7 @@ .table-holder %table.table.builds %tbody - %th Pipeline ID + %th ID %th Commit - @pipelines.stages.each do |stage| %th -- cgit v1.2.3 From 0d43b9270677106f3aa37112da1dd0482ed40b55 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 May 2016 13:08:18 -0500 Subject: Fix CI tests --- app/controllers/projects/pipelines_controller.rb | 8 +++++--- app/models/project.rb | 2 +- app/services/ci/create_pipeline_service.rb | 2 +- app/views/projects/commit/_ci_stage.html.haml | 5 +++-- app/views/projects/pipelines/index.html.haml | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index a261d03f4d0..bdb5722c55f 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -7,8 +7,10 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - @all_pipelines = project.ci_commits - @pipelines = PipelinesFinder.new(project).execute(@all_pipelines, @scope) + all_pipelines = project.ci_commits + @pipelines_count = all_pipelines.count + @running_or_pending_count = all_pipelines.running_or_pending.count + @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope) @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30) end @@ -22,7 +24,7 @@ class Projects::PipelinesController < Projects::ApplicationController rescue ArgumentError => e @error = e.message render 'new' - rescue => e + rescue @error = 'Undefined error' render 'new' end diff --git a/app/models/project.rb b/app/models/project.rb index dfd1e54ecf7..82489235a3f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -354,7 +354,7 @@ class Project < ActiveRecord::Base join_body = "INNER JOIN ( SELECT project_id, COUNT(*) AS amount FROM notes - WHERE created_at >= #{sanitize(since)} + WHERE created_at >= #{sanitize(since)}project.ci_commits GROUP BY project_id ) join_note_counts ON projects.id = join_note_counts.project_id" diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 40414b49864..223514968fc 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -39,4 +39,4 @@ module Ci @pipeline ||= project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) end end -end \ No newline at end of file +end diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml index f9f2757dc34..bdadf2944c4 100644 --- a/app/views/projects/commit/_ci_stage.html.haml +++ b/app/views/projects/commit/_ci_stage.html.haml @@ -6,8 +6,9 @@ - status = latest.status %span{class: "ci-status-link ci-status-icon-#{status}"} = ci_icon_for_status(status) -   - = stage.titleize.pluralize + - if stage +   + = stage.titleize.pluralize = render latest.ordered, coverage: @project.build_coverage_enabled?, tage: false, ref: false, allow_retry: true = render retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true %tr diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index d247ea445ea..574941ed5f2 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -7,13 +7,13 @@ = link_to project_pipelines_path(@project) do All %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_pipelines.count) + = number_with_delimiter(@pipelines_count) %li{class: ('active' if @scope == 'running')} = link_to project_pipelines_path(@project, scope: :running) do Running %span.badge.js-running-count - = number_with_delimiter(@all_pipelines.running_or_pending.count) + = number_with_delimiter(@running_or_pending_count) %li{class: ('active' if @scope == 'branches')} = link_to project_pipelines_path(@project, scope: :branches) do -- cgit v1.2.3 From 886bfb0a4ddeb546b9e14f7671273466dc5708ba Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 May 2016 08:36:44 -0500 Subject: Initial specs for pipelines --- spec/features/pipelines_spec.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 spec/features/pipelines_spec.rb diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb new file mode 100644 index 00000000000..1e0c58be979 --- /dev/null +++ b/spec/features/pipelines_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe "Pipelines" do + include GitlabRoutingHelper + + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + before { login_as(user) } + + describe "GET /:project/pipelines" do + + end + + describe "GET /:project/pipelines/:id" do + let(:pipeline) { create(:ci_commit, project: project, ref: 'master') } + + before do + create(:ci_build, :success, commit: pipeline) + end + + before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } + + it { expect(page).to()} + end +end -- cgit v1.2.3 From c1bc5c58a2861af25f4f03e0a757dceae4b67cda Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 May 2016 13:17:15 -0500 Subject: Added pipelines spec --- app/views/projects/ci/commits/_commit.html.haml | 2 +- spec/features/pipelines_spec.rb | 128 +++++++++++++++++++++++- 2 files changed, 124 insertions(+), 6 deletions(-) diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index c6359c7c981..90ac41666d0 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -55,7 +55,7 @@ .controls.hidden-xs.pull-right - artifacts = commit.builds.latest.select { |b| b.artifacts? } - if artifacts.present? - .dropdown.inline + .dropdown.inline.build-artifacts %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} = icon('download') %b.caret diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb index 1e0c58be979..0e654c3e40f 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -5,21 +5,139 @@ describe "Pipelines" do let(:project) { create(:empty_project) } let(:user) { create(:user) } - before { login_as(user) } - describe "GET /:project/pipelines" do + before do + login_as(user) + project.team << [user, :developer] + end + + describe 'GET /:project/pipelines' do + let!(:pipeline) { create(:ci_commit, project: project, ref: 'master', status: 'running') } + + [:all, :running, :branches].each do |scope| + context "displaying #{scope}" do + let(:project) { create(:project) } + + before { visit namespace_project_pipelines_path(project.namespace, project, scope: scope) } + + it { expect(page).to have_content(pipeline.short_sha) } + end + end + + context 'cancelable pipeline' do + let!(:running) { create(:ci_build, :running, commit: pipeline, stage: 'test', commands: 'test') } + + before { visit namespace_project_pipelines_path(project.namespace, project) } + + it { expect(page).to have_link('Cancel') } + it { expect(page).to have_selector('.ci-running') } + + context 'when canceling' do + before { click_link('Cancel') } + + it { expect(page).to_not have_link('Cancel') } + it { expect(page).to have_selector('.ci-canceled') } + end + end + + context 'retryable pipelines' do + let!(:failed) { create(:ci_build, :failed, commit: pipeline, stage: 'test', commands: 'test') } + + before { visit namespace_project_pipelines_path(project.namespace, project) } + + it { expect(page).to have_link('Retry') } + it { expect(page).to have_selector('.ci-failed') } + + context 'when retrying' do + before { click_link('Retry') } + + it { expect(page).to_not have_link('Retry') } + it { expect(page).to have_selector('.ci-running') } + end + end + + context 'downloadable pipelines' do + before { visit namespace_project_pipelines_path(project.namespace, project) } + + context 'with artifacts' do + let!(:with_artifacts) { create(:ci_build, :success, :artifacts, commit: pipeline, name: 'rspec tests', stage: 'test') } + + it { expect(page).to have_selector('.build-artifacts') } + it { expect(page).to have_link(with_artifacts.name) } + end + context 'without artifacts' do + let!(:without_artifacts) { create(:ci_build, :success, commit: pipeline, name: 'rspec', stage: 'test') } + + it { expect(page).to_not have_selector('.build-artifacts') } + end + end end - describe "GET /:project/pipelines/:id" do + describe 'GET /:project/pipelines/:id' do let(:pipeline) { create(:ci_commit, project: project, ref: 'master') } before do - create(:ci_build, :success, commit: pipeline) + @success = create(:ci_build, :success, commit: pipeline, stage: 'build') + @failed = create(:ci_build, :failed, commit: pipeline, stage: 'test', commands: 'test') + @running = create(:ci_build, :running, commit: pipeline, stage: 'deploy') + @external = create(:generic_commit_status, :success, commit: pipeline, stage: 'external') end before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { expect(page).to()} + it 'showing a list of builds' do + expect(page).to have_content('Tests') + expect(page).to have_content(@success.id) + expect(page).to have_content('Deploy') + expect(page).to have_content(@failed.id) + expect(page).to have_content(@running.id) + expect(page).to have_content(@external.id) + expect(page).to have_content('Retry failed') + expect(page).to have_content('Cancel running') + end + + context 'retrying builds' do + it { expect(page).to_not have_content('retried') } + + context 'when retrying' do + before { click_on 'Retry failed' } + + it { expect(page).to_not have_content('Retry failed') } + it { expect(page).to have_content('retried') } + end + end + + context 'canceling builds' do + it { expect(page).to_not have_selector('.ci-canceled') } + + context 'when canceling' do + before { click_on 'Cancel running' } + + it { expect(page).to_not have_content('Cancel running') } + it { expect(page).to have_selector('.ci-canceled') } + end + end + end + + describe 'POST /:project/pipelines' do + let(:project) { create(:project) } + + before { visit new_namespace_project_pipeline_path(project.namespace, project) } + + context 'for valid commit' do + before { fill_in('Create for', with: 'master') } + + it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) } + end + + context 'for invalid commit' do + before do + fill_in('Create for', with: 'invalid reference') + click_on 'Create pipeline' + end + + it { expect(page).to have_content('Reference not found') } + end end end -- cgit v1.2.3 From 6d19e13df62376916e024ff44939bf2a8f5b671b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 May 2016 17:11:57 -0500 Subject: Fix specs --- app/models/commit_status.rb | 11 +++-------- app/models/project.rb | 2 +- app/views/projects/ci/commits/_commit.html.haml | 3 +-- spec/features/pipelines_spec.rb | 21 ++++++++++++--------- spec/models/commit_status_spec.rb | 12 ------------ 5 files changed, 17 insertions(+), 32 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 2d09edf3ca1..c7451ea0a86 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -92,16 +92,11 @@ class CommitStatus < ActiveRecord::Base def self.stages # We group by stage name, but order stages by theirs' index - unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') + unscoped.where(id: all).group('stage').order('max(stage_idx)', 'stage').pluck('stage') end - def self.stages_status - # We execute subquery for each stage to calculate a stage status - statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) - statuses.inject({}) do |h, k| - h[k.first] = k.last - h - end + def self.status_for_stage(stage) + where(stage: stage).status end def ignored? diff --git a/app/models/project.rb b/app/models/project.rb index 82489235a3f..dfd1e54ecf7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -354,7 +354,7 @@ class Project < ActiveRecord::Base join_body = "INNER JOIN ( SELECT project_id, COUNT(*) AS amount FROM notes - WHERE created_at >= #{sanitize(since)}project.ci_commits + WHERE created_at >= #{sanitize(since)} GROUP BY project_id ) join_note_counts ON projects.id = join_note_counts.project_id" diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 90ac41666d0..7f9a3417836 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -31,10 +31,9 @@ Cant find HEAD commit for this branch - - stages_status = commit.statuses.stages_status - stages.each do |stage| %td - - if status = stages_status[stage] + - if status = commit.statuses.status_for_stage(stage) - tooltip = "#{stage.titleize}: #{status}" %span.has-tooltip(title="#{tooltip}"){class: "ci-status-icon-#{status}"} = ci_icon_for_status(status) diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb index 0e654c3e40f..1df516eafd5 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -52,15 +52,15 @@ describe "Pipelines" do before { click_link('Retry') } it { expect(page).to_not have_link('Retry') } - it { expect(page).to have_selector('.ci-running') } + it { expect(page).to have_selector('.ci-pending') } end end context 'downloadable pipelines' do - before { visit namespace_project_pipelines_path(project.namespace, project) } - context 'with artifacts' do - let!(:with_artifacts) { create(:ci_build, :success, :artifacts, commit: pipeline, name: 'rspec tests', stage: 'test') } + let!(:with_artifacts) { create(:ci_build, :artifacts, :success, commit: pipeline, name: 'rspec tests', stage: 'test') } + + before { visit namespace_project_pipelines_path(project.namespace, project) } it { expect(page).to have_selector('.build-artifacts') } it { expect(page).to have_link(with_artifacts.name) } @@ -78,10 +78,10 @@ describe "Pipelines" do let(:pipeline) { create(:ci_commit, project: project, ref: 'master') } before do - @success = create(:ci_build, :success, commit: pipeline, stage: 'build') - @failed = create(:ci_build, :failed, commit: pipeline, stage: 'test', commands: 'test') - @running = create(:ci_build, :running, commit: pipeline, stage: 'deploy') - @external = create(:generic_commit_status, :success, commit: pipeline, stage: 'external') + @success = create(:ci_build, :success, commit: pipeline, stage: 'build', name: 'build') + @failed = create(:ci_build, :failed, commit: pipeline, stage: 'test', name: 'test', commands: 'test') + @running = create(:ci_build, :running, commit: pipeline, stage: 'deploy', name: 'deploy') + @external = create(:generic_commit_status, status: 'success', commit: pipeline, name: 'jenkins', stage: 'external') end before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } @@ -126,7 +126,10 @@ describe "Pipelines" do before { visit new_namespace_project_pipeline_path(project.namespace, project) } context 'for valid commit' do - before { fill_in('Create for', with: 'master') } + before do + fill_in('Create for', with: 'master') + stub_ci_commit_to_return_yaml_file + end it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) } end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 971e6750375..eb3715f00d4 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -219,17 +219,5 @@ describe CommitStatus, models: true do is_expected.to eq(%w(build test deploy)) end end - - context 'stages with statuses' do - subject { CommitStatus.where(commit: commit).stages_status } - - it 'return list of stages with statuses' do - is_expected.to eq({ - 'build' => 'failed', - 'test' => 'success', - 'deploy' => 'running' - }) - end - end end end -- cgit v1.2.3 From a6b8d36ae933c3f21e7ba2c17864c6d8bf626d25 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 09:46:38 -0500 Subject: Fix specs --- app/controllers/projects/pipelines_controller.rb | 3 --- app/models/commit_status.rb | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index bdb5722c55f..f3f5338003a 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -31,9 +31,6 @@ class Projects::PipelinesController < Projects::ApplicationController end def show - respond_to do |format| - format.html - end end def retry diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 521456bedee..213cd20e8d8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -55,7 +55,7 @@ class CommitStatus < ActiveRecord::Base def self.stages # We group by stage name, but order stages by theirs' index - unscoped.where(id: all).group('stage').order('max(stage_idx)', 'stage').pluck('stage') + unscoped.where(id: all.ids).group('stage').order('max(stage_idx)', 'stage').pluck('stage') end def self.status_for_stage(stage) -- cgit v1.2.3 From 8c93b6051fdc7bb59af2e6bf2f5b3a1817a73ceb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 14:44:35 -0500 Subject: Revert `stages` change --- app/models/commit_status.rb | 11 ++++++++--- app/views/projects/ci/commits/_commit.html.haml | 3 ++- spec/models/commit_status_spec.rb | 12 ++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 213cd20e8d8..6fef1c038e9 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -55,11 +55,16 @@ class CommitStatus < ActiveRecord::Base def self.stages # We group by stage name, but order stages by theirs' index - unscoped.where(id: all.ids).group('stage').order('max(stage_idx)', 'stage').pluck('stage') + unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') end - def self.status_for_stage(stage) - where(stage: stage).status + def self.stages_status + # We execute subquery for each stage to calculate a stage status + statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) + statuses.inject({}) do |h, k| + h[k.first] = k.last + h + end end def ignored? diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 7f9a3417836..90ac41666d0 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -31,9 +31,10 @@ Cant find HEAD commit for this branch + - stages_status = commit.statuses.stages_status - stages.each do |stage| %td - - if status = commit.statuses.status_for_stage(stage) + - if status = stages_status[stage] - tooltip = "#{stage.titleize}: #{status}" %span.has-tooltip(title="#{tooltip}"){class: "ci-status-icon-#{status}"} = ci_icon_for_status(status) diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index ea60894c419..434e58cfd06 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -185,5 +185,17 @@ describe CommitStatus, models: true do is_expected.to eq(%w(build test deploy)) end end + + context 'stages with statuses' do + subject { CommitStatus.where(commit: commit).stages_status } + + it 'return list of stages with statuses' do + is_expected.to eq({ + 'build' => 'failed', + 'test' => 'success', + 'deploy' => 'running' + }) + end + end end end -- cgit v1.2.3 From 865e8853369b542569693ec23a9b533481228829 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 14:47:47 -0500 Subject: Fix specs for MySQL --- app/models/ci/commit.rb | 5 ++--- app/views/projects/pipelines/index.html.haml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 91c5b4c1cae..6675a3f5d53 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -8,8 +8,6 @@ module Ci has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' - delegate :stages, to: :statuses - validates_presence_of :sha validates_presence_of :status validate :valid_commit_sha @@ -22,7 +20,8 @@ module Ci end def self.stages - CommitStatus.where(commit: all).stages + # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries + CommitStatus.where(commit: pluck(:id)).stages end def project_id diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 574941ed5f2..af55ef42a6e 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -55,7 +55,7 @@ %tbody %th ID %th Commit - - @pipelines.stages.each do |stage| + - stages.each do |stage| %th %span.has-tooltip(title="#{stage.titleize}") = truncate(stage.titleize.pluralize, length: 8) -- cgit v1.2.3 From 7d907acc3805a692fd9f6f29e529c5b5fe15ec11 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 14:51:57 -0500 Subject: Fix spinach tests --- features/steps/dashboard/dashboard.rb | 2 +- features/steps/project/merge_requests.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index b5980b35102..80ed4c6d64c 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build skipped" + expect(page).to have_link "Commit: skipped" end step 'I should see last push widget' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 3b1a00f628a..b79d19f1c58 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -525,7 +525,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see merge request "Bug NS-05" with CI status' do page.within ".mr-list" do - expect(page).to have_link "Build pending" + expect(page).to have_link "Pipeline: pending" end end -- cgit v1.2.3 From 2566c89a3232d6d4d5aa5d77821819e86424548d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 15:55:00 -0500 Subject: Remove testing delegate --- app/controllers/projects/pipelines_controller.rb | 4 ++-- spec/models/ci/commit_spec.rb | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index f3f5338003a..78b85ea9a71 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -22,10 +22,10 @@ class Projects::PipelinesController < Projects::ApplicationController pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute redirect_to namespace_project_pipeline_path(project.namespace, project, pipeline) rescue ArgumentError => e - @error = e.message + flash[:alert] = e.message render 'new' rescue - @error = 'Undefined error' + flash[:alert] = 'Undefined error' render 'new' end end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index dc071ad1c90..1b5940ad5a8 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -10,7 +10,6 @@ describe Ci::Commit, models: true do it { is_expected.to have_many(:builds) } it { is_expected.to validate_presence_of :sha } it { is_expected.to validate_presence_of :status } - it { is_expected.to delegate_method(:stages).to(:statuses) } it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_email } -- cgit v1.2.3 From 003526e2ee408bc6be3596436288213cc57d1bcd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 19:47:16 -0500 Subject: Add method new_pipeline --- app/services/ci/create_pipeline_service.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 223514968fc..e13f4fce13d 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -13,6 +13,8 @@ module Ci raise RuntimeError, 'Insufficient permissions to create a new pipeline' end + pipeline = new_pipeline + Ci::Commit.transaction do unless pipeline.config_processor raise ArgumentError, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file' @@ -27,6 +29,10 @@ module Ci private + def new_pipeline + project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) + end + def ref_names @ref_names ||= project.repository.ref_names end @@ -34,9 +40,5 @@ module Ci def commit @commit ||= project.commit(params[:ref]) end - - def pipeline - @pipeline ||= project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) - end end end -- cgit v1.2.3 From bf4dc75801176c95b49763ff6ab5e03305c91f73 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 16:34:42 -0500 Subject: Improve the pipelines design --- app/assets/stylesheets/pages/pipelines.scss | 5 +++++ app/controllers/projects/pipelines_controller.rb | 2 +- app/models/commit_status.rb | 1 + app/views/projects/ci/commits/_commit.html.haml | 6 +++--- app/views/projects/commit/_ci_stage.html.haml | 8 +++----- app/views/projects/commit/_commit_box.html.haml | 4 ++-- app/views/projects/pipelines/index.html.haml | 8 ++++---- app/views/projects/pipelines/new.html.haml | 4 ++-- 8 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 app/assets/stylesheets/pages/pipelines.scss diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss new file mode 100644 index 00000000000..7d90a4bebc5 --- /dev/null +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -0,0 +1,5 @@ +.pipeline-stage { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 78b85ea9a71..19071cc9e43 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -25,7 +25,7 @@ class Projects::PipelinesController < Projects::ApplicationController flash[:alert] = e.message render 'new' rescue - flash[:alert] = 'Undefined error' + flash[:alert] = 'The pipeline could not be created. Please try again.' render 'new' end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 6fef1c038e9..1548a51e942 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -14,6 +14,7 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } + scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 90ac41666d0..13162b41f9b 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -19,7 +19,7 @@ - if commit.triggered? %span.label.label-primary triggered - if commit.yaml_errors.present? - %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid + %span.label.label-danger.has-tooltip{ title: "#{commit.yaml_errors}" } yaml invalid - if commit.builds.any?(&:stuck?) %span.label.label-warning stuck @@ -36,7 +36,7 @@ %td - if status = stages_status[stage] - tooltip = "#{stage.titleize}: #{status}" - %span.has-tooltip(title="#{tooltip}"){class: "ci-status-icon-#{status}"} + %span.has-tooltip{ title: "#{tooltip}", class: "ci-status-icon-#{status}" } = ci_icon_for_status(status) %td @@ -74,4 +74,4 @@   - if commit.active? = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do - = icon("remove", class: "cred") + = icon("remove") diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml index bdadf2944c4..aaa318e1eb3 100644 --- a/app/views/projects/commit/_ci_stage.html.haml +++ b/app/views/projects/commit/_ci_stage.html.haml @@ -1,16 +1,14 @@ -- latest = statuses.latest -- retried = statuses.where.not(id: latest) %tr %th{colspan: 10} %strong - - status = latest.status + - status = statuses.latest.status %span{class: "ci-status-link ci-status-icon-#{status}"} = ci_icon_for_status(status) - if stage   = stage.titleize.pluralize - = render latest.ordered, coverage: @project.build_coverage_enabled?, tage: false, ref: false, allow_retry: true - = render retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true + = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true + = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true %tr %td{colspan: 10}   diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 77fb0bc7357..30ec47cc8e3 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -27,14 +27,14 @@ .pull-right = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline), class: "ci-status ci-#{pipeline.status}" do = ci_icon_for_status(pipeline.status) - pipeline: + Pipeline: = ci_label_for_status(pipeline.status) - elsif @commit.status .pull-right = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do = ci_icon_for_status(@commit.status) - build: + Build: = ci_label_for_status(@commit.status) %span.light Authored by diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index af55ef42a6e..9d5b6d367c9 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -27,7 +27,7 @@ - if can? current_user, :create_pipeline, @project = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do = icon('plus') - New + New pipeline - unless @repository.gitlab_ci_yml = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' @@ -36,7 +36,7 @@ = icon('wrench') %span CI Lint -.gray-content-block +.row-content-block - if @scope == 'running' Running pipelines for this project - elsif @scope.nil? @@ -57,8 +57,8 @@ %th Commit - stages.each do |stage| %th - %span.has-tooltip(title="#{stage.titleize}") - = truncate(stage.titleize.pluralize, length: 8) + %span.pipeline-stage.has-tooltip{ title: "#{stage.titleize}" } + = stage.titleize.pluralize %th %th = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 39b1571b9cf..534a495dd85 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -9,7 +9,7 @@ New Pipeline %hr -= form_tag namespace_project_pipelines_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do += form_tag namespace_project_pipelines_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" do .form-group = label_tag :ref, 'Create for', class: 'control-label' .col-sm-10 @@ -22,4 +22,4 @@ :javascript var availableRefs = #{@project.repository.ref_names.to_json}; - new NewBranchForm($('.js-create-branch-form'), availableRefs) + new NewBranchForm($('.js-new-pipeline-form'), availableRefs) -- cgit v1.2.3 From 7657d202160e04e6b5e5412d949977bc48cdacea Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:09:04 -0500 Subject: Improve design of commit_box with status of builds and pipelines --- app/views/projects/commit/_builds.html.haml | 2 +- app/views/projects/commit/_ci_commit.html.haml | 29 ++++++++++---------- app/views/projects/commit/_commit_box.html.haml | 35 +++++++++++++++---------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index 5c9a319edeb..7f7a15aa214 100644 --- a/app/views/projects/commit/_builds.html.haml +++ b/app/views/projects/commit/_builds.html.haml @@ -1,2 +1,2 @@ - @ci_commits.each do |ci_commit| - = render "ci_commit", ci_commit: ci_commit + = render "ci_commit", ci_commit: ci_commit, pipeline_details: true diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 0ff679d0e3d..df92a38db31 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -7,20 +7,21 @@ - if ci_commit.builds.running_or_pending.any? = link_to "Cancel running", cancel_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - .oneline - Pipeline - = link_to "##{ci_commit.id}", namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: "monospace" - with - = pluralize ci_commit.statuses.count(:id), "build" - - if ci_commit.ref - for - = link_to ci_commit.ref, namespace_project_commits_path(@project.namespace, @project, ci_commit.ref), class: "monospace" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - - if ci_commit.duration - in - = time_interval_in_words ci_commit.duration + - if defined?(pipeline_details) && pipeline_details + .oneline + Pipeline + = link_to "##{ci_commit.id}", namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: "monospace" + with + = pluralize ci_commit.statuses.count(:id), "build" + - if ci_commit.ref + for + = link_to ci_commit.ref, namespace_project_commits_path(@project.namespace, @project, ci_commit.ref), class: "monospace" + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" + - if ci_commit.duration + in + = time_interval_in_words ci_commit.duration - if ci_commit.yaml_errors.present? .bs-callout.bs-callout-danger diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 30ec47cc8e3..611272b8a74 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -23,20 +23,6 @@ %p .commit-info-row - - if defined?(pipeline) && pipeline - .pull-right - = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline), class: "ci-status ci-#{pipeline.status}" do - = ci_icon_for_status(pipeline.status) - Pipeline: - = ci_label_for_status(pipeline.status) - - - elsif @commit.status - .pull-right - = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do - = ci_icon_for_status(@commit.status) - Build: - = ci_label_for_status(@commit.status) - %span.light Authored by %strong = commit_author_link(@commit, avatar: true, size: 24) @@ -60,6 +46,27 @@ %span.commit-info.branches %i.fa.fa-spinner.fa-spin +- if defined?(pipeline) && pipeline + .commit-info-row + Pipeline + = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline), class: "ci-status-link ci-status-icon-#{pipeline.status}" do + = ci_icon_for_status(pipeline.status) + = ci_label_for_status(pipeline.status) + - if pipeline.duration + in + = time_interval_in_words 3600 + +- elsif @commit.status + .commit-info-row + Builds for + = pluralize(@commit.ci_commits.count, 'pipeline') + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do + = ci_icon_for_status(@commit.status) + = ci_label_for_status(@commit.status) + - if @commit.ci_commits.duration + in + = time_interval_in_words @commit.ci_commits.duration + .commit-box.content-block %h3.commit-title = markdown escape_once(@commit.title), pipeline: :single_line -- cgit v1.2.3 From bfc6799c9367a0bb249e1d4eeece5485e16ec6a5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 18:13:44 -0500 Subject: Fix spinach tests --- features/steps/project/commits/commits.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index 93c37bf507f..f33f37be951 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -173,7 +173,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see commit ci info' do - expect(page).to have_content "build: pending" + expect(page).to have_content "Builds for 1 pipeline pending" end step 'I click status link' do @@ -181,7 +181,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see builds list' do - expect(page).to have_content "build: pending" + expect(page).to have_content "Builds for 1 pipeline pending" expect(page).to have_content "1 build" end -- cgit v1.2.3 From 5194a9d9f62d5e9702f3a60e42079ce7bf81ddd4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 23:29:11 -0500 Subject: Improve the pipeline box --- app/views/projects/commit/_commit_box.html.haml | 12 +------- app/views/projects/pipelines/_info.html.haml | 37 +++++++++++++++++++++++++ app/views/projects/pipelines/show.html.haml | 2 +- 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 app/views/projects/pipelines/_info.html.haml diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 611272b8a74..028564c9305 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -46,17 +46,7 @@ %span.commit-info.branches %i.fa.fa-spinner.fa-spin -- if defined?(pipeline) && pipeline - .commit-info-row - Pipeline - = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline), class: "ci-status-link ci-status-icon-#{pipeline.status}" do - = ci_icon_for_status(pipeline.status) - = ci_label_for_status(pipeline.status) - - if pipeline.duration - in - = time_interval_in_words 3600 - -- elsif @commit.status +- if @commit.status .commit-info-row Builds for = pluralize(@commit.ci_commits.count, 'pipeline') diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml new file mode 100644 index 00000000000..8289aefcde7 --- /dev/null +++ b/app/views/projects/pipelines/_info.html.haml @@ -0,0 +1,37 @@ +%p +.commit-info-row + Pipeline + = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @pipeline.id), class: "monospace" + with + = pluralize @pipeline.statuses.count(:id), "build" + - if @pipeline.ref + for + = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace" + - if @pipeline.duration + in + = time_interval_in_words @pipeline.duration + + .pull-right + = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do + = ci_icon_for_status(@pipeline.status) + = ci_label_for_status(@pipeline.status) + +- if @commit + .commit-info-row + %span.light Authored by + %strong + = commit_author_link(@commit, avatar: true, size: 24) + #{time_ago_with_tooltip(@commit.authored_date)} + +.commit-info-row + %span.light Commit + = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace" + = clipboard_button(clipboard_text: @pipeline.sha) + +- if @commit + .commit-box.content-block + %h3.commit-title + = markdown escape_once(@commit.title), pipeline: :single_line + - if @commit.description.present? + %pre.commit-description + = preserve(markdown(escape_once(@commit.description), pipeline: :single_line)) diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 8a2e14d8d87..b082d4d5da8 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -3,7 +3,7 @@ = render "header_title" .prepend-top-default - if @commit - = render "projects/commit/commit_box", pipeline: @pipeline + = render "projects/pipelines/info" %div.block-connector = render "projects/commit/ci_commit", ci_commit: @pipeline -- cgit v1.2.3 From 379dc6fbccc84858a392226111a0abadb54f0c04 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 23:34:42 -0500 Subject: Wrap text --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 7d90a4bebc5..546176b65e4 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -1,5 +1,4 @@ .pipeline-stage { - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -- cgit v1.2.3 From 6b834f2cbcfc26fe3123b6682ed7e20618e31d1b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 8 Mar 2016 18:22:50 +0000 Subject: Create a todo on failing MR build When a build fails for a commit, create a todo for the author of the merge request that commit is the HEAD of. If the commit isn't the HEAD commit of any MR, don't do anything. If there already is a todo for that user and MR, don't do anything. Current limitations: - This isn't configurable by project. - The author of a merge request might not be the person who pushed the breaking commit. --- app/finders/todos_finder.rb | 2 +- app/helpers/todos_helper.rb | 8 ++- app/models/ci/build.rb | 1 + app/models/commit_status.rb | 4 ++ app/models/todo.rb | 9 ++- .../add_todo_when_build_fails_service.rb | 17 +++++ app/services/merge_requests/base_service.rb | 25 +++++++ .../merge_when_build_succeeds_service.rb | 23 +----- app/services/merge_requests/refresh_service.rb | 7 ++ app/services/todo_service.rb | 30 ++++++++ app/views/dashboard/todos/_todo.html.haml | 12 ++-- spec/factories/todos.rb | 4 ++ .../add_todo_when_build_fails_service_spec.rb | 81 ++++++++++++++++++++++ .../merge_requests/refresh_service_spec.rb | 28 ++++++++ spec/services/todo_service_spec.rb | 19 +++++ 15 files changed, 237 insertions(+), 33 deletions(-) create mode 100644 app/services/merge_requests/add_todo_when_build_fails_service.rb create mode 100644 spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 3ba27c40504..4bd46a76087 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -36,7 +36,7 @@ class TodosFinder private def action_id? - action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i) + action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i) end def action_id diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 2f066682180..81b9b5d7052 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -11,6 +11,7 @@ module TodosHelper case todo.action when Todo::ASSIGNED then 'assigned you' when Todo::MENTIONED then 'mentioned you on' + when Todo::BUILD_FAILED then 'The build failed for your' end end @@ -28,8 +29,11 @@ module TodosHelper namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, todo.target, anchor: anchor) else - polymorphic_path([todo.project.namespace.becomes(Namespace), - todo.project, todo.target], anchor: anchor) + path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target] + + path.unshift(:builds) if todo.build_failed? + + polymorphic_path(path, anchor: anchor) end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 92327bdb08d..50190101f2b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -53,6 +53,7 @@ module Ci new_build.stage_idx = build.stage_idx new_build.trigger_request = build.trigger_request new_build.save + MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) new_build end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index cacbc13b391..8181416f7d5 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -45,6 +45,10 @@ class CommitStatus < ActiveRecord::Base after_transition [:pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end + + after_transition any => :failed do |commit_status| + MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.commit.project, nil).execute(commit_status) + end end delegate :sha, :short_sha, to: :commit diff --git a/app/models/todo.rb b/app/models/todo.rb index f8b59fe4126..3a091373329 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,6 +1,7 @@ class Todo < ActiveRecord::Base - ASSIGNED = 1 - MENTIONED = 2 + ASSIGNED = 1 + MENTIONED = 2 + BUILD_FAILED = 3 belongs_to :author, class_name: "User" belongs_to :note @@ -28,6 +29,10 @@ class Todo < ActiveRecord::Base state :done end + def build_failed? + action == BUILD_FAILED + end + def body if note.present? note.note diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb new file mode 100644 index 00000000000..566049525cb --- /dev/null +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -0,0 +1,17 @@ +module MergeRequests + class AddTodoWhenBuildFailsService < MergeRequests::BaseService + # Adds a todo to the parent merge_request when a CI build fails + def execute(commit_status) + each_merge_request(commit_status) do |merge_request| + todo_service.merge_request_build_failed(merge_request) + end + end + + # Closes any pending build failed todos for the parent MRs when a build is retried + def close(commit_status) + each_merge_request(commit_status) do |merge_request| + todo_service.merge_request_build_retried(merge_request) + end + end + end +end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index e6837a18696..9d7fca6882d 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -38,5 +38,30 @@ module MergeRequests def filter_params super(:merge_request) end + + def merge_request_from(commit_status) + branches = commit_status.ref + + # This is for ref-less builds + branches ||= @project.repository.branch_names_contains(commit_status.sha) + + return [] if branches.blank? + + merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a + merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a + + merge_requests.uniq.select(&:source_project) + end + + def each_merge_request(commit_status) + merge_request_from(commit_status).each do |merge_request| + ci_commit = merge_request.ci_commit + + next unless ci_commit + next unless ci_commit.sha == commit_status.sha + + yield merge_request, ci_commit + end + end end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index d6af12f9739..8fd6a4ea1f6 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -20,15 +20,9 @@ module MergeRequests # Triggers the automatic merge of merge_request once the build succeeds def trigger(commit_status) - merge_requests = merge_request_from(commit_status) - - merge_requests.each do |merge_request| + each_merge_request(commit_status) do |merge_request, ci_commit| next unless merge_request.merge_when_build_succeeds? next unless merge_request.mergeable? - - ci_commit = merge_request.ci_commit - next unless ci_commit - next unless ci_commit.sha == commit_status.sha next unless ci_commit.success? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) @@ -47,20 +41,5 @@ module MergeRequests end end - private - - def merge_request_from(commit_status) - branches = commit_status.ref - - # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(commit_status.sha) - - return [] if branches.blank? - - merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a - merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a - - merge_requests.uniq.select(&:source_project) - end end end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 8b3d56c2b4c..fe0579744b4 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -12,6 +12,7 @@ module MergeRequests close_merge_requests reload_merge_requests reset_merge_when_build_succeeds + mark_pending_todos_done # Leave a system note if a branch was deleted/added if branch_added? || branch_removed? @@ -80,6 +81,12 @@ module MergeRequests merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds) end + def mark_pending_todos_done + merge_requests_for_source_branch.each do |merge_request| + todo_service.merge_request_push(merge_request, @current_user) + end + end + def find_new_commits if branch_added? @commits = [] diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 42c5bca90fd..4bf4e144727 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -80,6 +80,30 @@ class TodoService mark_pending_todos_as_done(merge_request, current_user) end + # When a build fails on the HEAD of a merge request we should: + # + # * create a todo for that user to fix it + # + def merge_request_build_failed(merge_request) + create_build_failed_todo(merge_request) + end + + # When a new commit is pushed to a merge request we should: + # + # * mark all pending todos related to the merge request for that user as done + # + def merge_request_push(merge_request, current_user) + mark_pending_todos_as_done(merge_request, current_user) + end + + # When a build is retried to a merge request we should: + # + # * mark all pending todos related to the merge request for the author as done + # + def merge_request_build_retried(merge_request) + mark_pending_todos_as_done(merge_request, merge_request.author) + end + # When create a note we should: # # * mark all pending todos related to the noteable for the note author as done @@ -145,6 +169,12 @@ class TodoService create_todos(mentioned_users, attributes) end + def create_build_failed_todo(merge_request) + author = merge_request.author + attributes = attributes_for_todo(merge_request.project, merge_request, author, Todo::BUILD_FAILED) + create_todos(author, attributes) + end + def attributes_for_target(target) attributes = { project_id: target.project.id, diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index aa0aff86d4d..539f1dc6036 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -1,13 +1,13 @@ %li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} } .todo-item.todo-block = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:'' - .todo-title.title - %span.author-name - - if todo.author - = link_to_author(todo) - - else - (removed) + - unless todo.build_failed? + %span.author-name + - if todo.author + = link_to_author(todo) + - else + (removed) %span.todo-label = todo_action_name(todo) - if todo.target diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index e3681ae93a5..f426e27afed 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -18,5 +18,9 @@ FactoryGirl.define do commit_id RepoHelpers.sample_commit.id target_type "Commit" end + + trait :build_failed do + action { Todo::BUILD_FAILED } + end end end diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb new file mode 100644 index 00000000000..f70716c9d19 --- /dev/null +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +# Write specs in this file. +describe MergeRequests::AddTodoWhenBuildFailsService do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { create(:project) } + let(:sha) { '1234567890abcdef1234567890abcdef12345678' } + let(:ci_commit) { create(:ci_commit_with_one_job, ref: merge_request.source_branch, project: project, sha: sha) } + let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user, commit_message: 'Awesome message') } + let(:todo_service) { TodoService.new } + + let(:merge_request) do + create(:merge_request, merge_user: user, source_branch: 'master', + target_branch: 'feature', source_project: project, target_project: project, + state: 'opened') + end + + before do + allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit) + allow(service).to receive(:todo_service).and_return(todo_service) + end + + describe '#execute' do + context 'commit status with ref' do + let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, commit: ci_commit) } + + it 'notifies the todo service' do + expect(todo_service).to receive(:merge_request_build_failed).with(merge_request) + service.execute(commit_status) + end + end + + context 'commit status with non-HEAD ref' do + let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch) } + + it 'does not notify the todo service' do + expect(todo_service).not_to receive(:merge_request_build_failed) + service.execute(commit_status) + end + end + + context 'commit status without ref' do + let(:commit_status) { create(:generic_commit_status) } + + it 'does not notify the todo service' do + expect(todo_service).not_to receive(:merge_request_build_failed) + service.execute(commit_status) + end + end + end + + describe '#close' do + context 'commit status with ref' do + let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, commit: ci_commit) } + + it 'notifies the todo service' do + expect(todo_service).to receive(:merge_request_build_retried).with(merge_request) + service.close(commit_status) + end + end + + context 'commit status with non-HEAD ref' do + let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch) } + + it 'does not notify the todo service' do + expect(todo_service).not_to receive(:merge_request_build_retried) + service.close(commit_status) + end + end + + context 'commit status without ref' do + let(:commit_status) { create(:generic_commit_status) } + + it 'does not notify the todo service' do + expect(todo_service).not_to receive(:merge_request_build_retried) + service.close(commit_status) + end + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index fea8182bd30..31b93850c7c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -27,6 +27,20 @@ describe MergeRequests::RefreshService, services: true do target_branch: 'feature', target_project: @project) + @build_failed_todo = create(:todo, + :build_failed, + user: @user, + project: @project, + target: @merge_request, + author: @user) + + @fork_build_failed_todo = create(:todo, + :build_failed, + user: @user, + project: @project, + target: @merge_request, + author: @user) + @commits = @merge_request.commits @oldrev = @commits.last.id @@ -51,6 +65,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.merge_when_build_succeeds).to be_falsey} it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } + it { expect(@build_failed_todo).to be_done } + it { expect(@fork_build_failed_todo).to be_done } end context 'push to origin repo target branch' do @@ -63,6 +79,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_merged } it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } end context 'manual merge of source branch' do @@ -82,6 +100,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.diffs.size).to be > 0 } it { expect(@fork_merge_request).to be_merged } it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } end context 'push to fork repo source branch' do @@ -101,6 +121,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request).to be_open } it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') } it { expect(@fork_merge_request).to be_open } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } end context 'push to fork repo target branch' do @@ -113,6 +135,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } it { expect(@fork_merge_request).to be_open } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } end context 'push to origin repo target branch after fork project was removed' do @@ -126,6 +150,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } end context 'push new branch that exists in a merge request' do @@ -153,6 +179,8 @@ describe MergeRequests::RefreshService, services: true do def reload_mrs @merge_request.reload @fork_merge_request.reload + @build_failed_todo.reload + @fork_build_failed_todo.reload end end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index a075496ee63..42147736532 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -305,6 +305,25 @@ describe TodoService, services: true do expect(second_todo.reload).to be_done end end + + describe '#merge_request_build_failed' do + it 'creates a pending todo for the merge request author' do + service.merge_request_build_failed(mr_unassigned) + + should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED) + end + end + + describe '#merge_request_push' do + it 'marks related pending todos to the target for the user as done' do + first_todo = create(:todo, :build_failed, user: author, project: project, target: mr_assigned, author: john_doe) + second_todo = create(:todo, :build_failed, user: john_doe, project: project, target: mr_assigned, author: john_doe) + service.merge_request_push(mr_assigned, author) + + expect(first_todo.reload).to be_done + expect(second_todo.reload).not_to be_done + end + end end def should_create_todo(attributes = {}) -- cgit v1.2.3 From a9977f2b7a39d57d0633714616b4653aca103993 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 12 May 2016 16:06:14 +0100 Subject: Syntax-highlight diffs in push emails Based on: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/151 --- .../stylesheets/mailers/repository_push_email.scss | 43 ++++++++++++++++ app/helpers/emails_helper.rb | 6 --- app/mailers/emails/projects.rb | 3 +- app/mailers/notify.rb | 2 + app/views/layouts/notify.html.haml | 1 + app/views/notify/repository_push_email.html.haml | 59 ++++++++++++++-------- app/views/notify/repository_push_email.text.haml | 38 +++++++------- app/views/projects/diffs/_file.html.haml | 2 +- app/workers/emails_on_push_worker.rb | 18 ++++--- config/application.rb | 1 + lib/gitlab/email/message/repository_push.rb | 7 ++- .../gitlab/email/message/repository_push_spec.rb | 2 +- spec/mailers/notify_spec.rb | 16 +++--- 13 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 app/assets/stylesheets/mailers/repository_push_email.scss diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss new file mode 100644 index 00000000000..001994db97b --- /dev/null +++ b/app/assets/stylesheets/mailers/repository_push_email.scss @@ -0,0 +1,43 @@ +@import "framework/variables"; + +table.code { + width: 100%; + font-family: monospace; + border: none; + border-collapse: separate; + margin: 0; + padding: 0; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + + td { + line-height: $code_line_height; + font-family: monospace; + font-size: $code_font_size; + } + + td.diff-line-num { + margin: 0; + padding: 0; + border: none; + background: $background-color; + color: rgba(0, 0, 0, 0.3); + padding: 0 5px; + border-right: 1px solid $border-color; + text-align: right; + min-width: 35px; + max-width: 50px; + width: 35px; + } + + td.line_content { + display: block; + margin: 0; + padding: 0 0.5em; + border: none; + white-space: pre; + } +} + +@import "highlight/white"; diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 41b5bd7be90..8466d0aa0ba 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -32,12 +32,6 @@ module EmailsHelper nil end - def color_email_diff(diffcontent) - formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github') - lexer = Rouge::Lexers::Diff - raw formatter.format(lexer.lex(diffcontent)) - end - def password_reset_token_valid_time valid_hours = Devise.reset_password_within / 60 / 60 if valid_hours >= 24 diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 5489283432b..fdf1e9f5afc 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -65,7 +65,8 @@ module Emails # used in notify layout @target_url = @message.target_url - @project = Project.find project_id + @project = Project.find(project_id) + @diff_notes_disabled = true add_project_headers headers['X-GitLab-Author'] = @message.author_username diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 826e5f96fa1..1c663bdd521 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -10,6 +10,8 @@ class Notify < BaseMailer include Emails::Builds add_template_helper MergeRequestsHelper + add_template_helper DiffHelper + add_template_helper BlobHelper add_template_helper EmailsHelper def test_email(recipient_email, subject, body) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 2997f59d946..dde2e2889dc 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -4,6 +4,7 @@ %title GitLab = stylesheet_link_tag 'notify' + = yield :head %body %div.content = yield diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index f2e405b14fd..f1532371b2e 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,3 +1,6 @@ += content_for :head do + = stylesheet_link_tag 'mailers/repository_push_email' + %h3 #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))} @@ -43,26 +46,38 @@ = diff.new_path - unless @message.disable_diffs? - %h4 Changes: - - @message.diffs.each_with_index do |diff, i| - %li{id: "diff-#{i}"} - %a{href: @message.target_url + "#diff-#{i}"} - - if diff.deleted_file - %strong - = diff.old_path - deleted - - elsif diff.renamed_file - %strong - = diff.old_path - → - %strong - = diff.new_path - - else - %strong - = diff.new_path - %hr - = color_email_diff(diff.diff) - %br + - diff_files = @message.diffs - - if @message.compare_timeout - %h5 Huge diff. To prevent performance issues changes are hidden + - if @message.compare_timeout + %h5 The diff was not included because it is too large. + - else + %h4 Changes: + - diff_files.each_with_index do |diff_file, i| + %li{id: "diff-#{i}"} + %a{href: @message.target_url + "#diff-#{i}"}< + - if diff_file.deleted_file + %strong< + = diff_file.old_path + deleted + - elsif diff_file.renamed_file + %strong< + = diff_file.old_path + → + %strong< + = diff_file.new_path + - else + %strong< + = diff_file.new_path + - if diff_file.too_large? + The diff for this file was not included because it is too large. + - else + %hr + - diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last + - blob = @message.project.repository.blob_for_diff(diff_commit, diff_file) + - if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob) + %table.code.white + - diff_file.highlighted_diff_lines.each do |line| + = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true} + - else + No preview for this file type + %br diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index 53869e36b28..5ac23aa3997 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -25,24 +25,28 @@ - else \- #{diff.new_path} - unless @message.disable_diffs? - \ - \ - Changes: - - @message.diffs.each do |diff| + - if @message.compare_timeout \ - \===================================== - - if diff.deleted_file - #{diff.old_path} deleted - - elsif diff.renamed_file - #{diff.old_path} → #{diff.new_path} - - else - = diff.new_path - \===================================== - != diff.diff - - if @message.compare_timeout - \ - \ - Huge diff. To prevent performance issues it was hidden + \ + The diff was not included because it is too large. + - else + \ + \ + Changes: + - @message.diffs.each do |diff_file| + \ + \===================================== + - if diff_file.deleted_file + #{diff_file.old_path} deleted + - elsif diff_file.renamed_file + #{diff_file.old_path} → #{diff_file.new_path} + - else + = diff_file.new_path + \===================================== + - if diff_file.too_large? + The diff for this file was not included because it is too large. + - else + != diff_file.diff.diff - if @message.target_url \ \ diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 0f04fc5d33c..f10b5094eaf 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -41,7 +41,7 @@ .diff-content.diff-wrap-lines - # Skip all non non-supported blobs - - return unless blob.respond_to?('text?') + - return unless blob.respond_to?(:text?) - if diff_file.too_large? .nothing-here-block This diff could not be displayed because it is too large. - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob) diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 6ebcba5f39b..fa959fc56e3 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -27,15 +27,18 @@ class EmailsOnPushWorker :push end + diff_refs = nil compare = nil reverse_compare = false if action == :push compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) + diff_refs = [project.merge_base_commit(before_sha, after_sha), project.commit(after_sha)] return false if compare.same if compare.commits.empty? compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) + diff_refs = [project.merge_base_commit(after_sha, before_sha), project.commit(before_sha)] reverse_compare = true @@ -48,13 +51,14 @@ class EmailsOnPushWorker send_email( recipient, project_id, - author_id: author_id, - ref: ref, - action: action, - compare: compare, - reverse_compare: reverse_compare, - send_from_committer_email: send_from_committer_email, - disable_diffs: disable_diffs + author_id: author_id, + ref: ref, + action: action, + compare: compare, + reverse_compare: reverse_compare, + diff_refs: diff_refs, + send_from_committer_email: send_from_committer_email, + disable_diffs: disable_diffs ) # These are input errors and won't be corrected even if Sidekiq retries diff --git a/config/application.rb b/config/application.rb index cba80f38f1f..a96765a3c9b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -78,6 +78,7 @@ module Gitlab config.assets.precompile << "*.png" config.assets.precompile << "print.css" config.assets.precompile << "notify.css" + config.assets.precompile << "mailers/repository_push_email.css" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 2c91a0487c3..e2fee6b9f3e 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -5,6 +5,7 @@ module Gitlab attr_reader :author_id, :ref, :action include Gitlab::Routing.url_helpers + include DiffHelper delegate :namespace, :name_with_namespace, to: :project, prefix: :project delegate :name, to: :author, prefix: :author @@ -36,7 +37,7 @@ module Gitlab end def diffs - @diffs ||= (compare.diffs if compare) + @diffs ||= (safe_diff_files(compare.diffs, diff_refs) if compare) end def diffs_count @@ -47,6 +48,10 @@ module Gitlab @opts[:compare] end + def diff_refs + @opts[:diff_refs] + end + def compare_timeout diffs.overflow? if diffs end diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index 7d6cce6daec..c19f33e2224 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -57,7 +57,7 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#diffs' do subject { message.diffs } - it { is_expected.to all(be_an_instance_of Gitlab::Git::Diff) } + it { is_expected.to all(be_an_instance_of Gitlab::Diff::File) } end describe '#diffs_count' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 5f7e4a526e6..b963a3e0324 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -693,8 +693,9 @@ describe Notify do let(:commits) { Commit.decorate(compare.commits, nil) } let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } let(:send_from_committer_email) { false } + let(:diff_refs) { [project.merge_base_commit(sample_image_commit.id, sample_commit.id), project.commit(sample_commit.id)] } - subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) } + subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) } it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" @@ -715,15 +716,15 @@ describe Notify do is_expected.to have_body_text /Change some files/ end - it 'includes diffs' do - is_expected.to have_body_text /def archive_formats_regex/ + it 'includes diffs with character-level highlighting' do + is_expected.to have_body_text /def<\/span> archive_formats_regex/ end it 'contains a link to the diff' do is_expected.to have_body_text /#{diff_path}/ end - it 'doesn not contain the misleading footer' do + it 'does not contain the misleading footer' do is_expected.not_to have_body_text /you are a member of/ end @@ -797,8 +798,9 @@ describe Notify do let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } let(:commits) { Commit.decorate(compare.commits, nil) } let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } + let(:diff_refs) { [project.merge_base_commit(sample_commit.parent_id, sample_commit.id), project.commit(sample_commit.id)] } - subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } + subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) } it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like "a user cannot unsubscribe through footer link" @@ -819,8 +821,8 @@ describe Notify do is_expected.to have_body_text /Change some files/ end - it 'includes diffs' do - is_expected.to have_body_text /def archive_formats_regex/ + it 'includes diffs with character-level highlighting' do + is_expected.to have_body_text /def<\/span> archive_formats_regex/ end it 'contains a link to the diff' do -- cgit v1.2.3 From ef60b8e1685a8761477e822b3190a3a0cf4b0cfa Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 18 May 2016 13:02:10 -0500 Subject: Use pipelines.errors when communicating the error --- app/controllers/projects/pipelines_controller.rb | 14 ++++----- app/services/ci/create_pipeline_service.rb | 36 +++++++++++++++--------- app/views/projects/pipelines/new.html.haml | 7 ++--- spec/features/pipelines_spec.rb | 15 +++++++--- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 19071cc9e43..1282913d981 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -15,19 +15,17 @@ class Projects::PipelinesController < Projects::ApplicationController end def new + @pipeline = project.ci_commits.new end def create - begin - pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute - redirect_to namespace_project_pipeline_path(project.namespace, project, pipeline) - rescue ArgumentError => e - flash[:alert] = e.message - render 'new' - rescue - flash[:alert] = 'The pipeline could not be created. Please try again.' + @pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute + unless @pipeline.persisted? render 'new' + return end + + redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline) end def show diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index e13f4fce13d..b864807ec35 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -1,27 +1,39 @@ module Ci class CreatePipelineService < BaseService def execute + pipeline = project.ci_commits.new + unless ref_names.include?(params[:ref]) - raise ArgumentError, 'Reference not found' + pipeline.errors.add(:base, 'Reference not found') + return pipeline end unless commit - raise ArgumentError, 'Commit not found' + pipeline.errors.add(:base, 'Commit not found') + return pipeline end unless can?(current_user, :create_pipeline, project) - raise RuntimeError, 'Insufficient permissions to create a new pipeline' + pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline') + return pipeline end - pipeline = new_pipeline + begin + Ci::Commit.transaction do + pipeline.sha = commit.id + pipeline.ref = params[:ref] + pipeline.before_sha = Gitlab::Git::BLANK_SHA - Ci::Commit.transaction do - unless pipeline.config_processor - raise ArgumentError, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file' - end + unless pipeline.config_processor + pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file') + raise ActiveRecord::Rollback + end - pipeline.save! - pipeline.create_builds(current_user) + pipeline.save! + pipeline.create_builds(current_user) + end + rescue + pipeline.errors.add(:base, 'The pipeline could not be created. Please try again.') end pipeline @@ -29,10 +41,6 @@ module Ci private - def new_pipeline - project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) - end - def ref_names @ref_names ||= project.repository.ref_names end diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 534a495dd85..1050b28b381 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -1,15 +1,12 @@ - page_title "New Pipeline" = render "header_title" -- if @error - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - = @error %h3.page-title New Pipeline %hr -= form_tag namespace_project_pipelines_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" do += form_for @pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do + = form_errors(@pipeline) .form-group = label_tag :ref, 'Create for', class: 'control-label' .col-sm-10 diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb index 1df516eafd5..32665aadd22 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -126,12 +126,19 @@ describe "Pipelines" do before { visit new_namespace_project_pipeline_path(project.namespace, project) } context 'for valid commit' do - before do - fill_in('Create for', with: 'master') - stub_ci_commit_to_return_yaml_file + before { fill_in('Create for', with: 'master') } + + context 'with gitlab-ci.yml' do + before { stub_ci_commit_to_return_yaml_file } + + it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) } end - it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) } + context 'without gitlab-ci.yml' do + before { click_on 'Create pipeline' } + + it { expect(page).to have_content('Missing .gitlab-ci.yml file') } + end end context 'for invalid commit' do -- cgit v1.2.3 From 4f1c63683175fa88ca41ba2180b68e266d7118e4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 18 May 2016 17:01:42 -0500 Subject: Create pipeline objects with parameters --- app/controllers/projects/pipelines_controller.rb | 4 ++-- app/services/ci/create_pipeline_service.rb | 4 +--- app/views/projects/commit/_ci_commit.html.haml | 4 ++-- app/views/projects/pipelines/new.html.haml | 8 ++++---- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 1282913d981..b36081205d8 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -15,7 +15,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def new - @pipeline = project.ci_commits.new + @pipeline = project.ci_commits.new(ref: @project.default_branch) end def create @@ -46,7 +46,7 @@ class Projects::PipelinesController < Projects::ApplicationController private def create_params - params.permit(:ref) + params.require(:pipeline).permit(:ref) end def pipeline diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index b864807ec35..5bc0c31cb42 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -1,7 +1,7 @@ module Ci class CreatePipelineService < BaseService def execute - pipeline = project.ci_commits.new + pipeline = project.ci_commits.new(params) unless ref_names.include?(params[:ref]) pipeline.errors.add(:base, 'Reference not found') @@ -21,8 +21,6 @@ module Ci begin Ci::Commit.transaction do pipeline.sha = commit.id - pipeline.ref = params[:ref] - pipeline.before_sha = Gitlab::Git::BLANK_SHA unless pipeline.config_processor pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file') diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index df92a38db31..8228c067be0 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -7,8 +7,8 @@ - if ci_commit.builds.running_or_pending.any? = link_to "Cancel running", cancel_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - - if defined?(pipeline_details) && pipeline_details - .oneline + .oneline.clearfix + - if defined?(pipeline_details) && pipeline_details Pipeline = link_to "##{ci_commit.id}", namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: "monospace" with diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 1050b28b381..b97c9f5f3b6 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -5,15 +5,15 @@ New Pipeline %hr -= form_for @pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do += form_for @pipeline, as: :pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f| = form_errors(@pipeline) .form-group - = label_tag :ref, 'Create for', class: 'control-label' + = f.label :ref, 'Create for', class: 'control-label' .col-sm-10 - = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control' + = f.text_field :ref, required: true, tabindex: 2, class: 'form-control' .help-block Existing branch name, tag .form-actions - = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3 + = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel' :javascript -- cgit v1.2.3 From 01256eecfa39cf3edc3d0eb43e624f62e45df552 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 18 May 2016 21:34:58 -0500 Subject: Update 'after_script' introduction note --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 7e9bced7616..63866d8c71c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -128,7 +128,7 @@ builds, including deploy builds. This can be an array or a multi-line string. ### after_script >**Note:** -Introduced in GitLab 8.7 and GitLab Runner v1.2. +Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 (not yet released) `after_script` is used to define the command that will be run after for all builds. This has to be an array or a multi-line string. -- cgit v1.2.3 From c22be75794e0843d1c5de9a15593ccefcc1c09dc Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 19 May 2016 12:36:02 -0500 Subject: Removed outdated comment from migration helpers --- lib/gitlab/database/migration_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 31a87b73fbe..9b662d163f0 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -47,7 +47,7 @@ module Gitlab first['count']. to_i - # Update in batches of 5% with an upper limit of 5000 rows. + # Update in batches of 5% batch_size = ((total / 100.0) * 5.0).ceil while processed < total -- cgit v1.2.3 From 9429da3ae2d118ca1edcc20e992d0b2863cadeed Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 19 May 2016 14:58:35 -0500 Subject: Move generator templates to generator_templates/ Since we eager load everything in lib/ putting ERB code in .rb files will result in syntax errors. By moving the templates to ./generator_templates we can work around this. --- config/application.rb | 4 +- .../migration/create_table_migration.rb | 35 ++++++++++++++ .../active_record/migration/migration.rb | 55 ++++++++++++++++++++++ .../migration/create_table_migration.rb | 35 -------------- lib/templates/active_record/migration/migration.rb | 55 ---------------------- 5 files changed, 93 insertions(+), 91 deletions(-) create mode 100644 generator_templates/active_record/migration/create_table_migration.rb create mode 100644 generator_templates/active_record/migration/migration.rb delete mode 100644 lib/templates/active_record/migration/create_table_migration.rb delete mode 100644 lib/templates/active_record/migration/migration.rb diff --git a/config/application.rb b/config/application.rb index cba80f38f1f..06114bf34ee 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,6 +26,8 @@ module Gitlab #{config.root}/app/models/members #{config.root}/app/models/project_services)) + config.generators.templates.push("#{config.root}/generator_templates") + # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] @@ -39,7 +41,7 @@ module Gitlab config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - # + # # Parameters filtered: # - Password (:password, :password_confirmation) # - Private tokens (:private_token) diff --git a/generator_templates/active_record/migration/create_table_migration.rb b/generator_templates/active_record/migration/create_table_migration.rb new file mode 100644 index 00000000000..27acc75dcc4 --- /dev/null +++ b/generator_templates/active_record/migration/create_table_migration.rb @@ -0,0 +1,35 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class <%= migration_class_name %> < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + create_table :<%= table_name %> do |t| +<% attributes.each do |attribute| -%> +<% if attribute.password_digest? -%> + t.string :password_digest<%= attribute.inject_options %> +<% else -%> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> +<% end -%> +<% end -%> +<% if options[:timestamps] %> + t.timestamps null: false +<% end -%> + end +<% attributes_with_index.each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> +<% end -%> + end +end diff --git a/generator_templates/active_record/migration/migration.rb b/generator_templates/active_record/migration/migration.rb new file mode 100644 index 00000000000..06bdea11367 --- /dev/null +++ b/generator_templates/active_record/migration/migration.rb @@ -0,0 +1,55 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class <%= migration_class_name %> < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + +<%- if migration_action == 'add' -%> + def change +<% attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- if attribute.has_index? -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> +<%- end -%> + end +<%- elsif migration_action == 'join' -%> + def change + create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| + <%- attributes.each do |attribute| -%> + <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + end + end +<%- else -%> + def change +<% attributes.each do |attribute| -%> +<%- if migration_action -%> + <%- if attribute.reference? -%> + remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> + <%- if attribute.has_index? -%> + remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- end -%> +<%- end -%> +<%- end -%> + end +<%- end -%> +end diff --git a/lib/templates/active_record/migration/create_table_migration.rb b/lib/templates/active_record/migration/create_table_migration.rb deleted file mode 100644 index 27acc75dcc4..00000000000 --- a/lib/templates/active_record/migration/create_table_migration.rb +++ /dev/null @@ -1,35 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class <%= migration_class_name %> < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - create_table :<%= table_name %> do |t| -<% attributes.each do |attribute| -%> -<% if attribute.password_digest? -%> - t.string :password_digest<%= attribute.inject_options %> -<% else -%> - t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> -<% end -%> -<% end -%> -<% if options[:timestamps] %> - t.timestamps null: false -<% end -%> - end -<% attributes_with_index.each do |attribute| -%> - add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> -<% end -%> - end -end diff --git a/lib/templates/active_record/migration/migration.rb b/lib/templates/active_record/migration/migration.rb deleted file mode 100644 index 06bdea11367..00000000000 --- a/lib/templates/active_record/migration/migration.rb +++ /dev/null @@ -1,55 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class <%= migration_class_name %> < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - -<%- if migration_action == 'add' -%> - def change -<% attributes.each do |attribute| -%> - <%- if attribute.reference? -%> - add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> - <%- else -%> - add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> - <%- if attribute.has_index? -%> - add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end -%> - <%- end -%> -<%- end -%> - end -<%- elsif migration_action == 'join' -%> - def change - create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| - <%- attributes.each do |attribute| -%> - <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end -%> - end - end -<%- else -%> - def change -<% attributes.each do |attribute| -%> -<%- if migration_action -%> - <%- if attribute.reference? -%> - remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> - <%- else -%> - <%- if attribute.has_index? -%> - remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end -%> - remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> - <%- end -%> -<%- end -%> -<%- end -%> - end -<%- end -%> -end -- cgit v1.2.3 From d0032935a1d720c8e3cfd2597667c62a0c4015a1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 4 May 2016 14:34:11 +0200 Subject: Add runner db field for ability to run untagged jobs --- db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb | 11 +++++++++++ db/schema.rb | 1 + 2 files changed, 12 insertions(+) create mode 100644 db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb diff --git a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb new file mode 100644 index 00000000000..38fee17f904 --- /dev/null +++ b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb @@ -0,0 +1,11 @@ +class AddRunUntaggedToCiRunner < ActiveRecord::Migration + ## + # Downtime expected! + # + # This migration will cause downtime due to exclusive lock + # caused by the default value. + # + def change + add_column :ci_runners, :run_untagged, :boolean, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index af4f4c609e7..5b9fbe79fa2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -269,6 +269,7 @@ ActiveRecord::Schema.define(version: 20160509201028) do t.string "revision" t.string "platform" t.string "architecture" + t.boolean "run_untagged", default: true end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} -- cgit v1.2.3 From a8a205cd5c3f2840e7e8eab16bceff608c288f33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 4 May 2016 14:40:03 +0200 Subject: Add form for runner config to run untagged jobs --- app/views/projects/runners/_form.html.haml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 2d6c964ae94..147f1a2ebc8 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -5,6 +5,12 @@ .checkbox = f.check_box :active %span.light Paused runners don't accept new builds + .form-group + = label :run_untagged, 'Run untagged jobs', class: 'control-label' + .col-sm-10 + .checkbox + = f.check_box :run_untagged + %span.light This runner can pick jobs without tags .form-group = label_tag :token, class: 'control-label' do Token -- cgit v1.2.3 From 2aa57071947e562f302b3858a249339024ef7119 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 5 May 2016 12:20:33 +0200 Subject: Extend runner config options for untagged jobs --- app/models/ci/runner.rb | 2 +- app/views/admin/runners/show.html.haml | 2 -- app/views/projects/runners/_runner.html.haml | 2 +- app/views/projects/runners/show.html.haml | 51 +++++++++++----------------- spec/features/runners_spec.rb | 29 ++++++++++++++++ 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 819064f99bb..23b1556712c 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -4,7 +4,7 @@ module Ci LAST_CONTACT_TIME = 5.minutes.ago AVAILABLE_SCOPES = %w[specific shared active paused online] - FORM_EDITABLE = %i[description tag_list active] + FORM_EDITABLE = %i[description tag_list active run_untagged] has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 4dfb3ed05bb..c3784bf7192 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -9,8 +9,6 @@ %span.runner-state.runner-state-specific Specific - - - if @runner.shared? .bs-callout.bs-callout-success %h4 This runner will process builds from ALL UNASSIGNED projects diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 47ec420189d..96e2aac451f 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -5,7 +5,7 @@ - if @runners.include?(runner) = link_to runner.short_sha, runner_path(runner) %small - =link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do + = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do %i.fa.fa-edit.btn - else = runner.short_sha diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index 5bf4c09ca25..f24e1b9144e 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -17,50 +17,39 @@ %th Property Name %th Value %tr - %td - Tags + %td Active + %td= @runner.active? ? 'Yes' : 'No' + %tr + %td Can run untagged jobs + %td= @runner.run_untagged? ? 'Yes' : 'No' + %tr + %td Tags %td - @runner.tag_list.each do |tag| %span.label.label-primary = tag %tr - %td - Name - %td - = @runner.name + %td Name + %td= @runner.name %tr - %td - Version - %td - = @runner.version + %td Version + %td= @runner.version %tr - %td - Revision - %td - = @runner.revision + %td Revision + %td= @runner.revision %tr - %td - Platform - %td - = @runner.platform + %td Platform + %td= @runner.platform %tr - %td - Architecture - %td - = @runner.architecture + %td Architecture + %td= @runner.architecture %tr - %td - Description - %td - = @runner.description + %td Description + %td= @runner.description %tr - %td - Last contact + %td Last contact %td - if @runner.contacted_at #{time_ago_in_words(@runner.contacted_at)} ago - else Never - - - diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 8edeb8d18af..4f942c3ab1c 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -110,4 +110,33 @@ describe "Runners" do expect(page).to have_content(@specific_runner.platform) end end + + feature 'configuring runners ability to picking untagged jobs' do + given(:project) { create(:empty_project) } + given(:runner) { create(:ci_runner) } + + background do + project.team << [user, :master] + project.runners << runner + end + + scenario 'user checks default configuration' do + visit namespace_project_runner_path(project.namespace, project, runner) + + expect(page).to have_content 'Can run untagged jobs Yes' + end + + scenario 'user want to prevent runner from running untagged job' do + visit runners_path(project) + page.within('.activated-specific-runners') do + first('small > a').click + end + + uncheck 'runner_run_untagged' + click_button 'Save changes' + + expect(page).to have_content 'Can run untagged jobs No' + expect(runner.reload.run_untagged?).to eq false + end + end end -- cgit v1.2.3 From 83df6384558c27d3ff7282e6d66b06fa7e9c0c60 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 5 May 2016 13:08:17 +0200 Subject: Disallow runner to pick untagged build if configured --- app/models/ci/build.rb | 4 ++++ spec/models/build_spec.rb | 38 +++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 92327bdb08d..77a2dec4f72 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -290,6 +290,10 @@ module Ci end def can_be_served?(runner) + if tag_list.empty? && !runner.run_untagged? + return false + end + (tag_list - runner.tag_list).empty? end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index b5d356aa066..5da54e07de8 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -259,11 +259,11 @@ describe Ci::Build, models: true do end describe '#can_be_served?' do - let(:runner) { FactoryGirl.create :ci_runner } + let(:runner) { create(:ci_runner) } before { build.project.runners << runner } - context 'runner without tags' do + context 'when runner does not have tags' do it 'can handle builds without tags' do expect(build.can_be_served?(runner)).to be_truthy end @@ -274,21 +274,37 @@ describe Ci::Build, models: true do end end - context 'runner with tags' do + context 'when runner has tags' do before { runner.tag_list = ['bb', 'cc'] } - it 'can handle builds without tags' do - expect(build.can_be_served?(runner)).to be_truthy + shared_examples 'tagged build picker' do + it 'can handle build with matching tags' do + build.tag_list = ['bb'] + expect(build.can_be_served?(runner)).to be_truthy + end + + it 'cannot handle build without matching tags' do + build.tag_list = ['aa'] + expect(build.can_be_served?(runner)).to be_falsey + end end - it 'can handle build with matching tags' do - build.tag_list = ['bb'] - expect(build.can_be_served?(runner)).to be_truthy + context 'when runner can pick untagged jobs' do + it 'can handle builds without tags' do + expect(build.can_be_served?(runner)).to be_truthy + end + + it_behaves_like 'tagged build picker' end - it 'cannot handle build with not matching tags' do - build.tag_list = ['aa'] - expect(build.can_be_served?(runner)).to be_falsey + context 'when runner can not pick untagged jobs' do + before { runner.run_untagged = false } + + it 'can not handle builds without tags' do + expect(build.can_be_served?(runner)).to be_falsey + end + + it_behaves_like 'tagged build picker' end end end -- cgit v1.2.3 From 9129c37c5bd6e648e23ebe6847b909dd151c7e8a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 5 May 2016 13:28:44 +0200 Subject: Add CI API tests for runner config and untagged jobs --- spec/requests/ci/api/builds_spec.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index cae4656010f..7ebf8e41f3b 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -128,6 +128,38 @@ describe Ci::API::API do end end end + + context 'when build has no tags' do + before do + commit = create(:ci_commit, project: project) + create(:ci_build, commit: commit, tags: []) + end + + context 'when runner is allowed to pick untagged builds' do + before { runner.update_column(:run_untagged, true) } + + it 'picks build' do + register_builds + + expect(response).to have_http_status 201 + end + end + + context 'when runner is not allowed to pick untagged builds' do + before { runner.update_column(:run_untagged, false) } + + it 'does not pick build' do + register_builds + + expect(response).to have_http_status 404 + end + end + + def register_builds + post ci_api("/builds/register"), token: runner.token, + info: { platform: :darwin } + end + end end describe "PUT /builds/:id" do -- cgit v1.2.3 From dd8e9e2d7acf05b837a3bacd1258e9f3df4254a6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 5 May 2016 14:27:06 +0200 Subject: Add custom validator to runner model --- app/controllers/projects/runners_controller.rb | 3 ++- app/models/ci/runner.rb | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 3a9d67aff64..12172b47520 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -20,7 +20,8 @@ class Projects::RunnersController < Projects::ApplicationController if @runner.update_attributes(runner_params) redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' else - redirect_to runner_path(@runner), alert: 'Runner was not updated.' + flash[:alert] = @runner.errors.full_messages.to_sentence + render 'edit' end end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 23b1556712c..b1227d72405 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -26,6 +26,13 @@ module Ci .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) end + validate do |runner| + if runner.tag_list.empty? && !runner.run_untagged? + errors.add(:tags_errors, + 'Runner without tags must be able to pick untagged jobs!') + end + end + acts_as_taggable # Searches for runners matching the given query. -- cgit v1.2.3 From 2ee24bd9ece394269c006f9762d3fa5e16876949 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 08:41:51 +0200 Subject: Update specs to be valid only for tagged runner --- spec/features/runners_spec.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 4f942c3ab1c..2c9c12b4dfa 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -126,17 +126,21 @@ describe "Runners" do expect(page).to have_content 'Can run untagged jobs Yes' end - scenario 'user want to prevent runner from running untagged job' do - visit runners_path(project) - page.within('.activated-specific-runners') do - first('small > a').click - end + context 'when runner has tags' do + before { runner.update_attribute(:tag_list, ['tag']) } - uncheck 'runner_run_untagged' - click_button 'Save changes' + scenario 'user want to prevent runner from running untagged job' do + visit runners_path(project) + page.within('.activated-specific-runners') do + first('small > a').click + end - expect(page).to have_content 'Can run untagged jobs No' - expect(runner.reload.run_untagged?).to eq false + uncheck 'runner_run_untagged' + click_button 'Save changes' + + expect(page).to have_content 'Can run untagged jobs No' + expect(runner.reload.run_untagged?).to eq false + end end end end -- cgit v1.2.3 From 9fd6f1b6009410cee0d0d7db8703ad64701db62b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 08:47:19 +0200 Subject: Improve displaying validation messages for runner --- app/controllers/projects/runners_controller.rb | 1 - app/models/ci/runner.rb | 10 +++++++--- app/views/projects/runners/edit.html.haml | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 12172b47520..0b4fa572501 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -20,7 +20,6 @@ class Projects::RunnersController < Projects::ApplicationController if @runner.update_attributes(runner_params) redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' else - flash[:alert] = @runner.errors.full_messages.to_sentence render 'edit' end end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index b1227d72405..f5813589492 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -27,9 +27,9 @@ module Ci end validate do |runner| - if runner.tag_list.empty? && !runner.run_untagged? - errors.add(:tags_errors, - 'Runner without tags must be able to pick untagged jobs!') + unless runner.has_tags? || runner.run_untagged? + errors.add(:tags_list, + 'can not be empty when runner is not allowed to pick untagged jobs') end end @@ -103,5 +103,9 @@ module Ci def short_sha token[0...8] if token end + + def has_tags? + tag_list.any? + end end end diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml index 771947d7908..a5cfa9a8da9 100644 --- a/app/views/projects/runners/edit.html.haml +++ b/app/views/projects/runners/edit.html.haml @@ -1,5 +1,11 @@ - page_title "Edit", "#{@runner.description} ##{@runner.id}", "Runners" %h4 Runner ##{@runner.id} + +- if @runner.errors.any? + .error-message.js-errors + - @runner.errors.full_messages.each do |error| + %div= error + %hr = render 'form', runner: @runner, runner_form_url: runner_path(@runner) -- cgit v1.2.3 From 8252333183dcff74fd229ca81e7317641ad30791 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 09:04:09 +0200 Subject: Extend CI runners specs --- spec/models/ci/runner_spec.rb | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index eaa94228922..7c6e39419ed 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,6 +1,24 @@ require 'spec_helper' describe Ci::Runner, models: true do + describe 'validation' do + context 'when runner is not allowed to pick untagged jobs' do + context 'when runner does not have tags' do + it 'is not valid' do + runner = build(:ci_runner, tag_list: [], run_untagged: false) + expect(runner).to be_invalid + end + end + + context 'when runner has tags' do + it 'is valid' do + runner = build(:ci_runner, tag_list: ['tag'], run_untagged: false) + expect(runner).to be_valid + end + end + end + end + describe '#display_name' do it 'should return the description if it has a value' do runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') @@ -114,7 +132,19 @@ describe Ci::Runner, models: true do end end - describe '#search' do + describe '#has_tags?' do + context 'when runner has tags' do + subject { create(:ci_runner, tag_list: ['tag']) } + it { is_expected.to have_tags } + end + + context 'when runner does not have tags' do + subject { create(:ci_runner, tag_list: []) } + it { is_expected.to_not have_tags } + end + end + + describe '.search' do let(:runner) { create(:ci_runner, token: '123abc') } it 'returns runners with a matching token' do -- cgit v1.2.3 From 8001eed06e5871f1cb108ceb70213c8b3f9192d2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 09:17:27 +0200 Subject: Add method that check if build has tags --- app/models/ci/build.rb | 8 +++++--- spec/models/build_spec.rb | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 77a2dec4f72..6c9ce0dc481 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -290,13 +290,15 @@ module Ci end def can_be_served?(runner) - if tag_list.empty? && !runner.run_untagged? - return false - end + return false unless has_tags? || runner.run_untagged? (tag_list - runner.tag_list).empty? end + def has_tags? + tag_list.any? + end + def any_runners_online? project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 5da54e07de8..abae3271a5c 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -309,6 +309,18 @@ describe Ci::Build, models: true do end end + describe '#has_tags?' do + context 'when build has tags' do + subject { create(:ci_build, tag_list: ['tag']) } + it { is_expected.to have_tags } + end + + context 'when build does not have tags' do + subject { create(:ci_build, tag_list: []) } + it { is_expected.to_not have_tags } + end + end + describe '#any_runners_online?' do subject { build.any_runners_online? } -- cgit v1.2.3 From 7b607cf4f0edda45e864c07b468b0b0819f974df Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 09:58:17 +0200 Subject: Add Changelog entry for new runner configuration --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 422718dec9a..e8987000f6f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.8.0 (unreleased) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Use a case-insensitive comparison in sanitizing URI schemes - Toggle sign-up confirmation emails in application settings + - Make it possible to prevent tagged runner from picking untagged jobs - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Escape HTML in commit titles in system note messages - Improve multiple branch push performance by memoizing permission checking -- cgit v1.2.3 From da8b72d45300db3339925cf34800b6d17828582f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 12:32:17 +0200 Subject: Extend runner options that are configurable via API --- lib/api/entities.rb | 1 + lib/api/runners.rb | 2 +- spec/requests/api/runners_spec.rb | 15 +++++++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index dbd03ea74fa..8298e3ad34c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -408,6 +408,7 @@ module API class RunnerDetails < Runner expose :tag_list + expose :run_untagged expose :version, :revision, :platform, :architecture expose :contacted_at expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 8ec91485b26..4faba9dc87b 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -49,7 +49,7 @@ module API runner = get_runner(params[:id]) authenticate_update_runner!(runner) - attrs = attributes_for_keys [:description, :active, :tag_list] + attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged] if runner.update(attrs) present runner, with: Entities::RunnerDetails, current_user: current_user else diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 3af61d4b335..73ae8ef631c 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -184,21 +184,24 @@ describe API::Runners, api: true do description = shared_runner.description active = shared_runner.active - put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active, - tag_list: ['ruby2.1', 'pgsql', 'mysql'] + update_runner(shared_runner.id, admin, description: "#{description}_updated", + active: !active, + tag_list: ['ruby2.1', 'pgsql', 'mysql'], + run_untagged: 'false') shared_runner.reload expect(response.status).to eq(200) expect(shared_runner.description).to eq("#{description}_updated") expect(shared_runner.active).to eq(!active) expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') + expect(shared_runner.run_untagged?).to be false end end context 'when runner is not shared' do it 'should update runner' do description = specific_runner.description - put api("/runners/#{specific_runner.id}", admin), description: 'test' + update_runner(specific_runner.id, admin, description: 'test') specific_runner.reload expect(response.status).to eq(200) @@ -208,10 +211,14 @@ describe API::Runners, api: true do end it 'should return 404 if runner does not exists' do - put api('/runners/9999', admin), description: 'test' + update_runner(9999, admin, description: 'test') expect(response.status).to eq(404) end + + def update_runner(id, user, args) + put api("/runners/#{id}", user), args + end end context 'authorized user' do -- cgit v1.2.3 From b253aa32c75cb5b0796ad6a7cd85cba78516ba26 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 12:44:20 +0200 Subject: Add docs for a new configuration setting for runner --- doc/ci/runners/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index a06650b3387..e449d3dc61e 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -125,7 +125,13 @@ shared runners will only run the jobs they are equipped to run. For instance, at GitLab we have runners tagged with "rails" if they contain the appropriate dependencies to run Rails test suites. -### Be Careful with Sensitive Information +### Prevent runner with tags from picking jobs without tags + +You can configure runner to prevent it from picking jobs with tags when +runnner does not have tags assigned. This configuration setting is available +in GitLab interface when editting runner details. + +### Be careful with sensitive information If you can run a build on a runner, you can get access to any code it runs and get the token of the runner. With shared runners, this means that anyone -- cgit v1.2.3 From 9ba72378fc006ecd353e1447a50d2231df09c851 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 13:56:22 +0200 Subject: Refactor CI API specs for creating runner --- spec/requests/ci/api/runners_spec.rb | 46 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index db8189ffb79..5d6f5f774ea 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -12,44 +12,56 @@ describe Ci::API::API do end describe "POST /runners/register" do - describe "should create a runner if token provided" do + context 'when runner token is provided' do before { post ci_api("/runners/register"), token: registration_token } - it { expect(response.status).to eq(201) } + it 'creates runner' do + expect(response.status).to eq(201) + end end - describe "should create a runner with description" do + context 'when runner description is provided' do before { post ci_api("/runners/register"), token: registration_token, description: "server.hostname" } - it { expect(response.status).to eq(201) } - it { expect(Ci::Runner.first.description).to eq("server.hostname") } + it 'creates runner' do + expect(response.status).to eq(201) + expect(Ci::Runner.first.description).to eq("server.hostname") + end end - describe "should create a runner with tags" do + context 'when runner tags are provided' do before { post ci_api("/runners/register"), token: registration_token, tag_list: "tag1, tag2" } - it { expect(response.status).to eq(201) } - it { expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) } + it 'creates runner' do + expect(response.status).to eq(201) + expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) + end end - describe "should create a runner if project token provided" do + context 'when project token is provided' do let(:project) { FactoryGirl.create(:empty_project) } before { post ci_api("/runners/register"), token: project.runners_token } - it { expect(response.status).to eq(201) } - it { expect(project.runners.size).to eq(1) } + it 'creates runner' do + expect(response.status).to eq(201) + expect(project.runners.size).to eq(1) + end end - it "should return 403 error if token is invalid" do - post ci_api("/runners/register"), token: 'invalid' + context 'when token is invalid' do + it 'returns 403 error' do + post ci_api("/runners/register"), token: 'invalid' - expect(response.status).to eq(403) + expect(response.status).to eq(403) + end end - it "should return 400 error if no token" do - post ci_api("/runners/register") + context 'when no token provided' do + it 'returns 400 error' do + post ci_api("/runners/register") - expect(response.status).to eq(400) + expect(response.status).to eq(400) + end end %w(name version revision platform architecture).each do |param| -- cgit v1.2.3 From b8cf2a340b3c56eb7e226473034ead2c4e5d609a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 May 2016 14:06:57 +0200 Subject: Set run untagged option when registering a runner --- lib/ci/api/runners.rb | 15 ++++++--------- spec/requests/ci/api/runners_spec.rb | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb index 192b1d18a51..ea35d0f6dd0 100644 --- a/lib/ci/api/runners.rb +++ b/lib/ci/api/runners.rb @@ -28,20 +28,17 @@ module Ci post "register" do required_attributes! [:token] + attributes = { description: params[:description], + tag_list: params[:tag_list], + run_untagged: params[:run_untagged] || true } + runner = if runner_registration_token_valid? # Create shared runner. Requires admin access - Ci::Runner.create( - description: params[:description], - tag_list: params[:tag_list], - is_shared: true - ) + Ci::Runner.create(attributes.merge(is_shared: true)) elsif project = Project.find_by(runners_token: params[:token]) # Create a specific runner for project. - project.runners.create( - description: params[:description], - tag_list: params[:tag_list] - ) + project.runners.create(attributes) end return forbidden! unless runner diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index 5d6f5f774ea..eb11258cbbb 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -15,13 +15,17 @@ describe Ci::API::API do context 'when runner token is provided' do before { post ci_api("/runners/register"), token: registration_token } - it 'creates runner' do + it 'creates runner with default values' do expect(response.status).to eq(201) + expect(Ci::Runner.first.run_untagged).to be true end end context 'when runner description is provided' do - before { post ci_api("/runners/register"), token: registration_token, description: "server.hostname" } + before do + post ci_api("/runners/register"), token: registration_token, + description: "server.hostname" + end it 'creates runner' do expect(response.status).to eq(201) @@ -30,7 +34,10 @@ describe Ci::API::API do end context 'when runner tags are provided' do - before { post ci_api("/runners/register"), token: registration_token, tag_list: "tag1, tag2" } + before do + post ci_api("/runners/register"), token: registration_token, + tag_list: "tag1, tag2" + end it 'creates runner' do expect(response.status).to eq(201) @@ -38,6 +45,28 @@ describe Ci::API::API do end end + context 'when option for running untagged jobs is provided' do + context 'when tags are provided' do + it 'creates runner' do + post ci_api("/runners/register"), token: registration_token, + run_untagged: false, + tag_list: ['tag'] + + expect(response.status).to eq(201) + expect(Ci::Runner.first.run_untagged).to be false + end + end + + context 'when tags are not provided' do + it 'does not create runner' do + post ci_api("/runners/register"), token: registration_token, + run_untagged: false + + expect(response.status).to eq(404) + end + end + end + context 'when project token is provided' do let(:project) { FactoryGirl.create(:empty_project) } before { post ci_api("/runners/register"), token: project.runners_token } -- cgit v1.2.3 From 3b0eeccc0324e2d6c024fd94067933314e45b862 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 7 May 2016 20:23:36 +0200 Subject: Add not null constraint to run untagged runner option --- db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb | 2 +- db/schema.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb index 38fee17f904..c6753f2b398 100644 --- a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb +++ b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb @@ -6,6 +6,6 @@ class AddRunUntaggedToCiRunner < ActiveRecord::Migration # caused by the default value. # def change - add_column :ci_runners, :run_untagged, :boolean, default: true + add_column :ci_runners, :run_untagged, :boolean, default: true, null: false end end diff --git a/db/schema.rb b/db/schema.rb index 5b9fbe79fa2..2e154b98b34 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -269,7 +269,7 @@ ActiveRecord::Schema.define(version: 20160509201028) do t.string "revision" t.string "platform" t.string "architecture" - t.boolean "run_untagged", default: true + t.boolean "run_untagged", default: true, null: false end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} -- cgit v1.2.3 From 8ab4a67ca6a9166168b744bc940da98bf9651efd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 7 May 2016 20:35:08 +0200 Subject: Use form errors helper in CI runner edit form --- app/views/projects/runners/edit.html.haml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml index a5cfa9a8da9..e4aa98080e5 100644 --- a/app/views/projects/runners/edit.html.haml +++ b/app/views/projects/runners/edit.html.haml @@ -2,10 +2,7 @@ %h4 Runner ##{@runner.id} -- if @runner.errors.any? - .error-message.js-errors - - @runner.errors.full_messages.each do |error| - %div= error += form_errors(@runner) %hr = render 'form', runner: @runner, runner_form_url: runner_path(@runner) -- cgit v1.2.3 From f6dd8a5257bf71f87ea9a2fa2f3d16012e61b6e4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 7 May 2016 20:36:03 +0200 Subject: Move runner validator to separate private method --- app/models/ci/runner.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index f5813589492..cf4d3236519 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -26,12 +26,7 @@ module Ci .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) end - validate do |runner| - unless runner.has_tags? || runner.run_untagged? - errors.add(:tags_list, - 'can not be empty when runner is not allowed to pick untagged jobs') - end - end + validate :verify_tags_constraints acts_as_taggable @@ -107,5 +102,14 @@ module Ci def has_tags? tag_list.any? end + + private + + def verify_tags_constraints + unless has_tags? || run_untagged? + errors.add(:tags_list, + 'can not be empty when runner is not allowed to pick untagged jobs') + end + end end end -- cgit v1.2.3 From 0fd100d28d3748de90aabc3dbbb789e37399f224 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 7 May 2016 20:42:36 +0200 Subject: Improve setting default runner attrs when using API --- lib/ci/api/runners.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb index ea35d0f6dd0..1dc4c8c2cd1 100644 --- a/lib/ci/api/runners.rb +++ b/lib/ci/api/runners.rb @@ -29,8 +29,11 @@ module Ci required_attributes! [:token] attributes = { description: params[:description], - tag_list: params[:tag_list], - run_untagged: params[:run_untagged] || true } + tag_list: params[:tag_list] } + + unless params[:run_untagged].nil? + attributes.merge!(run_untagged: params[:run_untagged]) + end runner = if runner_registration_token_valid? -- cgit v1.2.3 From bf9cc351c28a349ca4c573978c869d2b90209d52 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 10 May 2016 13:19:25 +0200 Subject: Add minor corrections related to config of runner --- app/views/projects/runners/_form.html.haml | 3 ++- app/views/projects/runners/edit.html.haml | 2 -- doc/ci/runners/README.md | 6 +++--- lib/ci/api/runners.rb | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 147f1a2ebc8..6be406fa186 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -1,4 +1,5 @@ = form_for runner, url: runner_form_url, html: { class: 'form-horizontal' } do |f| + = form_errors(@runner) .form-group = label :active, "Active", class: 'control-label' .col-sm-10 @@ -10,7 +11,7 @@ .col-sm-10 .checkbox = f.check_box :run_untagged - %span.light This runner can pick jobs without tags + %span.light Indicates whether runner can pick jobs without tags .form-group = label_tag :token, class: 'control-label' do Token diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml index e4aa98080e5..95706888655 100644 --- a/app/views/projects/runners/edit.html.haml +++ b/app/views/projects/runners/edit.html.haml @@ -2,7 +2,5 @@ %h4 Runner ##{@runner.id} -= form_errors(@runner) - %hr = render 'form', runner: @runner, runner_form_url: runner_path(@runner) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index e449d3dc61e..b42d7a62ebc 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -127,9 +127,9 @@ the appropriate dependencies to run Rails test suites. ### Prevent runner with tags from picking jobs without tags -You can configure runner to prevent it from picking jobs with tags when -runnner does not have tags assigned. This configuration setting is available -in GitLab interface when editting runner details. +You can configure a runner to prevent it from picking jobs with tags when +the runnner does not have tags assigned. This setting is available on each +runner in *Project Settings* > *Runners*. ### Be careful with sensitive information diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb index 1dc4c8c2cd1..0c41f22c7c5 100644 --- a/lib/ci/api/runners.rb +++ b/lib/ci/api/runners.rb @@ -32,7 +32,7 @@ module Ci tag_list: params[:tag_list] } unless params[:run_untagged].nil? - attributes.merge!(run_untagged: params[:run_untagged]) + attributes[:run_untagged] = params[:run_untagged] end runner = -- cgit v1.2.3 From 52ba3a2d05ab93caa5ddbc6207359e99301dda91 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 18 May 2016 17:23:26 +0200 Subject: Display validation errors when admin edits a runner --- app/controllers/admin/runners_controller.rb | 26 ++++++++++++++++---------- app/views/projects/runners/_form.html.haml | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 8b8a7320072..a164209455b 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -9,19 +9,13 @@ class Admin::RunnersController < Admin::ApplicationController 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) + set_builds_and_projects end def update - @runner.update_attributes(runner_params) + unless @runner.update_attributes(runner_params) + set_builds_and_projects and return render 'show' + end respond_to do |format| format.js @@ -60,4 +54,16 @@ class Admin::RunnersController < Admin::ApplicationController def runner_params params.require(:runner).permit(Ci::Runner::FORM_EDITABLE) end + + def set_builds_and_projects + @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 end diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 6be406fa186..b8fe0a98107 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -1,5 +1,5 @@ = form_for runner, url: runner_form_url, html: { class: 'form-horizontal' } do |f| - = form_errors(@runner) + = form_errors(runner) .form-group = label :active, "Active", class: 'control-label' .col-sm-10 -- cgit v1.2.3 From c3c503d259bbf4691f0fb24fcd713ec5b4474e61 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 18 May 2016 18:38:21 +0200 Subject: Rename method that validates runner tag constrains --- app/models/ci/runner.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index cf4d3236519..6829dc91cb9 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -26,7 +26,7 @@ module Ci .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) end - validate :verify_tags_constraints + validate :tag_constraints acts_as_taggable @@ -105,7 +105,7 @@ module Ci private - def verify_tags_constraints + def tag_constraints unless has_tags? || run_untagged? errors.add(:tags_list, 'can not be empty when runner is not allowed to pick untagged jobs') -- cgit v1.2.3 From 4cc77c3bf8ef72d1b08160da9f55eb1c5f95e832 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 19 May 2016 21:27:52 +0200 Subject: Minor runner-related code refactorings --- app/controllers/admin/runners_controller.rb | 19 ++++++++++--------- app/views/projects/runners/_form.html.haml | 2 +- spec/features/runners_spec.rb | 2 +- spec/requests/ci/api/runners_spec.rb | 20 ++++++++++---------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index a164209455b..7345c91f67d 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -9,17 +9,18 @@ class Admin::RunnersController < Admin::ApplicationController end def show - set_builds_and_projects + assign_builds_and_projects end def update - unless @runner.update_attributes(runner_params) - set_builds_and_projects and return render 'show' - end - - respond_to do |format| - format.js - format.html { redirect_to admin_runner_path(@runner) } + if @runner.update_attributes(runner_params) + respond_to do |format| + format.js + format.html { redirect_to admin_runner_path(@runner) } + end + else + assign_builds_and_projects + render 'show' end end @@ -55,7 +56,7 @@ class Admin::RunnersController < Admin::ApplicationController params.require(:runner).permit(Ci::Runner::FORM_EDITABLE) end - def set_builds_and_projects + def assign_builds_and_projects @builds = runner.builds.order('id DESC').first(30) @projects = if params[:search].present? diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index b8fe0a98107..d62f5c8f131 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -11,7 +11,7 @@ .col-sm-10 .checkbox = f.check_box :run_untagged - %span.light Indicates whether runner can pick jobs without tags + %span.light Indicates whether this runner can pick jobs without tags .form-group = label_tag :token, class: 'control-label' do Token diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 2c9c12b4dfa..49156130ad3 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -129,7 +129,7 @@ describe "Runners" do context 'when runner has tags' do before { runner.update_attribute(:tag_list, ['tag']) } - scenario 'user want to prevent runner from running untagged job' do + scenario 'user wants to prevent runner from running untagged job' do visit runners_path(project) page.within('.activated-specific-runners') do first('small > a').click diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index eb11258cbbb..43596f07cb5 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -16,7 +16,7 @@ describe Ci::API::API do before { post ci_api("/runners/register"), token: registration_token } it 'creates runner with default values' do - expect(response.status).to eq(201) + expect(response).to have_http_status 201 expect(Ci::Runner.first.run_untagged).to be true end end @@ -28,7 +28,7 @@ describe Ci::API::API do end it 'creates runner' do - expect(response.status).to eq(201) + expect(response).to have_http_status 201 expect(Ci::Runner.first.description).to eq("server.hostname") end end @@ -40,7 +40,7 @@ describe Ci::API::API do end it 'creates runner' do - expect(response.status).to eq(201) + expect(response).to have_http_status 201 expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) end end @@ -52,7 +52,7 @@ describe Ci::API::API do run_untagged: false, tag_list: ['tag'] - expect(response.status).to eq(201) + expect(response).to have_http_status 201 expect(Ci::Runner.first.run_untagged).to be false end end @@ -62,7 +62,7 @@ describe Ci::API::API do post ci_api("/runners/register"), token: registration_token, run_untagged: false - expect(response.status).to eq(404) + expect(response).to have_http_status 404 end end end @@ -72,7 +72,7 @@ describe Ci::API::API do before { post ci_api("/runners/register"), token: project.runners_token } it 'creates runner' do - expect(response.status).to eq(201) + expect(response).to have_http_status 201 expect(project.runners.size).to eq(1) end end @@ -81,7 +81,7 @@ describe Ci::API::API do it 'returns 403 error' do post ci_api("/runners/register"), token: 'invalid' - expect(response.status).to eq(403) + expect(response).to have_http_status 403 end end @@ -89,7 +89,7 @@ describe Ci::API::API do it 'returns 400 error' do post ci_api("/runners/register") - expect(response.status).to eq(400) + expect(response).to have_http_status 400 end end @@ -101,7 +101,7 @@ describe Ci::API::API do it do post ci_api("/runners/register"), token: registration_token, info: { param => value } - expect(response.status).to eq(201) + expect(response).to have_http_status 201 is_expected.to eq(value) end end @@ -112,7 +112,7 @@ describe Ci::API::API do let!(:runner) { FactoryGirl.create(:ci_runner) } before { delete ci_api("/runners/delete"), token: runner.token } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status 200 } it { expect(Ci::Runner.count).to eq(0) } end end -- cgit v1.2.3 From 52c8b9da37451943fe97f3a687d43ae39105aaa0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 19 May 2016 22:11:40 +0200 Subject: Use migration helper to prevent downtime migration --- .../20160504112519_add_run_untagged_to_ci_runner.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb index c6753f2b398..84e5e4eabe2 100644 --- a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb +++ b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb @@ -1,11 +1,13 @@ class AddRunUntaggedToCiRunner < ActiveRecord::Migration - ## - # Downtime expected! - # - # This migration will cause downtime due to exclusive lock - # caused by the default value. - # - def change - add_column :ci_runners, :run_untagged, :boolean, default: true, null: false + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + add_column_with_default(:ci_runners, :run_untagged, :boolean, + default: true, allow_null: false) + end + + def down + remove_column(:ci_runners, :run_untagged) end end -- cgit v1.2.3 From e3aaab2d266610e85a452df74bd41e75e86b8df3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 19 May 2016 15:32:07 -0500 Subject: Updated Rubocop for generator_templates/ --- .rubocop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 2d2055d76b5..0946ef5d848 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,7 +21,7 @@ AllCops: - 'lib/email_validator.rb' - 'lib/gitlab/upgrader.rb' - 'lib/gitlab/seeder.rb' - - 'lib/templates/**/*' + - 'generator_templates/**/*' ##################### Style ################################## -- cgit v1.2.3 From b876993677edde4fee3a8ed349c522f1542190f7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 May 2016 13:48:57 -0500 Subject: Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios --- CHANGELOG | 1 + app/services/create_commit_builds_service.rb | 17 +++++++---------- spec/workers/post_receive_spec.rb | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9613332774f..d0f3d037325 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.8.0 (unreleased) - Toggle sign-up confirmation emails in application settings - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Escape HTML in commit titles in system note messages + - Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios - Improve multiple branch push performance by memoizing permission checking - Log to application.log when an admin starts and stops impersonating a user - Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi) diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index 0d2aa1ff03d..5b6fefe669e 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -18,19 +18,16 @@ class CreateCommitBuildsService return false end - commit = project.ci_commit(sha, ref) - unless commit - commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag) + commit = Ci::Commit.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag) - # Skip creating ci_commit when no gitlab-ci.yml is found - unless commit.ci_yaml_file - return false - end - - # Create a new ci_commit - commit.save! + # Skip creating ci_commit when no gitlab-ci.yml is found + unless commit.ci_yaml_file + return false end + # Create a new ci_commit + commit.save! + # Skip creating builds for commits that have [ci skip] unless commit.skip_ci? # Create builds for commit diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 94ff3457902..2f465bcf1e3 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -48,6 +48,22 @@ describe PostReceive do PostReceive.new.perform(pwd(project), key_id, base64_changes) end end + + context "gitlab-ci.yml" do + subject { PostReceive.new.perform(pwd(project), key_id, base64_changes) } + + context "creates a Ci::Commit for every change" do + before { stub_ci_commit_to_return_yaml_file } + + it { expect{ subject }.to change{ Ci::Commit.count }.by(2) } + end + + context "does not create a Ci::Commit" do + before { stub_ci_commit_yaml_file(nil) } + + it { expect{ subject }.to_not change{ Ci::Commit.count } } + end + end end context "webhook" do -- cgit v1.2.3