Commit a524780b authored by Alex Kalderimis's avatar Alex Kalderimis

Add Job.queued_duration to model, webhook, REST and GQL

This ensures that jobs (and pipelines) have a `queued_duration` field,
wherever they are used (webhooks, REST API, GraphQL).
parent ac7a6af6
......@@ -41,6 +41,16 @@ module Types
field :scheduled_at, Types::TimeType, null: true,
description: 'Schedule for the build.'
# Life-cycle durations:
field :queued_duration,
type: Types::DurationType,
null: true,
description: 'How long this job was enqueued before starting.'
field :duration,
type: Types::DurationType,
null: true,
description: 'How long this job ran for, if it has started.'
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the job.'
field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true,
......
# frozen_string_literal: true
module Types
class DurationType < BaseScalar
graphql_name 'Duration'
description <<~DESC
Duration between two instants, represented as a fractional number of seconds.
For example: 12.3334
DESC
def self.coerce_input(value, ctx)
case value
when Float
value
when Integer
value.to_f
when NilClass
raise GraphQL::CoercionError, 'Cannot be nil'
else
raise GraphQL::CoercionError, "Expected number: got #{value.class}"
end
end
def self.coerce_result(value, ctx)
value.to_f
end
end
end
......@@ -214,8 +214,14 @@ class CommitStatus < ApplicationRecord
allow_failure? && (failed? || canceled?)
end
# Time spent running.
def duration
calculate_duration
calculate_duration(started_at, finished_at)
end
# Time spent in the pending state.
def queued_duration
calculate_duration(queued_at, started_at)
end
def latest?
......
......@@ -122,12 +122,10 @@ module Ci
private
def calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.current - started_at
end
def calculate_duration(start_time, end_time)
return unless start_time
(end_time || Time.current) - start_time
end
end
end
......@@ -6,7 +6,10 @@ module API
class JobBasic < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure
expose :created_at, :started_at, :finished_at
expose :duration
expose :duration,
documentation: { type: 'Floating', desc: 'Time spent running' }
expose :queued_duration,
documentation: { type: 'Floating', desc: 'Time spent enqueued' }
expose :user, with: ::API::Entities::User
expose :commit, with: ::API::Entities::Commit
expose :pipeline, with: ::API::Entities::Ci::PipelineBasic
......
......@@ -30,6 +30,7 @@ module Gitlab
build_started_at: build.started_at,
build_finished_at: build.finished_at,
build_duration: build.duration,
build_queued_duration: build.queued_duration,
build_allow_failure: build.allow_failure,
build_failure_reason: build.failure_reason,
pipeline_id: commit.id,
......
......@@ -31,6 +31,7 @@ module Gitlab
created_at: pipeline.created_at,
finished_at: pipeline.finished_at,
duration: pipeline.duration,
queued_duration: pipeline.queued_duration,
variables: pipeline.variables.map(&:hook_attrs)
}
end
......@@ -59,6 +60,8 @@ module Gitlab
created_at: build.created_at,
started_at: build.started_at,
finished_at: build.finished_at,
duration: build.duration,
queued_duration: build.queued_duration,
when: build.when,
manual: build.action?,
allow_failure: build.allow_failure,
......
......@@ -26,6 +26,7 @@ RSpec.describe Types::Ci::JobType do
pipeline
playable
queued_at
queued_duration
refName
refPath
retryable
......
......@@ -9,6 +9,10 @@ RSpec.describe Gitlab::DataBuilder::Build do
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
describe '.build' do
around do |example|
travel_to(Time.current) { example.run }
end
let(:data) do
described_class.build(build)
end
......@@ -22,6 +26,8 @@ RSpec.describe Gitlab::DataBuilder::Build do
it { expect(data[:build_created_at]).to eq(build.created_at) }
it { expect(data[:build_started_at]).to eq(build.started_at) }
it { expect(data[:build_finished_at]).to eq(build.finished_at) }
it { expect(data[:build_duration]).to eq(build.duration) }
it { expect(data[:build_queued_duration]).to eq(build.queued_duration) }
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:build_failure_reason]).to eq(build.failure_reason) }
it { expect(data[:project_id]).to eq(build.project.id) }
......
......@@ -259,6 +259,40 @@ RSpec.describe CommitStatus do
end
end
describe '#queued_duration' do
subject { commit_status.queued_duration }
around do |example|
travel_to(Time.current) { example.run }
end
context 'when created, then enqueued, then started' do
before do
commit_status.queued_at = 30.seconds.ago
commit_status.started_at = 25.seconds.ago
end
it { is_expected.to eq(5.0) }
end
context 'when created but not yet enqueued' do
before do
commit_status.queued_at = nil
end
it { is_expected.to be_nil }
end
context 'when enqueued, but not started' do
before do
commit_status.queued_at = Time.current - 1.minute
commit_status.started_at = nil
end
it { is_expected.to eq(1.minute) }
end
end
describe '.latest' do
subject { described_class.latest.order(:id) }
......
......@@ -362,6 +362,25 @@ RSpec.describe API::Ci::Pipelines do
it do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(json_response).to all match a_hash_including(
'duration' => be_nil,
'queued_duration' => (be >= 0.0)
)
end
end
context 'when filtering to only running jobs' do
let(:query) { { 'scope' => 'running' } }
it do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(json_response).to all match a_hash_including(
'duration' => (be >= 0.0),
'queued_duration' => (be >= 0.0)
)
end
end
......
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