summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml4
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-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
-rw-r--r--changelogs/unreleased/39491-slightly-misleading-tool-tip.yml5
-rw-r--r--changelogs/unreleased/alai-37360-sidebarBug.yml5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json8
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/development/pipelines.md4
-rw-r--r--doc/user/application_security/license_compliance/img/license_list_v12_6.pngbin0 -> 30154 bytes
-rw-r--r--doc/user/application_security/license_compliance/index.md22
-rw-r--r--lib/banzai/filter/plantuml_filter.rb2
-rw-r--r--lib/gitlab/ci/status/build/factory.rb1
-rw-r--r--lib/gitlab/ci/status/build/waiting_for_resource.rb27
-rw-r--r--lib/gitlab/ci/status/composite.rb6
-rw-r--r--lib/gitlab/ci/status/factory.rb2
-rw-r--r--lib/gitlab/ci/status/waiting_for_resource.rb29
-rw-r--r--lib/gitlab/git.rb10
-rw-r--r--locale/gitlab.pot28
-rw-r--r--spec/factories/ci/builds.rb4
-rw-r--r--spec/factories/commit_statuses.rb4
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb111
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js39
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb29
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb4
-rw-r--r--spec/lib/gitlab/git_spec.rb26
-rw-r--r--spec/models/ci/build_spec.rb54
-rw-r--r--spec/models/ci/pipeline_spec.rb13
-rw-r--r--spec/models/ci/stage_spec.rb12
-rw-r--r--spec/models/commit_status_spec.rb30
-rw-r--r--spec/models/concerns/has_status_spec.rb24
-rw-r--r--spec/services/ci/prepare_build_service_spec.rb4
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb22
-rw-r--r--spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb64
-rw-r--r--spec/services/ci/run_scheduled_build_service_spec.rb12
-rw-r--r--spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb34
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb4
58 files changed, 777 insertions, 111 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 6578eec8234..f8811fc57ed 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -14,9 +14,7 @@
- .assets-compile-cache
- .only:changes-code-backstage-qa
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-19.03.1
- stage: test
- dependencies: ["setup-test-env"]
- needs: ["setup-test-env"]
+ stage: prepare
services:
- docker:19.03.0-dind
variables:
diff --git a/Gemfile b/Gemfile
index 16d48c37738..652cc04ecbc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -148,7 +148,7 @@ gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
-gem 'asciidoctor-plantuml', '0.0.9'
+gem 'asciidoctor-plantuml', '0.0.10'
gem 'rouge', '~> 3.11.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 8b4657af4b6..9ca82644a97 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -71,7 +71,7 @@ GEM
asciidoctor (2.0.10)
asciidoctor-include-ext (0.3.1)
asciidoctor (>= 1.5.6, < 3.0.0)
- asciidoctor-plantuml (0.0.9)
+ asciidoctor-plantuml (0.0.10)
asciidoctor (>= 1.5.6, < 3.0.0)
ast (2.4.0)
atlassian-jwt (0.2.0)
@@ -1130,7 +1130,7 @@ DEPENDENCIES
asana (~> 0.9)
asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1)
- asciidoctor-plantuml (= 0.0.9)
+ asciidoctor-plantuml (= 0.0.10)
atlassian-jwt (~> 0.2.0)
attr_encrypted (~> 3.1.0)
awesome_print
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
diff --git a/changelogs/unreleased/39491-slightly-misleading-tool-tip.yml b/changelogs/unreleased/39491-slightly-misleading-tool-tip.yml
new file mode 100644
index 00000000000..6ce0d80318f
--- /dev/null
+++ b/changelogs/unreleased/39491-slightly-misleading-tool-tip.yml
@@ -0,0 +1,5 @@
+---
+title: Update to clarify slightly misleading tool tip
+merge_request: 22222
+author:
+type: other
diff --git a/changelogs/unreleased/alai-37360-sidebarBug.yml b/changelogs/unreleased/alai-37360-sidebarBug.yml
new file mode 100644
index 00000000000..798b334f325
--- /dev/null
+++ b/changelogs/unreleased/alai-37360-sidebarBug.yml
@@ -0,0 +1,5 @@
+---
+title: Sidebar getting partially hidden behind the content block
+merge_request: 21978
+author: allenlai18
+type: fixed
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 1e25e977286..49f788cfaae 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -4443,7 +4443,8 @@ type Pipeline {
startedAt: Time
"""
- Status of the pipeline (CREATED, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)
+ Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING,
+ RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)
"""
status: PipelineStatusEnum!
@@ -4521,6 +4522,7 @@ enum PipelineStatusEnum {
SCHEDULED
SKIPPED
SUCCESS
+ WAITING_FOR_RESOURCE
}
type Project {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index e71fd3fc71b..3ab37b262d2 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -12203,7 +12203,7 @@
},
{
"name": "status",
- "description": "Status of the pipeline (CREATED, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)",
+ "description": "Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)",
"args": [
],
@@ -12345,6 +12345,12 @@
"deprecationReason": null
},
{
+ "name": "WAITING_FOR_RESOURCE",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "PREPARING",
"description": null,
"isDeprecated": false,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f142cee8544..72c7c9ed7ea 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -635,7 +635,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `iid` | String! | Internal ID of the pipeline |
| `sha` | String! | SHA of the pipeline's commit |
| `beforeSha` | String | Base SHA of the source branch |
-| `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) |
+| `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) |
| `detailedStatus` | DetailedStatus! | Detailed status of the pipeline |
| `duration` | Int | Duration of the pipeline in seconds |
| `coverage` | Float | Coverage percentage |
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index b959240cae2..d516a808afd 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -218,6 +218,8 @@ graph RL;
subgraph "`prepare` stage"
A
+ B
+ C
F
K
J
@@ -225,8 +227,6 @@ subgraph "`prepare` stage"
end
subgraph "`test` stage"
- B --> |needs| A;
- C --> |needs| A;
D --> |needs| A;
H -.-> |needs and depends on| A;
H -.-> |needs and depends on| K;
diff --git a/doc/user/application_security/license_compliance/img/license_list_v12_6.png b/doc/user/application_security/license_compliance/img/license_list_v12_6.png
new file mode 100644
index 00000000000..8f2b510be0d
--- /dev/null
+++ b/doc/user/application_security/license_compliance/img/license_list_v12_6.png
Binary files differ
diff --git a/doc/user/application_security/license_compliance/index.md b/doc/user/application_security/license_compliance/index.md
index 3cf8301adca..97804a451b9 100644
--- a/doc/user/application_security/license_compliance/index.md
+++ b/doc/user/application_security/license_compliance/index.md
@@ -253,3 +253,25 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
+
+## License list
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/13582) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.7.
+
+The License list allows you to see your project's licenses and key
+details about them.
+
+In order for the licenses to appear under the license list, the following
+requirements must be met:
+
+1. The License Compliance CI job must be [configured](#configuration) for your project.
+1. Your project must use at least one of the
+ [supported languages and package managers](#supported-languages-and-package-managers).
+
+Once everything is set, navigate to **Security & Compliance > License Compliance**
+in your project's sidebar, and you'll see the licenses displayed, where:
+
+- **Name:** The name of the license.
+- **Component:** The components which have this license.
+
+![License List](img/license_list_v12_6.png)
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
index caba8955bac..1a75cd14b11 100644
--- a/lib/banzai/filter/plantuml_filter.rb
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "nokogiri"
-require "asciidoctor-plantuml/plantuml"
+require "asciidoctor_plantuml/plantuml"
module Banzai
module Filter
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index 96d05842838..7e5afbad806 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -11,6 +11,7 @@ module Gitlab
Status::Build::Manual,
Status::Build::Canceled,
Status::Build::Created,
+ Status::Build::WaitingForResource,
Status::Build::Preparing,
Status::Build::Pending,
Status::Build::Skipped],
diff --git a/lib/gitlab/ci/status/build/waiting_for_resource.rb b/lib/gitlab/ci/status/build/waiting_for_resource.rb
new file mode 100644
index 00000000000..008e6a17bdd
--- /dev/null
+++ b/lib/gitlab/ci/status/build/waiting_for_resource.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class WaitingForResource < Status::Extended
+ ##
+ # TODO: image is shared with 'pending'
+ # until we get a dedicated one
+ #
+ def illustration
+ {
+ image: 'illustrations/pending_job_empty.svg',
+ size: 'svg-430',
+ title: _('This job is waiting for resource: ') + subject.resource_group.key
+ }
+ end
+
+ def self.matches?(build, _)
+ build.waiting_for_resource?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/composite.rb b/lib/gitlab/ci/status/composite.rb
index 3c00b67911f..074651f1040 100644
--- a/lib/gitlab/ci/status/composite.rb
+++ b/lib/gitlab/ci/status/composite.rb
@@ -25,6 +25,8 @@ module Gitlab
# 2. In other cases we assume that status is of that type
# based on what statuses are no longer valid based on the
# data set that we have
+ # rubocop: disable Metrics/CyclomaticComplexity
+ # rubocop: disable Metrics/PerceivedComplexity
def status
return if none?
@@ -43,6 +45,8 @@ module Gitlab
'pending'
elsif any_of?(:running, :pending)
'running'
+ elsif any_of?(:waiting_for_resource)
+ 'waiting_for_resource'
elsif any_of?(:manual)
'manual'
elsif any_of?(:scheduled)
@@ -56,6 +60,8 @@ module Gitlab
end
end
end
+ # rubocop: enable Metrics/CyclomaticComplexity
+ # rubocop: enable Metrics/PerceivedComplexity
def warnings?
@status_set.include?(:success_with_warnings)
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index c29dc51f076..73c73a3b3fc 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -20,7 +20,7 @@ module Gitlab
def core_status
Gitlab::Ci::Status
- .const_get(@status.capitalize, false)
+ .const_get(@status.to_s.camelize, false)
.new(@subject, @user)
.extend(self.class.common_helpers)
end
diff --git a/lib/gitlab/ci/status/waiting_for_resource.rb b/lib/gitlab/ci/status/waiting_for_resource.rb
new file mode 100644
index 00000000000..4c9e706bc51
--- /dev/null
+++ b/lib/gitlab/ci/status/waiting_for_resource.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ class WaitingForResource < Status::Core
+ def text
+ s_('CiStatusText|waiting')
+ end
+
+ def label
+ s_('CiStatusLabel|waiting for resource')
+ end
+
+ def icon
+ 'status_pending'
+ end
+
+ def favicon
+ 'favicon_pending'
+ end
+
+ def group
+ 'waiting-for-resource'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index c58fb1d0be3..b6bffb11344 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -35,16 +35,6 @@ module Gitlab
end
end
- def committer_hash(email:, name:)
- return if email.nil? || name.nil?
-
- {
- email: email,
- name: name,
- time: Time.now
- }
- end
-
def tag_name(ref)
ref = ref.to_s
if self.tag_ref?(ref)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c4bfe86363d..954dbd4af70 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -548,7 +548,9 @@ msgstr[0] ""
msgstr[1] ""
msgid "1 day"
-msgstr ""
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
msgid "1 group"
msgid_plural "%d groups"
@@ -560,6 +562,11 @@ msgid_plural "%{merge_requests} merged merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 open issue"
msgid_plural "%{issues} open issues"
msgstr[0] ""
@@ -3361,6 +3368,9 @@ msgstr ""
msgid "CiStatusLabel|waiting for manual action"
msgstr ""
+msgid "CiStatusLabel|waiting for resource"
+msgstr ""
+
msgid "CiStatusText|blocked"
msgstr ""
@@ -3391,6 +3401,9 @@ msgstr ""
msgid "CiStatusText|skipped"
msgstr ""
+msgid "CiStatusText|waiting"
+msgstr ""
+
msgid "CiStatus|running"
msgstr ""
@@ -17457,7 +17470,7 @@ msgstr ""
msgid "Successfully unlocked"
msgstr ""
-msgid "Suggest code changes which are immediately applied. Try it out!"
+msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
msgstr ""
msgid "Suggested change"
@@ -18511,6 +18524,9 @@ msgstr ""
msgid "This job is stuck because you don't have any active runners that can run this job."
msgstr ""
+msgid "This job is waiting for resource: "
+msgstr ""
+
msgid "This job requires a manual action"
msgstr ""
@@ -21114,6 +21130,11 @@ msgstr ""
msgid "a design"
msgstr ""
+msgid "about 1 hour"
+msgid_plural "about %d hours"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "added %{created_at_timeago}"
msgstr ""
@@ -21704,6 +21725,9 @@ msgstr ""
msgid "leave %{group_name}"
msgstr ""
+msgid "less than a minute"
+msgstr ""
+
msgid "limit of %{project_limit} reached"
msgstr ""
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index a38935c89ba..0c5d14eeb32 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -77,6 +77,10 @@ FactoryBot.define do
status { 'created' }
end
+ trait :waiting_for_resource do
+ status { 'waiting_for_resource' }
+ end
+
trait :preparing do
status { 'preparing' }
end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 5d635d93ff2..a54c0ce74c6 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -35,6 +35,10 @@ FactoryBot.define do
status { 'pending' }
end
+ trait :waiting_for_resource do
+ status { 'waiting_for_resource' }
+ end
+
trait :preparing do
status { 'preparing' }
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 94fac9a2eb5..4b97c58d920 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -606,6 +606,117 @@ describe 'Pipeline', :js do
end
end
+ context 'when build requires resource', :sidekiq_inline do
+ let_it_be(:project) { create(:project, :repository) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:resource_group) { create(:ci_resource_group, project: project) }
+
+ let!(:test_job) do
+ create(:ci_build, :pending, stage: 'test', name: 'test',
+ stage_idx: 1, pipeline: pipeline, project: project)
+ end
+
+ let!(:deploy_job) do
+ create(:ci_build, :created, stage: 'deploy', name: 'deploy',
+ stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group)
+ end
+
+ describe 'GET /:project/pipelines/:id' do
+ subject { visit project_pipeline_path(project, pipeline) }
+
+ it 'shows deploy job as created' do
+ subject
+
+ within('.pipeline-header-container') do
+ expect(page).to have_content('pending')
+ end
+
+ within('.pipeline-graph') do
+ within '.stage-column:nth-child(1)' do
+ expect(page).to have_content('test')
+ expect(page).to have_css('.ci-status-icon-pending')
+ end
+
+ within '.stage-column:nth-child(2)' do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-created')
+ end
+ end
+ end
+
+ context 'when test job succeeded' do
+ before do
+ test_job.success!
+ end
+
+ it 'shows deploy job as pending' do
+ subject
+
+ within('.pipeline-header-container') do
+ expect(page).to have_content('running')
+ end
+
+ within('.pipeline-graph') do
+ within '.stage-column:nth-child(1)' do
+ expect(page).to have_content('test')
+ expect(page).to have_css('.ci-status-icon-success')
+ end
+
+ within '.stage-column:nth-child(2)' do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-pending')
+ end
+ end
+ end
+ end
+
+ context 'when test job succeeded but there are no available resources' do
+ let(:another_job) { create(:ci_build, :running, project: project, resource_group: resource_group) }
+
+ before do
+ resource_group.assign_resource_to(another_job)
+ test_job.success!
+ end
+
+ it 'shows deploy job as waiting for resource' do
+ subject
+
+ within('.pipeline-header-container') do
+ expect(page).to have_content('waiting')
+ end
+
+ within('.pipeline-graph') do
+ within '.stage-column:nth-child(2)' do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-waiting-for-resource')
+ end
+ end
+ end
+
+ context 'when resource is released from another job' do
+ before do
+ another_job.success!
+ end
+
+ it 'shows deploy job as pending' do
+ subject
+
+ within('.pipeline-header-container') do
+ expect(page).to have_content('running')
+ end
+
+ within('.pipeline-graph') do
+ within '.stage-column:nth-child(2)' do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-pending')
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
describe 'GET /:project/pipelines/:id/builds' do
include_context 'pipeline builds'
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index 872779299d2..c32710196db 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -341,6 +341,16 @@ describe('prettyTime methods', () => {
assertTimeUnits(twoDays, 3, 48, 0, 0);
});
+
+ it('should correctly parse values when limitedToDays is true', () => {
+ const sevenDays = datetimeUtility.parseSeconds(648750, {
+ hoursPerDay: 24,
+ daysPerWeek: 7,
+ limitToDays: true,
+ });
+
+ assertTimeUnits(sevenDays, 12, 12, 7, 0);
+ });
});
describe('stringifyTime', () => {
@@ -507,3 +517,32 @@ describe('secondsToDays', () => {
expect(datetimeUtility.secondsToDays(270000)).toBe(3);
});
});
+
+describe('approximateDuration', () => {
+ it.each`
+ seconds
+ ${null}
+ ${{}}
+ ${[]}
+ ${-1}
+ `('returns a blank string for seconds=$seconds', ({ seconds }) => {
+ expect(datetimeUtility.approximateDuration(seconds)).toBe('');
+ });
+
+ it.each`
+ seconds | approximation
+ ${0} | ${'less than a minute'}
+ ${25} | ${'less than a minute'}
+ ${45} | ${'1 minute'}
+ ${90} | ${'1 minute'}
+ ${100} | ${'1 minute'}
+ ${150} | ${'2 minutes'}
+ ${220} | ${'3 minutes'}
+ ${3000} | ${'about 1 hour'}
+ ${30000} | ${'about 8 hours'}
+ ${100000} | ${'1 day'}
+ ${180000} | ${'2 days'}
+ `('converts $seconds seconds to $approximation', ({ seconds, approximation }) => {
+ expect(datetimeUtility.approximateDuration(seconds)).toBe(approximation);
+ });
+});
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index 713bab4527b..abe525ac47a 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -26,7 +26,7 @@ describe Banzai::Filter::PlantumlFilter do
it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
- output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> PlantUML Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
+ output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
index 9d7dfc42848..9c11e42fc5a 100644
--- a/spec/lib/gitlab/ci/status/external/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::External::Factory do
end
let(:expected_status) do
- Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
+ Gitlab::Ci::Status.const_get(simple_status.to_s.camelize, false)
end
it "fabricates a core status #{simple_status}" do
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index c6d7a1ec5d9..219eb53d9df 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Factory do
let(:resource) { double('resource', status: simple_status) }
let(:expected_status) do
- Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
+ Gitlab::Ci::Status.const_get(simple_status.to_s.camelize, false)
end
it "fabricates a core status #{simple_status}" do
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 3acc767ab7a..838154759cb 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
let(:expected_status) do
- Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
+ Gitlab::Ci::Status.const_get(simple_status.camelize, false)
end
it "matches correct core status for #{simple_status}" do
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index dcb53712157..317756ea13c 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
it "fabricates a core status #{core_status}" do
expect(status).to be_a(
- Gitlab::Ci::Status.const_get(core_status.capitalize, false))
+ Gitlab::Ci::Status.const_get(core_status.camelize, false))
end
it 'extends core status with common stage methods' do
diff --git a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
new file mode 100644
index 00000000000..ed00dac8560
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::WaitingForResource do
+ subject do
+ described_class.new(double('subject'), double('user'))
+ end
+
+ describe '#text' do
+ it { expect(subject.text).to eq 'waiting' }
+ end
+
+ describe '#label' do
+ it { expect(subject.label).to eq 'waiting for resource' }
+ end
+
+ describe '#icon' do
+ it { expect(subject.icon).to eq 'status_pending' }
+ end
+
+ describe '#favicon' do
+ it { expect(subject.favicon).to eq 'favicon_pending' }
+ end
+
+ describe '#group' do
+ it { expect(subject.group).to eq 'waiting-for-resource' }
+ end
+end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index cc26b7e7fcd..cb3f4df2dbd 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -71,9 +71,7 @@ describe Gitlab::Git::Branch, :seed_helper do
end
let(:user) { create(:user) }
- let(:committer) do
- Gitlab::Git.committer_hash(email: user.email, name: user.name)
- end
+ let(:committer) { { email: user.email, name: user.name } }
let(:params) do
parents = [rugged.head.target]
tree = parents.first.tree
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index cb07bbdbaaa..d6d12b84724 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -6,32 +6,6 @@ describe Gitlab::Git do
let(:committer_email) { 'user@example.org' }
let(:committer_name) { 'John Doe' }
- describe 'committer_hash' do
- it "returns a hash containing the given email and name" do
- committer_hash = described_class.committer_hash(email: committer_email, name: committer_name)
-
- expect(committer_hash[:email]).to eq(committer_email)
- expect(committer_hash[:name]).to eq(committer_name)
- expect(committer_hash[:time]).to be_a(Time)
- end
-
- context 'when email is nil' do
- it "returns nil" do
- committer_hash = described_class.committer_hash(email: nil, name: committer_name)
-
- expect(committer_hash).to be_nil
- end
- end
-
- context 'when name is nil' do
- it "returns nil" do
- committer_hash = described_class.committer_hash(email: committer_email, name: nil)
-
- expect(committer_hash).to be_nil
- end
- end
- end
-
describe '.ref_name' do
it 'ensure ref is a valid UTF-8 string' do
utf8_invalid_ref = Gitlab::Git::BRANCH_REF_PREFIX + "an_invalid_ref_\xE5"
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index f9c17f732b6..5fabdd9c6c5 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1119,6 +1119,60 @@ describe Ci::Build do
end
end
+ describe 'state transition with resource group' do
+ let(:resource_group) { create(:ci_resource_group, project: project) }
+
+ context 'when build status is created' do
+ let(:build) { create(:ci_build, :created, project: project, resource_group: resource_group) }
+
+ it 'is waiting for resource when build is enqueued' do
+ expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(resource_group.id)
+
+ expect { build.enqueue! }.to change { build.status }.from('created').to('waiting_for_resource')
+
+ expect(build.waiting_for_resource_at).not_to be_nil
+ end
+
+ context 'when build is waiting for resource' do
+ before do
+ build.update_column(:status, 'waiting_for_resource')
+ end
+
+ it 'is enqueued when build requests resource' do
+ expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('pending')
+ end
+
+ it 'releases a resource when build finished' do
+ expect(build.resource_group).to receive(:release_resource_from).with(build).and_call_original
+ expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(build.resource_group_id)
+
+ build.enqueue_waiting_for_resource!
+ build.success!
+ end
+
+ context 'when build has prerequisites' do
+ before do
+ allow(build).to receive(:any_unmet_prerequisites?) { true }
+ end
+
+ it 'is preparing when build is enqueued' do
+ expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('preparing')
+ end
+ end
+
+ context 'when there are no available resources' do
+ before do
+ resource_group.assign_resource_to(create(:ci_build))
+ end
+
+ it 'stays as waiting for resource when build requests resource' do
+ expect { build.enqueue_waiting_for_resource }.not_to change { build.status }
+ end
+ end
+ end
+ end
+ end
+
describe '#on_stop' do
subject { build.on_stop }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 286d2ac4fe6..b30e88532e1 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1750,7 +1750,7 @@ describe Ci::Pipeline, :mailer do
subject { described_class.bridgeable_statuses }
it { is_expected.to be_an(Array) }
- it { is_expected.not_to include('created', 'preparing', 'pending') }
+ it { is_expected.not_to include('created', 'waiting_for_resource', 'preparing', 'pending') }
end
describe '#status', :sidekiq_might_not_need_inline do
@@ -1760,6 +1760,17 @@ describe Ci::Pipeline, :mailer do
subject { pipeline.reload.status }
+ context 'on waiting for resource' do
+ before do
+ allow(build).to receive(:requires_resource?) { true }
+ allow(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async)
+
+ build.enqueue
+ end
+
+ it { is_expected.to eq('waiting_for_resource') }
+ end
+
context 'on prepare' do
before do
# Prevent skipping directly to 'pending'
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index b65c11f837c..7e2751128e2 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -105,6 +105,18 @@ describe Ci::Stage, :models do
end
end
+ context 'when build is waiting for resource' do
+ before do
+ create(:ci_build, :waiting_for_resource, stage_id: stage.id)
+ end
+
+ it 'updates status to waiting for resource' do
+ expect { stage.update_status }
+ .to change { stage.reload.status }
+ .to 'waiting_for_resource'
+ end
+ end
+
context 'when stage is skipped because is empty' do
it 'updates status to skipped' do
expect { stage.update_status }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 31aebac54e1..98dc6f00412 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -634,6 +634,30 @@ describe CommitStatus do
end
end
+ describe '#all_met_to_become_pending?' do
+ subject { commit_status.all_met_to_become_pending? }
+
+ let(:commit_status) { create(:commit_status) }
+
+ it { is_expected.to eq(true) }
+
+ context 'when build requires a resource' do
+ before do
+ allow(commit_status).to receive(:requires_resource?) { true }
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when build has a prerequisite' do
+ before do
+ allow(commit_status).to receive(:any_unmet_prerequisites?) { true }
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
describe '#enqueue' do
let!(:current_time) { Time.new(2018, 4, 5, 14, 0, 0) }
@@ -654,12 +678,6 @@ describe CommitStatus do
it_behaves_like 'commit status enqueued'
end
- context 'when initial state is :preparing' do
- let(:commit_status) { create(:commit_status, :preparing) }
-
- it_behaves_like 'commit status enqueued'
- end
-
context 'when initial state is :skipped' do
let(:commit_status) { create(:commit_status, :skipped) }
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 21e4dda6dab..99d09af80d0 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -39,6 +39,22 @@ describe HasStatus do
it { is_expected.to eq 'running' }
end
+ context 'all waiting for resource' do
+ let!(:statuses) do
+ [create(type, status: :waiting_for_resource), create(type, status: :waiting_for_resource)]
+ end
+
+ it { is_expected.to eq 'waiting_for_resource' }
+ end
+
+ context 'at least one waiting for resource' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :waiting_for_resource)]
+ end
+
+ it { is_expected.to eq 'waiting_for_resource' }
+ end
+
context 'all preparing' do
let!(:statuses) do
[create(type, status: :preparing), create(type, status: :preparing)]
@@ -219,7 +235,7 @@ describe HasStatus do
end
end
- %i[created preparing running pending success
+ %i[created waiting_for_resource preparing running pending success
failed canceled skipped].each do |status|
it_behaves_like 'having a job', status
end
@@ -265,7 +281,7 @@ describe HasStatus do
describe '.alive' do
subject { CommitStatus.alive }
- %i[running pending preparing created].each do |status|
+ %i[running pending waiting_for_resource preparing created].each do |status|
it_behaves_like 'containing the job', status
end
@@ -277,7 +293,7 @@ describe HasStatus do
describe '.alive_or_scheduled' do
subject { CommitStatus.alive_or_scheduled }
- %i[running pending preparing created scheduled].each do |status|
+ %i[running pending waiting_for_resource preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
@@ -313,7 +329,7 @@ describe HasStatus do
describe '.cancelable' do
subject { CommitStatus.cancelable }
- %i[running pending preparing created scheduled].each do |status|
+ %i[running pending waiting_for_resource preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb
index 3c3d8b90bb0..02928b58ff8 100644
--- a/spec/services/ci/prepare_build_service_spec.rb
+++ b/spec/services/ci/prepare_build_service_spec.rb
@@ -14,7 +14,7 @@ describe Ci::PrepareBuildService do
shared_examples 'build enqueueing' do
it 'enqueues the build' do
- expect(build).to receive(:enqueue).once
+ expect(build).to receive(:enqueue_preparing).once
subject
end
@@ -34,7 +34,7 @@ describe Ci::PrepareBuildService do
context 'prerequisites fail to complete' do
before do
- allow(build).to receive(:enqueue).and_return(false)
+ allow(build).to receive(:enqueue_preparing).and_return(false)
end
it 'drops the build' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index ba5891c8694..509a4231472 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -261,12 +261,16 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
- enqueue_scheduled('rollout10%')
+ Timecop.travel 2.minutes.from_now do
+ enqueue_scheduled('rollout10%')
+ end
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'scheduled' })
- enqueue_scheduled('rollout100%')
+ Timecop.travel 2.minutes.from_now do
+ enqueue_scheduled('rollout100%')
+ end
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'success', 'cleanup': 'pending' })
@@ -328,7 +332,9 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
- enqueue_scheduled('rollout10%')
+ Timecop.travel 2.minutes.from_now do
+ enqueue_scheduled('rollout10%')
+ end
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'failed' })
@@ -394,7 +400,9 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed1': 'scheduled', 'delayed2': 'scheduled' })
- enqueue_scheduled('delayed1')
+ Timecop.travel 2.minutes.from_now do
+ enqueue_scheduled('delayed1')
+ end
expect(builds_names_and_statuses).to eq({ 'delayed1': 'pending', 'delayed2': 'scheduled' })
expect(pipeline.reload.status).to eq 'running'
@@ -413,7 +421,9 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed': 'scheduled' })
- enqueue_scheduled('delayed')
+ Timecop.travel 2.minutes.from_now do
+ enqueue_scheduled('delayed')
+ end
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'delayed': 'failed', 'job': 'pending' })
@@ -906,7 +916,7 @@ describe Ci::ProcessPipelineService, '#execute' do
end
def enqueue_scheduled(name)
- builds.scheduled.find_by(name: name).enqueue
+ builds.scheduled.find_by(name: name).enqueue_scheduled
end
def retry_build(name)
diff --git a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
new file mode 100644
index 00000000000..50d312647ae
--- /dev/null
+++ b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ subject { service.execute(resource_group) }
+
+ let(:resource_group) { create(:ci_resource_group, project: project) }
+ let!(:build) { create(:ci_build, :waiting_for_resource, project: project, user: user, resource_group: resource_group) }
+
+ context 'when there is an available resource' do
+ it 'requests resource' do
+ subject
+
+ expect(build.reload).to be_pending
+ expect(build.resource).to be_present
+ end
+
+ context 'when failed to request resource' do
+ before do
+ allow_next_instance_of(Ci::Build) do |build|
+ allow(build).to receive(:enqueue_waiting_for_resource) { false }
+ end
+ end
+
+ it 'has a build waiting for resource' do
+ subject
+
+ expect(build).to be_waiting_for_resource
+ end
+ end
+
+ context 'when the build has already retained a resource' do
+ before do
+ resource_group.assign_resource_to(build)
+ build.update_column(:status, :pending)
+ end
+
+ it 'has a pending build' do
+ subject
+
+ expect(build).to be_pending
+ end
+ end
+ end
+
+ context 'when there are no available resources' do
+ before do
+ resource_group.assign_resource_to(create(:ci_build))
+ end
+
+ it 'does not request resource' do
+ expect_any_instance_of(Ci::Build).not_to receive(:enqueue_waiting_for_resource)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb
index ab8d9f4ba2e..43d110cbc8f 100644
--- a/spec/services/ci/run_scheduled_build_service_spec.rb
+++ b/spec/services/ci/run_scheduled_build_service_spec.rb
@@ -26,6 +26,18 @@ describe Ci::RunScheduledBuildService do
expect(build).to be_pending
end
+
+ context 'when build requires resource' do
+ let(:resource_group) { create(:ci_resource_group, project: project) }
+
+ before do
+ build.update!(resource_group: resource_group)
+ end
+
+ it 'transits to waiting for resource status' do
+ expect { subject }.to change { build.status }.from('scheduled').to('waiting_for_resource')
+ end
+ end
end
context 'when scheduled_at is not expired' do
diff --git a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
new file mode 100644
index 00000000000..634d932121e
--- /dev/null
+++ b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ subject { worker.perform(resource_group_id) }
+
+ context 'when resource group exists' do
+ let(:resource_group) { create(:ci_resource_group) }
+ let(:resource_group_id) { resource_group.id }
+
+ it 'executes AssignResourceFromResourceGroupService' do
+ expect_next_instance_of(Ci::ResourceGroups::AssignResourceFromResourceGroupService, resource_group.project, nil) do |service|
+ expect(service).to receive(:execute).with(resource_group)
+ end
+
+ subject
+ end
+ end
+
+ context 'when build does not exist' do
+ let(:resource_group_id) { 123 }
+
+ it 'does not execute AssignResourceFromResourceGroupService' do
+ expect(Ci::ResourceGroups::AssignResourceFromResourceGroupService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index cc1c23bb9e7..64ad4ba7eb6 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -230,8 +230,8 @@ describe GitGarbageCollectWorker do
new_commit_sha = Rugged::Commit.create(
rugged,
message: "hello world #{SecureRandom.hex(6)}",
- author: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'),
- committer: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'),
+ author: { email: 'foo@bar', name: 'baz' },
+ committer: { email: 'foo@bar', name: 'baz' },
tree: old_commit.tree,
parents: [old_commit]
)