summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js48
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue6
-rw-r--r--app/assets/stylesheets/framework/icons.scss1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss1
-rw-r--r--app/assets/stylesheets/pages/status.scss1
-rw-r--r--app/helpers/ci_status_helper.rb4
-rw-r--r--app/models/ci/build.rb62
-rw-r--r--app/models/ci/pipeline.rb15
-rw-r--r--app/models/ci/stage.rb7
-rw-r--r--app/models/commit_status.rb20
-rw-r--r--app/models/concerns/has_status.rb16
-rw-r--r--app/services/ci/prepare_build_service.rb2
-rw-r--r--app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb17
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb20
16 files changed, 192 insertions, 31 deletions
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 794391da103..0ffb31b323b 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -470,7 +470,7 @@ export const pikadayToString = date => {
*/
export const parseSeconds = (
seconds,
- { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false } = {},
+ { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false, limitToDays = false } = {},
) => {
const DAYS_PER_WEEK = daysPerWeek;
const HOURS_PER_DAY = hoursPerDay;
@@ -486,8 +486,11 @@ export const parseSeconds = (
minutes: 1,
};
- if (limitToHours) {
+ if (limitToDays || limitToHours) {
timePeriodConstraints.weeks = 0;
+ }
+
+ if (limitToHours) {
timePeriodConstraints.days = 0;
}
@@ -612,3 +615,44 @@ export const secondsToDays = seconds => Math.round(seconds / 86400);
* @return {Date} the date following the date provided
*/
export const dayAfter = date => new Date(newDate(date).setDate(date.getDate() + 1));
+
+/**
+ * Mimics the behaviour of the rails distance_of_time_in_words function
+ * https://api.rubyonrails.org/v6.0.1/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words
+ * 0 < -> 29 secs => less than a minute
+ * 30 secs < -> 1 min, 29 secs => 1 minute
+ * 1 min, 30 secs < -> 44 mins, 29 secs => [2..44] minutes
+ * 44 mins, 30 secs < -> 89 mins, 29 secs => about 1 hour
+ * 89 mins, 30 secs < -> 23 hrs, 59 mins, 29 secs => about[2..24]hours
+ * 23 hrs, 59 mins, 30 secs < -> 41 hrs, 59 mins, 29 secs => 1 day
+ * 41 hrs, 59 mins, 30 secs => x days
+ *
+ * @param {Number} seconds
+ * @return {String} approximated time
+ */
+export const approximateDuration = (seconds = 0) => {
+ if (!_.isNumber(seconds) || seconds < 0) {
+ return '';
+ }
+
+ const ONE_MINUTE_LIMIT = 90; // 1 minute 30s
+ const MINUTES_LIMIT = 2670; // 44 minutes 30s
+ const ONE_HOUR_LIMIT = 5370; // 89 minutes 30s
+ const HOURS_LIMIT = 86370; // 23 hours 59 minutes 30s
+ const ONE_DAY_LIMIT = 151170; // 41 hours 59 minutes 30s
+
+ const { days = 0, hours = 0, minutes = 0 } = parseSeconds(seconds, {
+ daysPerWeek: 7,
+ hoursPerDay: 24,
+ limitToDays: true,
+ });
+
+ if (seconds < 30) {
+ return __('less than a minute');
+ } else if (seconds < MINUTES_LIMIT) {
+ return n__('1 minute', '%d minutes', seconds < ONE_MINUTE_LIMIT ? 1 : minutes);
+ } else if (seconds < HOURS_LIMIT) {
+ return n__('about 1 hour', 'about %d hours', seconds < ONE_HOUR_LIMIT ? 1 : hours);
+ }
+ return n__('1 day', '%d days', seconds < ONE_DAY_LIMIT ? 1 : days);
+};
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index af4ac024e4f..fee5d6d5e3a 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -136,7 +136,11 @@ export default {
>
<strong>{{ __('New! Suggest changes directly') }}</strong>
<p class="mb-2">
- {{ __('Suggest code changes which are immediately applied. Try it out!') }}
+ {{
+ __(
+ 'Suggest code changes which can be immediately applied in one click. Try it out!',
+ )
+ }}
</p>
<gl-button variant="primary" size="sm" @click="handleSuggestDismissed">
{{ __('Got it') }}
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index a53f5d85949..9ae313db4c1 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -20,6 +20,7 @@
}
.ci-status-icon-pending,
+.ci-status-icon-waiting-for-resource,
.ci-status-icon-failed-with-warnings,
.ci-status-icon-success-with-warnings {
svg {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 09b335f9ba2..11d5f4cd374 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -288,7 +288,7 @@
}
.issuable-sidebar {
- width: calc(100% + 100px);
+ width: 100%;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 364fe3da71e..72657a64794 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -795,6 +795,7 @@
}
&.ci-status-icon-pending,
+ &.ci-status-icon-waiting-for-resource,
&.ci-status-icon-success-with-warnings {
@include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 5d6a4b7cd13..4f3d6fb0d44 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -42,6 +42,7 @@
}
&.ci-pending,
+ &.ci-waiting-for-resource,
&.ci-failed-with-warnings,
&.ci-success-with-warnings {
@include status-color($orange-100, $orange-500, $orange-700);
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 4471d5b64b2..80d1b7e7edb 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -62,6 +62,7 @@ module CiStatusHelper
status.humanize
end
+ # rubocop:disable Metrics/CyclomaticComplexity
def ci_icon_for_status(status, size: 16)
if detailed_status?(status)
return sprite_icon(status.icon, size: size)
@@ -77,6 +78,8 @@ module CiStatusHelper
'status_failed'
when 'pending'
'status_pending'
+ when 'waiting_for_resource'
+ 'status_pending'
when 'preparing'
'status_preparing'
when 'running'
@@ -97,6 +100,7 @@ module CiStatusHelper
sprite_icon(icon_name, size: size)
end
+ # rubocop:enable Metrics/CyclomaticComplexity
def ci_icon_class_for_status(status)
group = detailed_status?(status) ? status.group : status.dasherize
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index f0930705a3f..5324d59c155 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -206,9 +206,25 @@ module Ci
state_machine :status do
event :enqueue do
+ transition [:created, :skipped, :manual, :scheduled] => :waiting_for_resource, if: :requires_resource?
transition [:created, :skipped, :manual, :scheduled] => :preparing, if: :any_unmet_prerequisites?
end
+ event :enqueue_scheduled do
+ transition scheduled: :waiting_for_resource, if: :requires_resource?
+ transition scheduled: :preparing, if: :any_unmet_prerequisites?
+ transition scheduled: :pending
+ end
+
+ event :enqueue_waiting_for_resource do
+ transition waiting_for_resource: :preparing, if: :any_unmet_prerequisites?
+ transition waiting_for_resource: :pending
+ end
+
+ event :enqueue_preparing do
+ transition preparing: :pending
+ end
+
event :actionize do
transition created: :manual
end
@@ -221,14 +237,8 @@ module Ci
transition scheduled: :manual
end
- event :enqueue_scheduled do
- transition scheduled: :preparing, if: ->(build) do
- build.scheduled_at&.past? && build.any_unmet_prerequisites?
- end
-
- transition scheduled: :pending, if: ->(build) do
- build.scheduled_at&.past? && !build.any_unmet_prerequisites?
- end
+ before_transition on: :enqueue_scheduled do |build|
+ build.scheduled_at.nil? || build.scheduled_at.past? # If false is returned, it stops the transition
end
before_transition scheduled: any do |build|
@@ -239,6 +249,27 @@ module Ci
build.scheduled_at = build.options_scheduled_at
end
+ before_transition any => :waiting_for_resource do |build|
+ build.waiting_for_resource_at = Time.now
+ end
+
+ before_transition on: :enqueue_waiting_for_resource do |build|
+ next unless build.requires_resource?
+
+ build.resource_group.assign_resource_to(build) # If false is returned, it stops the transition
+ end
+
+ after_transition any => :waiting_for_resource do |build|
+ build.run_after_commit do
+ Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
+ .perform_async(build.resource_group_id)
+ end
+ end
+
+ before_transition on: :enqueue_preparing do |build|
+ build.any_unmet_prerequisites? # If false is returned, it stops the transition
+ end
+
after_transition created: :scheduled do |build|
build.run_after_commit do
Ci::BuildScheduleWorker.perform_at(build.scheduled_at, build.id)
@@ -267,6 +298,16 @@ module Ci
end
end
+ after_transition any => ::Ci::Build.completed_statuses do |build|
+ next unless build.resource_group_id.present?
+ next unless build.resource_group.release_resource_from(build)
+
+ build.run_after_commit do
+ Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
+ .perform_async(build.resource_group_id)
+ end
+ end
+
after_transition any => [:success, :failed, :canceled] do |build|
build.run_after_commit do
BuildFinishedWorker.perform_async(id)
@@ -439,6 +480,11 @@ module Ci
end
end
+ def requires_resource?
+ Feature.enabled?(:ci_resource_group, project) &&
+ self.resource_group_id.present?
+ end
+
def has_environment?
environment.present?
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 42a40e06240..4cecf6f3fe4 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -97,10 +97,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition [:created, :preparing, :skipped, :scheduled] => :pending
+ transition [:created, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running
end
+ event :request_resource do
+ transition any - [:waiting_for_resource] => :waiting_for_resource
+ end
+
event :prepare do
transition any - [:preparing] => :preparing
end
@@ -137,7 +141,7 @@ module Ci
# Do not add any operations to this state_machine
# Create a separate worker for each new operation
- before_transition [:created, :preparing, :pending] => :running do |pipeline|
+ before_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
pipeline.started_at = Time.now
end
@@ -160,7 +164,7 @@ module Ci
end
end
- after_transition [:created, :preparing, :pending] => :running do |pipeline|
+ after_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
@@ -168,7 +172,7 @@ module Ci
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
- after_transition [:created, :preparing, :pending, :running] => :success do |pipeline|
+ after_transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
end
@@ -319,7 +323,7 @@ module Ci
end
def self.bridgeable_statuses
- ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created preparing pending]
+ ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource preparing pending]
end
def stages_count
@@ -578,6 +582,7 @@ module Ci
new_status = latest_builds_status.to_s
case new_status
when 'created' then nil
+ when 'waiting_for_resource' then request_resource
when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 77ac8bfe875..96041e02337 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -39,10 +39,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition [:created, :preparing] => :pending
+ transition [:created, :waiting_for_resource, :preparing] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
+ event :request_resource do
+ transition any - [:waiting_for_resource] => :waiting_for_resource
+ end
+
event :prepare do
transition any - [:preparing] => :preparing
end
@@ -81,6 +85,7 @@ module Ci
new_status = latest_stage_status.to_s
case new_status
when 'created' then nil
+ when 'waiting_for_resource' then request_resource
when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 8d38835fb3b..9da49b44cbe 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -96,7 +96,7 @@ class CommitStatus < ApplicationRecord
# A CommitStatus will never have prerequisites, but this event
# is shared by Ci::Build, which cannot progress unless prerequisites
# are satisfied.
- transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending, unless: :any_unmet_prerequisites?
+ transition [:created, :skipped, :manual, :scheduled] => :pending, if: :all_met_to_become_pending?
end
event :run do
@@ -104,22 +104,22 @@ class CommitStatus < ApplicationRecord
end
event :skip do
- transition [:created, :preparing, :pending] => :skipped
+ transition [:created, :waiting_for_resource, :preparing, :pending] => :skipped
end
event :drop do
- transition [:created, :preparing, :pending, :running, :scheduled] => :failed
+ transition [:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled] => :failed
end
event :success do
- transition [:created, :preparing, :pending, :running] => :success
+ transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success
end
event :cancel do
- transition [:created, :preparing, :pending, :running, :manual, :scheduled] => :canceled
+ transition [:created, :waiting_for_resource, :preparing, :pending, :running, :manual, :scheduled] => :canceled
end
- before_transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
+ before_transition [:created, :waiting_for_resource, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
commit_status.queued_at = Time.now
end
@@ -218,10 +218,18 @@ class CommitStatus < ApplicationRecord
false
end
+ def all_met_to_become_pending?
+ !any_unmet_prerequisites? && !requires_resource?
+ end
+
def any_unmet_prerequisites?
false
end
+ def requires_resource?
+ false
+ end
+
def auto_canceled?
canceled? && auto_canceled_by_id?
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index c01fb4740e5..7fd21c29c82 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -5,16 +5,16 @@ module HasStatus
DEFAULT_STATUS = 'created'
BLOCKED_STATUS = %w[manual scheduled].freeze
- AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze
+ AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
ACTIVE_STATUSES = %w[preparing pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
- ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze
+ ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze
PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7,
- scheduled: 8, preparing: 9 }.freeze
+ scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
UnknownStatusError = Class.new(StandardError)
@@ -29,6 +29,7 @@ module HasStatus
manual = scope_relevant.manual.select('count(*)').to_sql
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
preparing = scope_relevant.preparing.select('count(*)').to_sql
+ waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql
pending = scope_relevant.pending.select('count(*)').to_sql
running = scope_relevant.running.select('count(*)').to_sql
skipped = scope_relevant.skipped.select('count(*)').to_sql
@@ -46,6 +47,7 @@ module HasStatus
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})>0 THEN 'running'
+ WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource'
WHEN (#{manual})>0 THEN 'manual'
WHEN (#{scheduled})>0 THEN 'scheduled'
WHEN (#{preparing})>0 THEN 'preparing'
@@ -95,6 +97,7 @@ module HasStatus
state_machine :status, initial: :created do
state :created, value: 'created'
+ state :waiting_for_resource, value: 'waiting_for_resource'
state :preparing, value: 'preparing'
state :pending, value: 'pending'
state :running, value: 'running'
@@ -107,6 +110,7 @@ module HasStatus
end
scope :created, -> { with_status(:created) }
+ scope :waiting_for_resource, -> { with_status(:waiting_for_resource) }
scope :preparing, -> { with_status(:preparing) }
scope :relevant, -> { without_status(:created) }
scope :running, -> { with_status(:running) }
@@ -117,8 +121,8 @@ module HasStatus
scope :skipped, -> { with_status(:skipped) }
scope :manual, -> { with_status(:manual) }
scope :scheduled, -> { with_status(:scheduled) }
- scope :alive, -> { with_status(:created, :preparing, :pending, :running) }
- scope :alive_or_scheduled, -> { with_status(:created, :preparing, :pending, :running, :scheduled) }
+ scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) }
+ scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) }
scope :created_or_pending, -> { with_status(:created, :pending) }
scope :running_or_pending, -> { with_status(:running, :pending) }
scope :finished, -> { with_status(:success, :failed, :canceled) }
@@ -126,7 +130,7 @@ module HasStatus
scope :incomplete, -> { without_statuses(completed_statuses) }
scope :cancelable, -> do
- where(status: [:running, :preparing, :pending, :created, :scheduled])
+ where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
end
scope :without_statuses, -> (names) do
diff --git a/app/services/ci/prepare_build_service.rb b/app/services/ci/prepare_build_service.rb
index 5d024c45e5f..3f87c711270 100644
--- a/app/services/ci/prepare_build_service.rb
+++ b/app/services/ci/prepare_build_service.rb
@@ -11,7 +11,7 @@ module Ci
def execute
prerequisites.each(&:complete!)
- build.enqueue!
+ build.enqueue_preparing!
rescue => e
Gitlab::ErrorTracking.track_exception(e, build_id: build.id)
diff --git a/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb b/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb
new file mode 100644
index 00000000000..a4bcca8e8b3
--- /dev/null
+++ b/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Ci
+ module ResourceGroups
+ class AssignResourceFromResourceGroupService < ::BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
+ def execute(resource_group)
+ free_resources = resource_group.resources.free.count
+
+ resource_group.builds.waiting_for_resource.take(free_resources).each do |build|
+ build.enqueue_waiting_for_resource
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 02acf360afc..96d75b2fd33 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -103,6 +103,7 @@
- pipeline_processing:stage_update
- pipeline_processing:update_head_pipeline_for_merge_request
- pipeline_processing:ci_build_schedule
+- pipeline_processing:ci_resource_groups_assign_resource_from_resource_group
- deployment:deployments_success
- deployment:deployments_finished
diff --git a/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb b/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb
new file mode 100644
index 00000000000..62233d19516
--- /dev/null
+++ b/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Ci
+ module ResourceGroups
+ class AssignResourceFromResourceGroupWorker
+ include ApplicationWorker
+ include PipelineQueue
+
+ queue_namespace :pipeline_processing
+ feature_category :continuous_delivery
+
+ def perform(resource_group_id)
+ ::Ci::ResourceGroup.find_by_id(resource_group_id).try do |resource_group|
+ Ci::ResourceGroups::AssignResourceFromResourceGroupService.new(resource_group.project, nil)
+ .execute(resource_group)
+ end
+ end
+ end
+ end
+end