diff options
| -rw-r--r-- | app/assets/stylesheets/framework/tables.scss | 16 | ||||
| -rw-r--r-- | app/controllers/projects/pipelines_controller.rb | 88 | ||||
| -rw-r--r-- | app/models/ability.rb | 7 | ||||
| -rw-r--r-- | app/models/ci/commit.rb | 11 | ||||
| -rw-r--r-- | app/views/layouts/nav/_project.html.haml | 7 | ||||
| -rw-r--r-- | app/views/projects/ci/builds/_build.html.haml | 4 | ||||
| -rw-r--r-- | app/views/projects/ci/commits/_commit.html.haml | 75 | ||||
| -rw-r--r-- | app/views/projects/commit/_ci_commit.html.haml | 30 | ||||
| -rw-r--r-- | app/views/projects/pipelines/_header_title.html.haml | 1 | ||||
| -rw-r--r-- | app/views/projects/pipelines/index.html.haml | 62 | ||||
| -rw-r--r-- | app/views/projects/pipelines/new.html.haml | 25 | ||||
| -rw-r--r-- | app/views/projects/pipelines/show.html.haml | 3 | ||||
| -rw-r--r-- | config/routes.rb | 7 | ||||
| -rw-r--r-- | spec/models/project_spec.rb | 2 |
14 files changed, 312 insertions, 26 deletions
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 75b770ae5a2..9d6a6c5b237 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -34,6 +34,22 @@ 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/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb new file mode 100644 index 00000000000..b2ee5573bfc --- /dev/null +++ b/app/controllers/projects/pipelines_controller.rb @@ -0,0 +1,88 @@ +class Projects::PipelinesController < Projects::ApplicationController + 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] + 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) + 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 + + 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 + respond_to do |format| + format.html + end + end + + def retry + pipeline.builds.latest.failed.select(&:retryable?).each(&:retry) + + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + def cancel + pipeline.builds.running_or_pending.each(&:cancel) + + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + private + + def pipeline + @pipeline ||= 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/models/ci/commit.rb b/app/models/ci/commit.rb index 8865bd76bd2..7991b987e35 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -94,6 +94,17 @@ 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 + 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 86b46e8c75e..b58d8270230 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_pipelines_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/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 new file mode 100644 index 00000000000..32f85cb8f8c --- /dev/null +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -0,0 +1,75 @@ +- status = commit.status +%tr.commit + %td.commit-link + = 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.branch-commit + - if 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.triggered? + %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 + + %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="#{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-clock-o + + #{duration_in_words(commit.finished_at, commit.started_at)} + - if commit.finished_at + %p + %i.fa.fa-calendar + + #{time_ago_with_tooltip(commit.finished_at)} + + %td + .controls.hidden-xs.pull-right + - artifacts = commit.builds.latest + - 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? && 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 25714e6cb47..2ec3c809e1c 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, ci_commit.sha), 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, ci_commit.sha), 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 ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), 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/_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..838b2986d4f --- /dev/null +++ b/app/views/projects/pipelines/index.html.haml @@ -0,0 +1,62 @@ +- 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_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 + + %li{class: ('active' if @scope == 'tags')} + = link_to project_pipelines_path(@project, scope: :tags) do + Tags + + .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 = @pipelines.stages + - if @pipelines.blank? + %li + .nothing-here-block No pipelines to show + - else + .table-holder + %table.table.builds + %tbody + %th Pipeline ID + %th Commit + - @pipelines.stages.each do |stage| + %th + %span.has-tooltip(title="#{stage.titleize}") + = truncate(stage.titleize.pluralize, length: 8) + %th + %th + = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages + + = paginate @pipelines, 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/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 48601b7567b..0932739473d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -656,6 +656,13 @@ Rails.application.routes.draw do resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] + resources :pipelines, only: [:index, :new, :create, :show] do + member do + post :cancel + post :retry + end + end + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all 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) } |
