From e778dcbceebbfbae42a3743cf76d80229ccc716c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 24 Nov 2022 15:07:34 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo/style/lambda.yml | 1 - app/assets/javascripts/flash.js | 8 +- .../projects/default_project_templates.js | 4 + .../development/schema_linting.yml | 2 +- db/docs/ci_job_token_project_scope_links.yml | 5 +- ...d_dashboard_fields_to_namespace_details.rb | 10 ++ db/schema_migrations/20221110183103 | 1 + db/structure.sql | 4 +- .../incident_management/incidents.md | 5 +- doc/user/product_analytics/index.md | 31 +++- doc/user/shortcuts.md | 1 + lib/gitlab/ci/config/external/mapper.rb | 8 +- lib/gitlab/ci/config/external/processor.rb | 4 +- lib/gitlab/ci/pipeline/chain/seed.rb | 10 +- lib/gitlab/memory/watchdog/configuration.rb | 10 +- lib/gitlab/memory/watchdog/configurator.rb | 61 ++++++-- .../watchdog/monitor/heap_fragmentation.rb | 5 +- .../watchdog/monitor/rss_memory_limit.rb | 18 +-- lib/gitlab/memory/watchdog/monitor_state.rb | 19 +-- lib/gitlab/project_template.rb | 1 + locale/gitlab.pot | 6 + .../gitlab_migration_large_project_spec.rb | 2 +- .../groups/labels_controller_spec.rb | 2 +- .../projects/labels_controller_spec.rb | 2 +- spec/frontend/flash_spec.js | 24 ++- .../memory/watchdog/configuration_spec.rb | 25 +-- .../memory/watchdog/configurator_spec.rb | 146 +++++++++++++++--- .../watchdog/monitor/rss_memory_limit_spec.rb | 16 +- .../memory/watchdog/monitor_state_spec.rb | 11 +- spec/models/ci/build_metadata_spec.rb | 128 ++++++++++----- spec/requests/api/project_import_spec.rb | 2 +- .../helpers/project_template_test_helper.rb | 2 +- vendor/project_templates/bridgetown.tar.gz | Bin 0 -> 42296 bytes 33 files changed, 409 insertions(+), 165 deletions(-) create mode 100644 db/migrate/20221110183103_add_dashboard_fields_to_namespace_details.rb create mode 100644 db/schema_migrations/20221110183103 create mode 100644 vendor/project_templates/bridgetown.tar.gz diff --git a/.rubocop_todo/style/lambda.yml b/.rubocop_todo/style/lambda.yml index f37c2c4967e..9da29f7bb59 100644 --- a/.rubocop_todo/style/lambda.yml +++ b/.rubocop_todo/style/lambda.yml @@ -49,7 +49,6 @@ Style/Lambda: - 'lib/gitlab/action_cable/request_store_callbacks.rb' - 'lib/gitlab/checks/diff_check.rb' - 'lib/gitlab/database/load_balancing/action_cable_callbacks.rb' - - 'lib/gitlab/memory/watchdog/configurator.rb' - 'lib/gitlab/middleware/rack_multipart_tempfile_factory.rb' - 'lib/gitlab/omniauth_initializer.rb' - 'lib/gitlab/prometheus/queries/query_additional_metrics.rb' diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index dc6c4642e94..9e804b60d59 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -114,6 +114,7 @@ const addDismissFlashClickListener = (flashEl, fadeTransition) => { * @param {object} [options.parent] - Reference to parent element under which alert needs to appear. Defaults to `document`. * @param {Function} [options.onDismiss] - Handler to call when this alert is dismissed. * @param {string} [options.containerSelector] - Selector for the container of the alert + * @param {boolean} [options.preservePrevious] - Set to `true` to preserve previous alerts. Defaults to `false`. * @param {object} [options.primaryButton] - Object describing primary button of alert * @param {string} [options.primaryButton.link] - Href of primary button * @param {string} [options.primaryButton.text] - Text of primary button @@ -131,6 +132,7 @@ const createAlert = function createAlert({ variant = VARIANT_DANGER, parent = document, containerSelector = '.flash-container', + preservePrevious = false, primaryButton = null, secondaryButton = null, onDismiss = null, @@ -143,7 +145,11 @@ const createAlert = function createAlert({ if (!alertContainer) return null; const el = document.createElement('div'); - alertContainer.appendChild(el); + if (preservePrevious) { + alertContainer.appendChild(el); + } else { + alertContainer.replaceChildren(el); + } return new Vue({ el, diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js index 3671b24b502..497d8acc4a9 100644 --- a/app/assets/javascripts/projects/default_project_templates.js +++ b/app/assets/javascripts/projects/default_project_templates.js @@ -113,4 +113,8 @@ export default { text: s__('ProjectTemplates|Jsonnet for Dynamic Child Pipelines'), icon: '.template-option .icon-gitlab_logo', }, + bridgetown: { + text: s__('ProjectTemplates|Pages/Bridgetown'), + icon: '.template-option .icon-gitlab_logo', + }, }; diff --git a/config/feature_flags/development/schema_linting.yml b/config/feature_flags/development/schema_linting.yml index 6c1cbdb5248..0abca3e03dc 100644 --- a/config/feature_flags/development/schema_linting.yml +++ b/config/feature_flags/development/schema_linting.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/255919 milestone: '13.2' type: development group: group::editor -default_enabled: false +default_enabled: true diff --git a/db/docs/ci_job_token_project_scope_links.yml b/db/docs/ci_job_token_project_scope_links.yml index 9102ef0db93..de6d69d1c64 100644 --- a/db/docs/ci_job_token_project_scope_links.yml +++ b/db/docs/ci_job_token_project_scope_links.yml @@ -4,7 +4,10 @@ classes: - Ci::JobToken::ProjectScopeLink feature_categories: - continuous_integration -description: The connection between a source project, which defines the job token scope, and a target project, which is the one allowed to be accessed by the job token. +description: | + Links a source project and target project, allowing a project's job token to give access to another project. + Using the outbound direction, the source project's job token can access target projects. + Using the inbound direction, the source project can be accessed by the target project's job token. introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62733 milestone: '14.0' gitlab_schema: gitlab_ci diff --git a/db/migrate/20221110183103_add_dashboard_fields_to_namespace_details.rb b/db/migrate/20221110183103_add_dashboard_fields_to_namespace_details.rb new file mode 100644 index 00000000000..73e8ccbcb51 --- /dev/null +++ b/db/migrate/20221110183103_add_dashboard_fields_to_namespace_details.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddDashboardFieldsToNamespaceDetails < Gitlab::Database::Migration[2.0] + enable_lock_retries! + + def change + add_column :namespace_details, :dashboard_notification_at, :datetime_with_timezone + add_column :namespace_details, :dashboard_enforcement_at, :datetime_with_timezone + end +end diff --git a/db/schema_migrations/20221110183103 b/db/schema_migrations/20221110183103 new file mode 100644 index 00000000000..08b3a8823df --- /dev/null +++ b/db/schema_migrations/20221110183103 @@ -0,0 +1 @@ +3a8b69f61d48ed02d1015cf63b1dd89fb7206a3d5ce9668126cfdc52048f1e61 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3331998bdbf..c7c8d98589a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18034,7 +18034,9 @@ CREATE TABLE namespace_details ( description text, description_html text, free_user_cap_over_limt_notified_at timestamp with time zone, - free_user_cap_over_limit_notified_at timestamp with time zone + free_user_cap_over_limit_notified_at timestamp with time zone, + dashboard_notification_at timestamp with time zone, + dashboard_enforcement_at timestamp with time zone ); CREATE TABLE namespace_limits ( diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index a5d38b1a27c..5c7c76896d3 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -66,8 +66,9 @@ You can set up a webhook with PagerDuty to automatically create a GitLab inciden for each PagerDuty incident. This configuration requires you to make changes in both PagerDuty and GitLab: -1. Sign in as a user with the Maintainer role. -1. Navigate to **Settings > Monitor > Incidents** and expand **Incidents**. +1. On the top bar, select **Main menu > Projects** and find your project. +1. On the left sidebar, select **Settings > Monitor** +1. Expand **Incidents**. 1. Select the **PagerDuty integration** tab: ![PagerDuty incidents integration](img/pagerduty_incidents_integration_v13_3.png) diff --git a/doc/user/product_analytics/index.md b/doc/user/product_analytics/index.md index 8e340fff32a..ebeb1f171a8 100644 --- a/doc/user/product_analytics/index.md +++ b/doc/user/product_analytics/index.md @@ -4,9 +4,9 @@ group: Product Analytics info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Product analytics **(ULTIMATE)** **Alpha** +# Product analytics **(ULTIMATE)** -> Introduced in GitLab 15.4 [with a flag](../../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default. +> Introduced in GitLab 15.4 as an [Alpha](../../policy/alpha-beta-support.md#alpha-features) feature [with a flag](../../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default. FLAG: On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `cube_api_proxy`. @@ -17,6 +17,33 @@ This feature is not ready for production use. You can view the [product category](https://about.gitlab.com/direction/analytics/product-analytics/) page for more information about our direction. This page is a work in progress and will be updated as we add more features. +## Enable product analytics + +You can enable and configure product analytics to track events +within your project applications on a self-managed instance. + +Prerequisite: + +- You must be an administrator of a self-managed GitLab instance. + +1. On the top bar, select **Main menu > Admin**. +1. On the left sidebar, select **Settings > General**. +1. Expand the **Product analytics** section. +1. Select **Enable product analytics** and enter the configuration values. + The following table shows the required configuration parameters and example values: + + | Name | Value | + |------------------------------|----------------------------| + | Jitsu host | `https://jitsu.gitlab.com` | + | Jitsu project ID | `g0maofw84gx5sjxgse2k` | + | Jitsu administrator email | `jitsu.admin@gitlab.com` | + | Jitsu administrator password | `` | + | Clickhouse URL | `https://:@clickhouse.gitlab.com:8123` | + | Cube API URL | `https://cube.gitlab.com` | + | Cube API key | `25718201b3e9...ae6bbdc62dbb` | + +1. Select **Save changes**. + ## Product analytics dashboards Each project can define an unlimited number of dashboards. These dashboards are defined using our YAML schema and stored diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md index f9e61ad78ad..64f9b53f891 100644 --- a/doc/user/shortcuts.md +++ b/doc/user/shortcuts.md @@ -37,6 +37,7 @@ These shortcuts are available in most areas of GitLab: | f | Put cursor in the filter bar. | | Shift + i | Go to your Issues page. | | Shift + m | Go to your [Merge requests](project/merge_requests/index.md) page. | +| Shift + r | Go to your Review requests page. | | Shift + t | Go to your To-Do List page. | | p, then b | Show or hide the Performance Bar. | | Escape | Hide tooltips or popovers. | diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb index fc03ac125fd..5e93aef9617 100644 --- a/lib/gitlab/ci/config/external/mapper.rb +++ b/lib/gitlab/ci/config/external/mapper.rb @@ -52,14 +52,8 @@ module Gitlab .each(&method(:verify!)) end - def normalize_location(location) - logger.instrument(:config_mapper_normalize) do - normalize_location_without_instrumentation(location) - end - end - # convert location if String to canonical form - def normalize_location_without_instrumentation(location) + def normalize_location(location) if location.is_a?(String) expanded_location = expand_variables(location) normalize_location_string(expanded_location) diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb index 6a4aee26d80..e15b51fbff4 100644 --- a/lib/gitlab/ci/config/external/processor.rb +++ b/lib/gitlab/ci/config/external/processor.rb @@ -32,9 +32,7 @@ module Gitlab def validate_external_files! @external_files.each do |file| - logger.instrument(:config_external_verify) do - raise IncludeError, file.error_message unless file.valid? - end + raise IncludeError, file.error_message unless file.valid? end end diff --git a/lib/gitlab/ci/pipeline/chain/seed.rb b/lib/gitlab/ci/pipeline/chain/seed.rb index 3f5df5ce71c..ae98c55e425 100644 --- a/lib/gitlab/ci/pipeline/chain/seed.rb +++ b/lib/gitlab/ci/pipeline/chain/seed.rb @@ -62,12 +62,10 @@ module Gitlab def root_variables strong_memoize(:root_variables) do - logger.instrument(:pipeline_seed_merge_variables, once: true) do - ::Gitlab::Ci::Variables::Helpers.merge_variables( - @command.yaml_processor_result.root_variables, - @command.workflow_rules_result.variables - ) - end + ::Gitlab::Ci::Variables::Helpers.merge_variables( + @command.yaml_processor_result.root_variables, + @command.workflow_rules_result.variables + ) end end end diff --git a/lib/gitlab/memory/watchdog/configuration.rb b/lib/gitlab/memory/watchdog/configuration.rb index 4bad9475531..885772d6119 100644 --- a/lib/gitlab/memory/watchdog/configuration.rb +++ b/lib/gitlab/memory/watchdog/configuration.rb @@ -10,7 +10,6 @@ module Gitlab end def push(monitor_class, *args, **kwargs, &block) - remove(monitor_class) @monitors.push(build_monitor_state(monitor_class, *args, **kwargs, &block)) end @@ -22,14 +21,11 @@ module Gitlab private - def remove(monitor_class) - @monitors.delete_if { |monitor| monitor.monitor_class == monitor_class } - end - - def build_monitor_state(monitor_class, *args, max_strikes:, **kwargs, &block) + def build_monitor_state(monitor_class, *args, max_strikes:, monitor_name: nil, **kwargs, &block) monitor = build_monitor(monitor_class, *args, **kwargs, &block) + monitor_name ||= monitor_class.name.demodulize.underscore - Gitlab::Memory::Watchdog::MonitorState.new(monitor, max_strikes: max_strikes) + Gitlab::Memory::Watchdog::MonitorState.new(monitor, max_strikes: max_strikes, monitor_name: monitor_name) end def build_monitor(monitor_class, *args, **kwargs, &block) diff --git a/lib/gitlab/memory/watchdog/configurator.rb b/lib/gitlab/memory/watchdog/configurator.rb index b6917873e5a..610d8ca9e97 100644 --- a/lib/gitlab/memory/watchdog/configurator.rb +++ b/lib/gitlab/memory/watchdog/configurator.rb @@ -4,25 +4,33 @@ module Gitlab module Memory class Watchdog class Configurator + DEFAULT_PUMA_WORKER_RSS_LIMIT_MB = 1200 + DEFAULT_SLEEP_INTERVAL_S = 60 + DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S = 3 + MIN_SIDEKIQ_SLEEP_INTERVAL_S = 2 + DEFAULT_MAX_STRIKES = 5 + DEFAULT_MAX_HEAP_FRAG = 0.5 + DEFAULT_MAX_MEM_GROWTH = 3.0 + # grace_time / sleep_interval = max_strikes allowed for Sidekiq process to violate defined limits. + DEFAULT_SIDEKIQ_GRACE_TIME_S = 300 + class << self def configure_for_puma - lambda do |config| + ->(config) do config.logger = Gitlab::AppLogger config.handler = Gitlab::Memory::Watchdog::PumaHandler.new config.write_heap_dumps = write_heap_dumps? - config.sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', 60).to_i + config.sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', DEFAULT_SLEEP_INTERVAL_S).to_i config.monitors(&configure_monitors_for_puma) end end def configure_for_sidekiq - lambda do |config| + ->(config) do config.logger = Sidekiq.logger config.handler = Gitlab::Memory::Watchdog::TermProcessHandler.new config.write_heap_dumps = write_heap_dumps? - config.sleep_time_seconds = [ - ENV.fetch('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', 3).to_i, 2 - ].max + config.sleep_time_seconds = sidekiq_sleep_time config.monitors(&configure_monitors_for_sidekiq) end end @@ -34,12 +42,12 @@ module Gitlab end def configure_monitors_for_puma - lambda do |stack| - max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', 5).to_i + ->(stack) do + max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', DEFAULT_MAX_STRIKES).to_i if Gitlab::Utils.to_boolean(ENV['DISABLE_PUMA_WORKER_KILLER']) - max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.5).to_f - max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', 3.0).to_f + max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', DEFAULT_MAX_HEAP_FRAG).to_f + max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', DEFAULT_MAX_MEM_GROWTH).to_f # stack.push MonitorClass, args*, max_strikes:, kwargs**, &block stack.push Gitlab::Memory::Watchdog::Monitor::HeapFragmentation, @@ -50,17 +58,44 @@ module Gitlab max_mem_growth: max_mem_growth, max_strikes: max_strikes else - memory_limit = ENV.fetch('PUMA_WORKER_MAX_MEMORY', 1200).to_i + memory_limit = ENV.fetch('PUMA_WORKER_MAX_MEMORY', DEFAULT_PUMA_WORKER_RSS_LIMIT_MB).to_i stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit, - memory_limit: memory_limit.megabytes, + memory_limit_bytes: memory_limit.megabytes, max_strikes: max_strikes end end end + def sidekiq_sleep_time + [ + ENV.fetch('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S).to_i, + MIN_SIDEKIQ_SLEEP_INTERVAL_S + ].max + end + def configure_monitors_for_sidekiq - # NOP - At the moment we don't run watchdog for Sidekiq + ->(stack) do + if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.nonzero? + soft_limit_bytes = ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.kilobytes + grace_time = ENV.fetch('SIDEKIQ_MEMORY_KILLER_GRACE_TIME', DEFAULT_SIDEKIQ_GRACE_TIME_S).to_i + max_strikes = grace_time / sidekiq_sleep_time + + stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit, + memory_limit_bytes: soft_limit_bytes, + max_strikes: max_strikes.to_i, + monitor_name: :rss_memory_soft_limit + end + + if ENV['SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS'].to_i.nonzero? + hard_limit_bytes = ENV['SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS'].to_i.kilobytes + + stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit, + memory_limit_bytes: hard_limit_bytes, + max_strikes: 0, + monitor_name: :rss_memory_hard_limit + end + end end end end diff --git a/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb b/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb index 8f230980eac..ce99b68464e 100644 --- a/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb +++ b/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb @@ -4,10 +4,7 @@ module Gitlab module Memory class Watchdog module Monitor - # A monitor that observes Ruby heap fragmentation and calls - # memory_violation_callback when the Ruby heap has been fragmented for an extended - # period of time. - # + # A monitor that observes Ruby heap fragmentation. # See Gitlab::Metrics::Memory for how heap fragmentation is defined. class HeapFragmentation attr_reader :max_heap_fragmentation diff --git a/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb b/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb index 3e7de024630..bcd122f0090 100644 --- a/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb +++ b/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb @@ -5,27 +5,27 @@ module Gitlab class Watchdog module Monitor class RssMemoryLimit - attr_reader :memory_limit + attr_reader :memory_limit_bytes - def initialize(memory_limit:) - @memory_limit = memory_limit + def initialize(memory_limit_bytes:) + @memory_limit_bytes = memory_limit_bytes end def call - worker_rss = Gitlab::Metrics::System.memory_usage_rss[:total] + worker_rss_bytes = Gitlab::Metrics::System.memory_usage_rss[:total] - return { threshold_violated: false, payload: {} } if worker_rss <= memory_limit + return { threshold_violated: false, payload: {} } if worker_rss_bytes <= memory_limit_bytes - { threshold_violated: true, payload: payload(worker_rss, memory_limit) } + { threshold_violated: true, payload: payload(worker_rss_bytes, memory_limit_bytes) } end private - def payload(worker_rss, memory_limit) + def payload(worker_rss_bytes, memory_limit_bytes) { message: 'rss memory limit exceeded', - memwd_rss_bytes: worker_rss, - memwd_max_rss_bytes: memory_limit + memwd_rss_bytes: worker_rss_bytes, + memwd_max_rss_bytes: memory_limit_bytes } end end diff --git a/lib/gitlab/memory/watchdog/monitor_state.rb b/lib/gitlab/memory/watchdog/monitor_state.rb index 73be5de3e45..2562599d2ab 100644 --- a/lib/gitlab/memory/watchdog/monitor_state.rb +++ b/lib/gitlab/memory/watchdog/monitor_state.rb @@ -5,12 +5,12 @@ module Gitlab class Watchdog class MonitorState class Result - attr_reader :payload + attr_reader :payload, :monitor_name - def initialize(strikes_exceeded:, threshold_violated:, monitor_class:, payload: ) + def initialize(strikes_exceeded:, threshold_violated:, monitor_name:, payload: ) @strikes_exceeded = strikes_exceeded @threshold_violated = threshold_violated - @monitor_class = monitor_class + @monitor_name = monitor_name.to_s.to_sym @payload = payload end @@ -21,15 +21,12 @@ module Gitlab def threshold_violated? @threshold_violated end - - def monitor_name - @monitor_class.name.demodulize.underscore.to_sym - end end - def initialize(monitor, max_strikes:) + def initialize(monitor, max_strikes:, monitor_name:) @monitor = monitor @max_strikes = max_strikes + @monitor_name = monitor_name @strikes = 0 end @@ -47,16 +44,12 @@ module Gitlab build_result(monitor_result) end - def monitor_class - @monitor.class - end - private def build_result(monitor_result) Result.new( strikes_exceeded: strikes_exceeded?, - monitor_class: monitor_class, + monitor_name: @monitor_name, threshold_violated: monitor_result[:threshold_violated], payload: payload.merge(monitor_result[:payload])) end diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index 51a5bedc44b..40f5359adcf 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -60,6 +60,7 @@ module Gitlab ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/third-party-logos/dotnet.svg'), ProjectTemplate.new('android', 'Android', _('A ready-to-go template for use with Android apps'), 'https://gitlab.com/gitlab-org/project-templates/android', 'illustrations/logos/android.svg'), ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development'), 'https://gitlab.com/gitlab-org/project-templates/go-micro', 'illustrations/logos/gomicro.svg'), + ProjectTemplate.new('bridgetown', 'Pages/Bridgetown', _('Everything you need to create a GitLab Pages site using Bridgetown'), 'https://gitlab.com/gitlab-org/project-templates/bridgetown'), ProjectTemplate.new('gatsby', 'Pages/Gatsby', _('Everything you need to create a GitLab Pages site using Gatsby'), 'https://gitlab.com/pages/gatsby', 'illustrations/third-party-logos/gatsby.svg'), ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo'), 'https://gitlab.com/pages/hugo', 'illustrations/logos/hugo.svg'), ProjectTemplate.new('pelican', 'Pages/Pelican', _('Everything you need to create a GitLab Pages site using Pelican'), 'https://gitlab.com/pages/pelican', 'illustrations/third-party-logos/pelican.svg'), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 78b870a8266..2d2327ee463 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16167,6 +16167,9 @@ msgstr "" msgid "Everything on your to-do list is marked as done." msgstr "" +msgid "Everything you need to create a GitLab Pages site using Bridgetown" +msgstr "" + msgid "Everything you need to create a GitLab Pages site using Gatsby" msgstr "" @@ -32557,6 +32560,9 @@ msgstr "" msgid "ProjectTemplates|NodeJS Express" msgstr "" +msgid "ProjectTemplates|Pages/Bridgetown" +msgstr "" + msgid "ProjectTemplates|Pages/Gatsby" msgstr "" diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb index 93a47a40a7e..985edf3efc1 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb @@ -4,7 +4,7 @@ # rubocop:disable Rails/Pluck, Layout/LineLength, RSpec/MultipleMemoizedHelpers module QA - RSpec.describe "Manage", only: { job: "large-gitlab-import" } do + RSpec.describe "Manage", :skip_live_env, only: { job: "large-gitlab-import" } do describe "Gitlab migration", orchestrated: false, product_group: :import do include_context "with gitlab group migration" diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 37db26096d3..0521c5e02a8 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -54,7 +54,7 @@ RSpec.describe Groups::LabelsController do get :index, params: { group_id: group.to_param } end - it 'avoids N+1 queries' do + it 'avoids N+1 queries', :use_clean_rails_redis_caching do control = ActiveRecord::QueryRecorder.new(skip_cached: false) { get :index, params: { group_id: group.to_param } } create_list(:group_label, 3, group: group) diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index a5259522fe2..dfa6ed639b6 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -100,7 +100,7 @@ RSpec.describe Projects::LabelsController do list_labels end - it 'avoids N+1 queries' do + it 'avoids N+1 queries', :use_clean_rails_redis_caching do control = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_labels } create_list(:label, 3, project: project) diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js index a105b0b165c..ade36cd1637 100644 --- a/spec/frontend/flash_spec.js +++ b/spec/frontend/flash_spec.js @@ -12,6 +12,9 @@ import createFlash, { jest.mock('@sentry/browser'); describe('Flash', () => { + const findTextContent = (containerSelector = '.flash-container') => + document.querySelector(containerSelector).textContent.replace(/\s+/g, ' ').trim(); + describe('hideFlash', () => { let el; @@ -99,7 +102,7 @@ describe('Flash', () => { it('adds alert element into the document by default', () => { alert = createAlert({ message: mockMessage }); - expect(document.querySelector('.flash-container').textContent.trim()).toBe(mockMessage); + expect(findTextContent()).toBe(mockMessage); expect(document.querySelector('.flash-container .gl-alert')).not.toBeNull(); }); @@ -202,8 +205,7 @@ describe('Flash', () => { message: mockMessage, }); - const text = document.querySelector('.flash-container').textContent.trim(); - expect(text).toBe(`${mockTitle} ${mockMessage}`); + expect(findTextContent()).toBe(`${mockTitle} ${mockMessage}`); }); }); @@ -319,6 +321,22 @@ describe('Flash', () => { }); }); }); + + describe('when called multiple times', () => { + it('clears previous alerts', () => { + createAlert({ message: 'message 1' }); + createAlert({ message: 'message 2' }); + + expect(findTextContent()).toBe('message 2'); + }); + + it('preserves alerts when `preservePrevious` is true', () => { + createAlert({ message: 'message 1' }); + createAlert({ message: 'message 2', preservePrevious: true }); + + expect(findTextContent()).toBe('message 1 message 2'); + }); + }); }); }); diff --git a/spec/lib/gitlab/memory/watchdog/configuration_spec.rb b/spec/lib/gitlab/memory/watchdog/configuration_spec.rb index 38a39f6a33a..8c9b26ce2c4 100644 --- a/spec/lib/gitlab/memory/watchdog/configuration_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/configuration_spec.rb @@ -38,6 +38,8 @@ RSpec.describe Gitlab::Memory::Watchdog::Configuration do describe '#monitors' do context 'when monitors are configured to be used' do + let(:monitor_name1) { :monitor1 } + let(:monitor_name2) { :monitor2 } let(:payload1) do { message: 'monitor_1_text', @@ -96,7 +98,7 @@ RSpec.describe Gitlab::Memory::Watchdog::Configuration do expect(payloads).to eq([payload1, payload2]) expect(thresholds).to eq([false, true]) expect(strikes).to eq([false, true]) - expect(monitor_names).to eq([:monitor1, :monitor2]) + expect(monitor_names).to eq([monitor_name1, monitor_name2]) end end @@ -119,18 +121,19 @@ RSpec.describe Gitlab::Memory::Watchdog::Configuration do include_examples 'executes monitors and returns correct results' end - end - context 'when same monitor class is configured twice' do - before do - configuration.monitors.push monitor_class_1, max_strikes: 1 - configuration.monitors.push monitor_class_1, max_strikes: 1 - end + context 'when monitors are configured with monitor name' do + let(:monitor_name1) { :mon_one } + let(:monitor_name2) { :mon_two } - it 'calls same monitor only once' do - expect do |b| - configuration.monitors.call_each(&b) - end.to yield_control.once + before do + configuration.monitors do |stack| + stack.push monitor_class_1, false, { message: 'monitor_1_text' }, max_strikes: 5, monitor_name: :mon_one + stack.push monitor_class_2, true, { message: 'monitor_2_text' }, max_strikes: 0, monitor_name: :mon_two + end + end + + include_examples 'executes monitors and returns correct results' end end end diff --git a/spec/lib/gitlab/memory/watchdog/configurator_spec.rb b/spec/lib/gitlab/memory/watchdog/configurator_spec.rb index 18da85eaaed..ec61d027329 100644 --- a/spec/lib/gitlab/memory/watchdog/configurator_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/configurator_spec.rb @@ -130,11 +130,11 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do it_behaves_like 'as configurator', Gitlab::Memory::Watchdog::PumaHandler, 'GITLAB_MEMWD_SLEEP_TIME_SEC', - 60 + described_class::DEFAULT_SLEEP_INTERVAL_S context 'with DISABLE_PUMA_WORKER_KILLER set to true' do - let(:primary_memory) { 2048 } - let(:worker_memory) { max_mem_growth * primary_memory + 1 } + let(:primary_memory_bytes) { 2_097_152_000 } + let(:worker_memory_bytes) { max_mem_growth * primary_memory_bytes + 1 } let(:expected_payloads) do { heap_fragmentation: { @@ -147,9 +147,9 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do }, unique_memory_growth: { message: 'memory limit exceeded', - memwd_uss_bytes: worker_memory, - memwd_ref_uss_bytes: primary_memory, - memwd_max_uss_bytes: max_mem_growth * primary_memory, + memwd_uss_bytes: worker_memory_bytes, + memwd_ref_uss_bytes: primary_memory_bytes, + memwd_max_uss_bytes: max_mem_growth * primary_memory_bytes, memwd_max_strikes: max_strikes, memwd_cur_strikes: 1 } @@ -159,10 +159,10 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do before do stub_env('DISABLE_PUMA_WORKER_KILLER', true) allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(max_heap_fragmentation + 0.1) - allow(Gitlab::Metrics::System).to receive(:memory_usage_uss_pss).and_return({ uss: worker_memory }) + allow(Gitlab::Metrics::System).to receive(:memory_usage_uss_pss).and_return({ uss: worker_memory_bytes }) allow(Gitlab::Metrics::System).to receive(:memory_usage_uss_pss).with( pid: Gitlab::Cluster::PRIMARY_PID - ).and_return({ uss: primary_memory }) + ).and_return({ uss: primary_memory_bytes }) end context 'when settings are set via environment variables' do @@ -180,21 +180,22 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do end context 'when settings are not set via environment variables' do - let(:max_heap_fragmentation) { 0.5 } - let(:max_mem_growth) { 3.0 } - let(:max_strikes) { 5 } + let(:max_heap_fragmentation) { described_class::DEFAULT_MAX_HEAP_FRAG } + let(:max_mem_growth) { described_class::DEFAULT_MAX_MEM_GROWTH } + let(:max_strikes) { described_class::DEFAULT_MAX_STRIKES } it_behaves_like 'as monitor configurator' end end context 'with DISABLE_PUMA_WORKER_KILLER set to false' do + let(:memory_limit_bytes) { memory_limit_mb.megabytes } let(:expected_payloads) do { rss_memory_limit: { message: 'rss memory limit exceeded', - memwd_rss_bytes: memory_limit + 1, - memwd_max_rss_bytes: memory_limit, + memwd_rss_bytes: memory_limit_bytes + 1, + memwd_max_rss_bytes: memory_limit_bytes, memwd_max_strikes: max_strikes, memwd_cur_strikes: 1 } @@ -203,15 +204,15 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do before do stub_env('DISABLE_PUMA_WORKER_KILLER', false) - allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: memory_limit + 1 }) + allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: memory_limit_bytes + 1 }) end context 'when settings are set via environment variables' do - let(:memory_limit) { 1300.megabytes } + let(:memory_limit_mb) { 1300 } let(:max_strikes) { 4 } before do - stub_env('PUMA_WORKER_MAX_MEMORY', 1300) + stub_env('PUMA_WORKER_MAX_MEMORY', memory_limit_mb) stub_env('GITLAB_MEMWD_MAX_STRIKES', 4) end @@ -219,8 +220,8 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do end context 'when settings are not set via environment variables' do - let(:memory_limit) { 1200.megabytes } - let(:max_strikes) { 5 } + let(:memory_limit_mb) { described_class::DEFAULT_PUMA_WORKER_RSS_LIMIT_MB } + let(:max_strikes) { described_class::DEFAULT_MAX_STRIKES } it_behaves_like 'as monitor configurator' end @@ -236,6 +237,113 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do it_behaves_like 'as configurator', Gitlab::Memory::Watchdog::TermProcessHandler, 'SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', - 3 + described_class::DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S + + context 'when sleep_time_seconds is less than MIN_SIDEKIQ_SLEEP_INTERVAL_S seconds' do + before do + stub_env('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', 0) + end + + it 'configures the correct sleep time' do + configurator.call(configuration) + + expect(configuration.sleep_time_seconds).to eq(described_class::MIN_SIDEKIQ_SLEEP_INTERVAL_S) + end + end + + context 'with monitors' do + let(:soft_limit_bytes) { soft_limit_kb.kilobytes } + let(:hard_limit_bytes) { hard_limit_kb.kilobytes } + + context 'when settings are set via environment variables' do + let(:soft_limit_kb) { 2000001 } + let(:hard_limit_kb) { 300000 } + let(:max_strikes) { 150 } + let(:grace_time) { 300 } + let(:expected_payloads) do + { + rss_memory_soft_limit: { + message: 'rss memory limit exceeded', + memwd_rss_bytes: soft_limit_bytes + 1, + memwd_max_rss_bytes: soft_limit_bytes, + memwd_max_strikes: max_strikes, + memwd_cur_strikes: 1 + }, + rss_memory_hard_limit: { + message: 'rss memory limit exceeded', + memwd_rss_bytes: hard_limit_bytes + 1, + memwd_max_rss_bytes: hard_limit_bytes, + memwd_max_strikes: 0, + memwd_cur_strikes: 1 + } + } + end + + before do + stub_env('SIDEKIQ_MEMORY_KILLER_MAX_RSS', soft_limit_kb) + stub_env('SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS', hard_limit_kb) + stub_env('SIDEKIQ_MEMORY_KILLER_GRACE_TIME', grace_time) + stub_env('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', 2) + allow(Gitlab::Metrics::System).to receive(:memory_usage_rss) + .and_return({ total: soft_limit_bytes + 1 }, { total: hard_limit_bytes + 1 }) + end + + it_behaves_like 'as monitor configurator' + end + + context 'when only SIDEKIQ_MEMORY_KILLER_MAX_RSS is set via environment variable' do + let(:soft_limit_kb) { 2000000 } + let(:max_strikes) do + described_class::DEFAULT_SIDEKIQ_GRACE_TIME_S / described_class::DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S + end + + let(:expected_payloads) do + { + rss_memory_soft_limit: { + message: 'rss memory limit exceeded', + memwd_rss_bytes: soft_limit_bytes + 1, + memwd_max_rss_bytes: soft_limit_bytes, + memwd_max_strikes: max_strikes, + memwd_cur_strikes: 1 + } + } + end + + before do + allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: soft_limit_bytes + 1 }) + stub_env('SIDEKIQ_MEMORY_KILLER_MAX_RSS', soft_limit_kb) + end + + it_behaves_like 'as monitor configurator' + end + + context 'when only SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS is set via environment variable' do + let(:hard_limit_kb) { 2000000 } + let(:expected_payloads) do + { + rss_memory_hard_limit: { + message: 'rss memory limit exceeded', + memwd_rss_bytes: hard_limit_bytes + 1, + memwd_max_rss_bytes: hard_limit_bytes, + memwd_max_strikes: 0, + memwd_cur_strikes: 1 + } + } + end + + before do + allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: hard_limit_bytes + 1 }) + stub_env('SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS', hard_limit_kb) + end + + it_behaves_like 'as monitor configurator' + end + + context 'when both SIDEKIQ_MEMORY_KILLER_MAX_RSS and SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS are not set' do + let(:expected_payloads) { {} } + + it_behaves_like 'as monitor configurator' + end + end end end diff --git a/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb b/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb index 9e25cfda782..fffe5d5ff00 100644 --- a/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb @@ -4,25 +4,25 @@ require 'fast_spec_helper' require 'support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples' RSpec.describe Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit do - let(:memory_limit) { 2048 } - let(:worker_memory) { 1024 } + let(:memory_limit_bytes) { 2_097_152_000 } + let(:worker_memory_bytes) { 1_048_576_000 } subject(:monitor) do - described_class.new(memory_limit: memory_limit) + described_class.new(memory_limit_bytes: memory_limit_bytes) end before do - allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: worker_memory }) + allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: worker_memory_bytes }) end describe '#call' do context 'when process exceeds threshold' do - let(:worker_memory) { memory_limit + 1 } + let(:worker_memory_bytes) { memory_limit_bytes + 1 } let(:payload) do { message: 'rss memory limit exceeded', - memwd_rss_bytes: worker_memory, - memwd_max_rss_bytes: memory_limit + memwd_rss_bytes: worker_memory_bytes, + memwd_max_rss_bytes: memory_limit_bytes } end @@ -30,7 +30,7 @@ RSpec.describe Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit do end context 'when process does not exceed threshold' do - let(:worker_memory) { memory_limit - 1 } + let(:worker_memory_bytes) { memory_limit_bytes - 1 } let(:payload) { {} } include_examples 'returns Watchdog Monitor result', threshold_violated: false diff --git a/spec/lib/gitlab/memory/watchdog/monitor_state_spec.rb b/spec/lib/gitlab/memory/watchdog/monitor_state_spec.rb index ace1353c6e3..7802e274c53 100644 --- a/spec/lib/gitlab/memory/watchdog/monitor_state_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/monitor_state_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Gitlab::Memory::Watchdog::MonitorState do let(:payload) { { message: 'DummyMessage' } } let(:threshold_violated) { true } let(:monitor) { monitor_class.new(threshold_violated, payload) } + let(:monitor_name) { :dummy_monitor_name } let(:monitor_class) do Struct.new(:threshold_violated, :payload) do def call @@ -19,7 +20,7 @@ RSpec.describe Gitlab::Memory::Watchdog::MonitorState do end end - subject(:monitor_state) { described_class.new(monitor, max_strikes: max_strikes) } + subject(:monitor_state) { described_class.new(monitor, max_strikes: max_strikes, monitor_name: monitor_name) } shared_examples 'returns correct result' do it 'returns correct result', :aggregate_failures do @@ -29,7 +30,7 @@ RSpec.describe Gitlab::Memory::Watchdog::MonitorState do expect(result.strikes_exceeded?).to eq(strikes_exceeded) expect(result.threshold_violated?).to eq(threshold_violated) expect(result.payload).to eq(expected_payload) - expect(result.monitor_name).to eq(:monitor_name) + expect(result.monitor_name).to eq(monitor_name) end end @@ -63,10 +64,4 @@ RSpec.describe Gitlab::Memory::Watchdog::MonitorState do end end end - - describe '#monitor_class' do - subject { monitor_state.monitor_class } - - it { is_expected.to eq(monitor_class) } - end end diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 3028f49a49c..65ef9308485 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -6,7 +6,6 @@ RSpec.describe Ci::BuildMetadata do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, group: group, build_timeout: 2000) } - let_it_be(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.id, @@ -14,7 +13,9 @@ RSpec.describe Ci::BuildMetadata do status: 'success') end - let(:job) { create(:ci_build, pipeline: pipeline) } + let_it_be_with_reload(:runner) { create(:ci_runner) } + + let(:job) { create(:ci_build, pipeline: pipeline, runner: runner) } let(:metadata) { job.metadata } it_behaves_like 'having unique enum values' @@ -32,63 +33,110 @@ RSpec.describe Ci::BuildMetadata do end end - context 'when project timeout is set' do - context 'when runner is assigned to the job' do + context 'when job, project and runner timeouts are set' do + context 'when job timeout is lower then runner timeout' do before do - job.update!(runner: runner) + runner.update!(maximum_timeout: 4000) + job.update!(options: { job_timeout: 3000 }) end - context 'when runner timeout is not set' do - let(:runner) { create(:ci_runner, maximum_timeout: nil) } - - it_behaves_like 'sets timeout', 'project_timeout_source', 2000 - end - - context 'when runner timeout is lower than project timeout' do - let(:runner) { create(:ci_runner, maximum_timeout: 1900) } - - it_behaves_like 'sets timeout', 'runner_timeout_source', 1900 - end - - context 'when runner timeout is higher than project timeout' do - let(:runner) { create(:ci_runner, maximum_timeout: 2100) } - - it_behaves_like 'sets timeout', 'project_timeout_source', 2000 - end + it_behaves_like 'sets timeout', 'job_timeout_source', 3000 end - context 'when job timeout is set' do - context 'when job timeout is higher than project timeout' do - let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } - - it_behaves_like 'sets timeout', 'job_timeout_source', 3000 + context 'when runner timeout is lower then job timeout' do + before do + runner.update!(maximum_timeout: 2000) + job.update!(options: { job_timeout: 3000 }) end - context 'when job timeout is lower than project timeout' do - let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1000 }) } + it_behaves_like 'sets timeout', 'runner_timeout_source', 2000 + end + end - it_behaves_like 'sets timeout', 'job_timeout_source', 1000 + context 'when job, project timeout values are set and runner is assigned' do + context 'when runner has no timeout set' do + before do + runner.update!(maximum_timeout: nil) + job.update!(options: { job_timeout: 3000 }) end + + it_behaves_like 'sets timeout', 'job_timeout_source', 3000 + end + end + + context 'when only job and project timeouts are defined' do + context 'when job timeout is lower then project timeout' do + before do + job.update!(options: { job_timeout: 1000 }) + end + + it_behaves_like 'sets timeout', 'job_timeout_source', 1000 end - context 'when both runner and job timeouts are set' do + context 'when project timeout is lower then job timeout' do before do - job.update!(runner: runner) + job.update!(options: { job_timeout: 3000 }) end - context 'when job timeout is higher than runner timeout' do - let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } - let(:runner) { create(:ci_runner, maximum_timeout: 2100) } + it_behaves_like 'sets timeout', 'job_timeout_source', 3000 + end + end - it_behaves_like 'sets timeout', 'runner_timeout_source', 2100 + context 'when only project and runner timeouts are defined' do + before do + runner.update!(maximum_timeout: 1900) + end + + context 'when runner timeout is lower then project timeout' do + it_behaves_like 'sets timeout', 'runner_timeout_source', 1900 + end + + context 'when project timeout is lower then runner timeout' do + before do + runner.update!(maximum_timeout: 2100) end - context 'when job timeout is lower than runner timeout' do - let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1900 }) } - let(:runner) { create(:ci_runner, maximum_timeout: 2100) } + it_behaves_like 'sets timeout', 'project_timeout_source', 2000 + end + end - it_behaves_like 'sets timeout', 'job_timeout_source', 1900 + context 'when only job and runner timeouts are defined' do + context 'when runner timeout is lower them job timeout' do + before do + job.update!(options: { job_timeout: 2000 }) + runner.update!(maximum_timeout: 1900) end + + it_behaves_like 'sets timeout', 'runner_timeout_source', 1900 + end + + context 'when job timeout is lower them runner timeout' do + before do + job.update!(options: { job_timeout: 1000 }) + runner.update!(maximum_timeout: 1900) + end + + it_behaves_like 'sets timeout', 'job_timeout_source', 1000 + end + end + + context 'when only job timeout is defined and runner is assigned, but has no timeout set' do + before do + job.update!(options: { job_timeout: 1000 }) + runner.update!(maximum_timeout: nil) + end + + it_behaves_like 'sets timeout', 'job_timeout_source', 1000 + end + + context 'when only one timeout value is defined' do + context 'when only project timeout value is defined' do + before do + job.update!(options: { job_timeout: nil }) + runner.update!(maximum_timeout: nil) + end + + it_behaves_like 'sets timeout', 'project_timeout_source', 2000 end end end diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 05fe55b06a1..3087f0cac7d 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -44,7 +44,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures do it_behaves_like 'requires authentication' - it 'executes a limited number of queries' do + it 'executes a limited number of queries', :use_clean_rails_redis_caching do control_count = ActiveRecord::QueryRecorder.new { subject }.count expect(control_count).to be <= 111 diff --git a/spec/support/helpers/project_template_test_helper.rb b/spec/support/helpers/project_template_test_helper.rb index eab41f6a1cf..1990cd4551a 100644 --- a/spec/support/helpers/project_template_test_helper.rb +++ b/spec/support/helpers/project_template_test_helper.rb @@ -9,7 +9,7 @@ module ProjectTemplateTestHelper nfjekyll nfplainhtml nfgitbook nfhexo salesforcedx serverless_framework tencent_serverless_framework jsonnet cluster_management kotlin_native_linux - pelican + pelican bridgetown ] end end diff --git a/vendor/project_templates/bridgetown.tar.gz b/vendor/project_templates/bridgetown.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1fb89694d0f1efe83a909f64eb528e78cf0dcb85 GIT binary patch literal 42296 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ⅈ6|46c2tCW=+$yH6Hf8vZ$%fo(^grWl9O}8_zR_9FVo@!_J7y+b4+%qp; zyMq1gFRP`%dp%}p2n2^J8qe6}IK!vX*{_wLGF0CMw8f!5>%@XASKh-CN2{;sVfmfz zB}`_HzN%ZTm>*Mhz*Ftc%O-&lyF@0{)US=7t`@XBgfrC3H2V7txnq>Wk{G= z;X-gik74n`^^XdD%Ucam1Lv{0^&p1qQuTe!DQpdodvwykf?8>7DNs9O?%<#x{O@8L)6 ze+#qY%11OgBtr@xsUgKnau@hTJdR{c@f-CX0`$xt)t+XGPSy{~v&5+*Lt~HNog#}% zuOg>vgaH7>qpT`FR5$*vqBqX@Fbm9YAc4m#3A5dWOJaY!*1ezTql0HLsYe^((z{(gkd5lZ+;dVWbHX^OG{HC+s~Y6|WBuOrOt zaD`1XN2l2SRi!CYIOJqA59r1c_>mGiis7uj%>t4K4r8oTgmds=klo#6TpOOq$f`ZjY^q##`E*<`$i*x_= zz`MIYQs7876UybVk6ZL({U6SzZ2y?a?!@a706rgO{YeEN2Kpn0`A7VH{cL^2!~o8&e*ZN0ufG331q_jg%7f&*d>sGmNq+KYAg}NT z7XPpO%l?BP|ImMsGWciz|4#tPE6OYX*+cx~PeWe8*VEO@%hAtQ;Sc-AkA_1*pnuYT o(C_*W{&D{+f}j8~$R9S*@Awbz|4)AMlmA@)0!8>hvjAuT0EXW`n*aa+ literal 0 HcmV?d00001