diff options
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: @@ -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 Binary files differnew file mode 100644 index 00000000000..8f2b510be0d --- /dev/null +++ b/doc/user/application_security/license_compliance/img/license_list_v12_6.png 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] ) |