Support pipelines API
Pass `updated_at` to get only incremental changes since last update
This commit is contained in:
parent
7ae775d7aa
commit
6f6119b738
|
|
@ -11,6 +11,22 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
|
||||
@running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count
|
||||
@pipelines_count = PipelinesFinder.new(project).execute.count
|
||||
@last_updated = params[:updated_at]
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: {
|
||||
pipelines: PipelineSerializer.new(project: @project).
|
||||
represent(@pipelines, current_user: current_user, last_updated: @last_updated),
|
||||
updated_at: Time.now,
|
||||
count: {
|
||||
all: @pipelines_count,
|
||||
running_or_pending: @running_or_pending_count
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
|
|||
|
|
@ -98,19 +98,38 @@ module Ci
|
|||
sha[0...8]
|
||||
end
|
||||
|
||||
def self.stages
|
||||
# We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
|
||||
CommitStatus.where(pipeline: pluck(:id)).stages
|
||||
end
|
||||
|
||||
def self.total_duration
|
||||
where.not(duration: nil).sum(:duration)
|
||||
end
|
||||
|
||||
def stages
|
||||
statuses.group('stage').select(:stage)
|
||||
.order('max(stage_idx)')
|
||||
end
|
||||
|
||||
def stages_with_statuses
|
||||
status_sql = statuses.latest.where('stage=sg.stage').status_sql
|
||||
|
||||
stages_with_statuses = CommitStatus.from(self.stages, :sg).
|
||||
pluck('sg.stage', status_sql)
|
||||
|
||||
stages_with_statuses.map do |stage|
|
||||
OpenStruct.new(
|
||||
name: stage.first,
|
||||
status: stage.last,
|
||||
pipeline: self
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def stages_with_latest_statuses
|
||||
statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
|
||||
end
|
||||
|
||||
def artifacts
|
||||
builds.latest.with_artifacts_not_expired
|
||||
end
|
||||
|
||||
def project_id
|
||||
project.id
|
||||
end
|
||||
|
|
|
|||
|
|
@ -119,16 +119,7 @@ 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')
|
||||
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
|
||||
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').select('sg.stage')
|
||||
end
|
||||
|
||||
def failed_but_allowed?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
class PipelineActionEntity < Grape::Entity
|
||||
include RequestAwareEntity
|
||||
|
||||
expose :name do |build|
|
||||
build.name.humanize
|
||||
end
|
||||
|
||||
expose :url do |build|
|
||||
play_namespace_project_build_path(
|
||||
pipeline.project.namespace,
|
||||
pipeline.project,
|
||||
build)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
class PipelineArtifactEntity < Grape::Entity
|
||||
include RequestAwareEntity
|
||||
|
||||
expose :name do |build|
|
||||
build.name
|
||||
end
|
||||
|
||||
expose :url do |build|
|
||||
download_namespace_project_build_artifacts_path(
|
||||
pipeline.project.namespace,
|
||||
pipeline.project,
|
||||
build)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
class PipelineEntity < Grape::Entity
|
||||
include RequestAwareEntity
|
||||
|
||||
expose :id
|
||||
expose :user, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: UserEntity
|
||||
|
||||
expose :status
|
||||
expose :duration
|
||||
expose :finished_at
|
||||
expose :stages_with_statuses, as: :stages, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineStageEntity
|
||||
expose :artifacts, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineArtifactEntity
|
||||
expose :manual_actions, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineActionEntity
|
||||
|
||||
expose :flags, if: -> (pipeline, opts) { created?(pipeline, opts) } do
|
||||
expose :latest?, as: :latest
|
||||
expose :triggered?, as: :triggered
|
||||
expose :yaml_errors?, as: :yaml_errors do |pipeline|
|
||||
pipeline.yaml_errors.present?
|
||||
end
|
||||
expose :stuck?, as: :stuck do |pipeline|
|
||||
pipeline.builds.any?(&:stuck?)
|
||||
end
|
||||
end
|
||||
|
||||
expose :ref, if: -> (pipeline, opts) { created?(pipeline, opts) } do
|
||||
expose :name do |pipeline|
|
||||
pipeline.ref
|
||||
end
|
||||
|
||||
expose :ref_url do |pipeline|
|
||||
namespace_project_tree_url(
|
||||
pipeline.project.namespace,
|
||||
pipeline.project,
|
||||
id: pipeline.ref)
|
||||
end
|
||||
|
||||
expose :tag?
|
||||
end
|
||||
|
||||
expose :commit, if: -> (pipeline, opts) { created?(pipeline, opts) } do
|
||||
expose :short_sha
|
||||
|
||||
expose :sha_url do |pipeline|
|
||||
namespace_project_commit_path(
|
||||
pipeline.project.namespace,
|
||||
pipeline.project,
|
||||
pipeline.sha)
|
||||
end
|
||||
|
||||
expose :title do |pipeline|
|
||||
pipeline.commit.try(:title)
|
||||
end
|
||||
|
||||
expose :author, using: UserEntity do |pipeline|
|
||||
pipeline.commit.try(:author)
|
||||
end
|
||||
end
|
||||
|
||||
expose :retry_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline|
|
||||
can?(current_user, :update_pipeline, pipeline.project) &&
|
||||
pipeline.retryable? &&
|
||||
retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id)
|
||||
end
|
||||
|
||||
expose :cancel_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline|
|
||||
can?(current_user, :update_pipeline, pipeline.project) &&
|
||||
pipeline.cancelable? &&
|
||||
cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def last_updated(opts)
|
||||
opts.fetch(:last_updated)
|
||||
end
|
||||
|
||||
def created?(pipeline, opts)
|
||||
!last_updated(opts) || pipeline.created_at > last_updated(opts)
|
||||
end
|
||||
|
||||
def updated?(pipeline, opts)
|
||||
!last_updated(opts) || pipeline.updated_at > last_updated(opts)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class PipelineSerializer < BaseSerializer
|
||||
entity PipelineEntity
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
class PipelineStageEntity < Grape::Entity
|
||||
include RequestAwareEntity
|
||||
|
||||
expose :name do |stage|
|
||||
stage.name
|
||||
end
|
||||
|
||||
expose :status do |stage|
|
||||
stage.status || 'not found'
|
||||
end
|
||||
|
||||
expose :url do |stage|
|
||||
namespace_project_pipeline_path(
|
||||
stage.pipeline.project.namespace,
|
||||
stage.pipeline.project,
|
||||
stage.pipeline.id,
|
||||
anchor: stage.name)
|
||||
end
|
||||
end
|
||||
|
|
@ -8,4 +8,12 @@ module RequestAwareEntity
|
|||
def request
|
||||
@options.fetch(:request)
|
||||
end
|
||||
|
||||
def current_user
|
||||
@options.fetch(:current_user)
|
||||
end
|
||||
|
||||
def can?(object, action, subject)
|
||||
Ability.allowed?(object, action, subject)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@
|
|||
|
||||
- stages_status = pipeline.statuses.latest.stages_status
|
||||
%td.stage-cell
|
||||
- stages.each do |stage|
|
||||
- status = stages_status[stage]
|
||||
- tooltip = "#{stage.titleize}: #{status || 'not found'}"
|
||||
- pipeline.statuses.latest.stages_status.each do |stage|
|
||||
- name = stage.first
|
||||
- status = stage.last
|
||||
- tooltip = "#{name.titleize}: #{status || 'not found'}"
|
||||
- if status
|
||||
.stage-container
|
||||
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
|
||||
|
|
|
|||
|
|
@ -66,5 +66,5 @@
|
|||
- if pipeline.project.build_coverage_enabled?
|
||||
%th Coverage
|
||||
%th
|
||||
- pipeline.statuses.relevant.stages.each do |stage|
|
||||
- pipeline.stages.each do |stage|
|
||||
= render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@
|
|||
%th Stages
|
||||
%th
|
||||
%th
|
||||
= render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false
|
||||
= render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@
|
|||
%span CI Lint
|
||||
|
||||
%div.content-list.pipelines{"data-project-id": "#{@project.id}", "data-count": "#{@pipelines_count}"}
|
||||
- stages = @pipelines.stages
|
||||
- if @pipelines.blank?
|
||||
%div
|
||||
.nothing-here-block No pipelines to show
|
||||
|
|
@ -52,7 +51,7 @@
|
|||
%th
|
||||
%th.hidden-xs
|
||||
|
||||
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
|
||||
= render @pipelines, commit_sha: true, stage: true, allow_retry: true
|
||||
= paginate @pipelines, theme: 'gitlab'
|
||||
- else
|
||||
.vue-pipelines-index
|
||||
|
|
|
|||
Loading…
Reference in New Issue