Commit 6f6119b7 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Support pipelines API

Pass `updated_at` to get only incremental changes since last update
parent 7ae775d7
......@@ -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?
......
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
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
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
class PipelineSerializer < BaseSerializer
entity PipelineEntity
end
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment