Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-03 21:08:58 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-03 21:08:58 +0300
commit7c8468c5ba828e1c1afe6ba0b25c77c130a69413 (patch)
tree3104d62bff9040a19756b9d407003eed14314f77
parent2a7fd3827b0838a900399b0c3440942cdaa09c75 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS2
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/package-and-test/variables.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml5
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml2
-rw-r--r--.rubocop_todo/layout/line_length.yml4
-rw-r--r--.rubocop_todo/rspec/factory_bot/avoid_create.yml3
-rw-r--r--.rubocop_todo/rspec/missing_feature_category.yml3
-rw-r--r--.rubocop_todo/style/empty_method.yml1
-rw-r--r--.rubocop_todo/style/if_unless_modifier.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue10
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item.vue105
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js64
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue71
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/utils.js31
-rw-r--r--app/assets/javascripts/nav/components/new_nav_toggle.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue3
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/index.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue31
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss3
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss57
-rw-r--r--app/assets/stylesheets/pages/notes.scss56
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/groups/usage_quotas_controller.rb2
-rw-r--r--app/controllers/projects/usage_quotas_controller.rb2
-rw-r--r--app/helpers/packages_helper.rb5
-rw-r--r--app/helpers/system_note_helper.rb12
-rw-r--r--app/views/groups/packages/index.html.haml1
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml1
-rw-r--r--app/views/projects/packages/packages/index.html.haml1
-rw-r--r--app/workers/all_queues.yml4
-rw-r--r--app/workers/update_highest_role_worker.rb2
-rw-r--r--app/workers/users/deactivate_dormant_users_worker.rb2
-rw-r--r--config/feature_categories.yml5
-rw-r--r--config/metrics/settings/20221015152126_deactivate_dormant_users_enabled.yml2
-rw-r--r--config/metrics/settings/20221015161233_deactivate_dormant_users_period.yml2
-rw-r--r--db/docs/namespace_aggregation_schedules.yml2
-rw-r--r--db/docs/namespace_limits.yml2
-rw-r--r--db/docs/namespaces_storage_limit_exclusions.yml2
-rw-r--r--doc/development/database/multiple_databases.md18
-rw-r--r--doc/user/profile/achievements.md9
-rw-r--r--locale/gitlab.pot156
-rw-r--r--qa/qa/page/main/menu.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb78
-rw-r--r--spec/features/admin/admin_health_check_spec.rb2
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb2
-rw-r--r--spec/features/boards/board_filters_spec.rb2
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb2
-rw-r--r--spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item_spec.js70
-rw-r--r--spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js57
-rw-r--r--spec/frontend/fixtures/projects.rb2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/list_spec.js56
-rw-r--r--spec/frontend/projects/commit/components/branches_dropdown_spec.js4
-rw-r--r--spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js61
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js10
-rw-r--r--spec/helpers/packages_helper_spec.rb69
-rw-r--r--spec/migrations/20220802204737_remove_deactivated_user_highest_role_stats_spec.rb2
-rw-r--r--spec/migrations/20221104115712_backfill_project_statistics_storage_size_without_uploads_size_spec.rb2
-rw-r--r--spec/migrations/20221221110733_remove_temp_index_for_project_statistics_upload_size_migration_spec.rb2
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb2
-rw-r--r--spec/requests/groups/usage_quotas_controller_spec.rb2
-rw-r--r--spec/requests/projects/usage_quotas_spec.rb2
-rw-r--r--spec/support/rspec_order_todo.yml5
-rw-r--r--spec/views/groups/packages/index.html.haml_spec.rb39
-rw-r--r--spec/views/projects/packages/index.html.haml_spec.rb39
-rw-r--r--spec/workers/update_highest_role_worker_spec.rb2
-rw-r--r--spec/workers/users/deactivate_dormant_users_worker_spec.rb2
-rw-r--r--spec/workers/users/migrate_records_to_ghost_user_in_batches_worker_spec.rb2
77 files changed, 926 insertions, 337 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 4ef74436cc8..1557d5d7a3d 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -364,8 +364,6 @@ Dangerfile @gl-quality/eng-prod
/ee/spec/services/namespaces/in_product_marketing_emails_service_spec.rb @gitlab-org/growth/engineers
/app/workers/projects/record_target_platforms_worker.rb @gitlab-org/growth/engineers
/spec/workers/projects/record_target_platforms_worker_spec.rb @gitlab-org/growth/engineers
-/ee/app/controllers/groups/feature_discovery_moments_controller.rb @gitlab-org/growth/engineers
-/ee/spec/requests/groups/feature_discovery_moments_spec.rb @gitlab-org/growth/engineers
^[Legal]
/config/dependency_decisions.yml @gitlab-org/legal-reviewers
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index e9b4d90afeb..4d819268c61 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -126,6 +126,7 @@ trigger-omnibus-env:
echo "OMNIBUS_GITLAB_RUBY3_BUILD=${OMNIBUS_GITLAB_RUBY3_BUILD:-false}" >> $BUILD_ENV
echo "OMNIBUS_GITLAB_RUBY2_BUILD=${OMNIBUS_GITLAB_RUBY2_BUILD:-false}" >> $BUILD_ENV
echo "OMNIBUS_GITLAB_CACHE_EDITION=${OMNIBUS_GITLAB_CACHE_EDITION:-GITLAB}" >> $BUILD_ENV
+ echo "OMNIBUS_GITLAB_BUILD_ON_ALL_OS=${OMNIBUS_GITLAB_BUILD_ON_ALL_OS:-false}" >> $BUILD_ENV
echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV
echo "EE=$([[ $FOSS_ONLY == 'true' ]] && echo 'false' || echo 'true')" >> $BUILD_ENV
echo "Built environment file for omnibus build:"
@@ -158,6 +159,7 @@ trigger-omnibus:
RUBY3_BUILD: $OMNIBUS_GITLAB_RUBY3_BUILD
RUBY2_BUILD: $OMNIBUS_GITLAB_RUBY2_BUILD
CACHE_EDITION: $OMNIBUS_GITLAB_CACHE_EDITION
+ BUILD_ON_ALL_OS: $OMNIBUS_GITLAB_BUILD_ON_ALL_OS
SKIP_QA_TEST: "true"
ee: $EE
trigger:
diff --git a/.gitlab/ci/package-and-test/variables.gitlab-ci.yml b/.gitlab/ci/package-and-test/variables.gitlab-ci.yml
index b7c4e5519ca..63a2b09fe04 100644
--- a/.gitlab/ci/package-and-test/variables.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/variables.gitlab-ci.yml
@@ -8,6 +8,7 @@ variables:
OMNIBUS_GITLAB_RUBY3_BUILD: "false"
OMNIBUS_GITLAB_RUBY2_BUILD: "false"
OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB"
+ OMNIBUS_GITLAB_BUILD_ON_ALL_OS: "false"
ALLURE_JOB_NAME: $CI_PROJECT_NAME
QA_LOG_LEVEL: "info"
QA_TESTS: ""
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 2f56678f8f5..a1caa17d5ab 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1212,6 +1212,11 @@
allow_failure: true
- <<: *if-ruby2-branch
allow_failure: true
+ - <<: *if-dot-com-gitlab-org-and-security-merge-request
+ changes: *dependency-patterns
+ allow_failure: true
+ variables:
+ OMNIBUS_GITLAB_BUILD_ON_ALL_OS: 'true'
- <<: *if-merge-request-labels-run-all-e2e
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-e2e
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index 6e24bf946bd..089d5a20ceb 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -63,7 +63,7 @@ verify-ruby-3.0:
verify-tests-yml:
extends:
- .setup:rules:verify-tests-yml
- image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-alpine3.13
+ image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-alpine3.16
stage: test
needs: []
script:
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index c549dd2b56d..a2752712f41 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -1045,7 +1045,6 @@ Layout/LineLength:
- 'ee/app/helpers/ee/trial_helper.rb'
- 'ee/app/helpers/epics_helper.rb'
- 'ee/app/helpers/gitlab_subscriptions/upcoming_reconciliation_helper.rb'
- - 'ee/app/helpers/groups/feature_discovery_moments_helper.rb'
- 'ee/app/helpers/groups/security_features_helper.rb'
- 'ee/app/helpers/groups/sso_helper.rb'
- 'ee/app/helpers/license_helper.rb'
@@ -1601,7 +1600,6 @@ Layout/LineLength:
- 'ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb'
- 'ee/spec/features/groups/audit_events_spec.rb'
- 'ee/spec/features/groups/billing_spec.rb'
- - 'ee/spec/features/groups/feature_discovery_moments_spec.rb'
- 'ee/spec/features/groups/group_roadmap_spec.rb'
- 'ee/spec/features/groups/group_settings_spec.rb'
- 'ee/spec/features/groups/groups_security_credentials_spec.rb'
@@ -2608,10 +2606,8 @@ Layout/LineLength:
- 'ee/spec/views/admin/application_settings/general.html.haml_spec.rb'
- 'ee/spec/views/admin/dashboard/index.html.haml_spec.rb'
- 'ee/spec/views/compliance_management/compliance_framework/_project_settings.html.haml_spec.rb'
- - 'ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb'
- 'ee/spec/views/groups/security/discover/show.html.haml_spec.rb'
- 'ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb'
- - 'ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb'
- 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb'
- 'ee/spec/views/operations/environments.html.haml_spec.rb'
- 'ee/spec/views/projects/security/discover/show.html.haml_spec.rb'
diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
index 09a66cacdb2..88e0d7c7a85 100644
--- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml
+++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
@@ -56,7 +56,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/helpers/ee/wiki_helper_spec.rb'
- 'ee/spec/helpers/epics_helper_spec.rb'
- 'ee/spec/helpers/gitlab_subscriptions/upcoming_reconciliation_helper_spec.rb'
- - 'ee/spec/helpers/groups/feature_discovery_moments_helper_spec.rb'
- 'ee/spec/helpers/groups/security_features_helper_spec.rb'
- 'ee/spec/helpers/incident_management/escalation_policy_helper_spec.rb'
- 'ee/spec/helpers/incident_management/oncall_schedule_helper_spec.rb'
@@ -202,7 +201,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/views/compliance_management/compliance_framework/_project_settings.html.haml_spec.rb'
- 'ee/spec/views/groups/billings/index.html.haml_spec.rb'
- 'ee/spec/views/groups/edit.html.haml_spec.rb'
- - 'ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb'
- 'ee/spec/views/groups/hook_logs/show.html.haml_spec.rb'
- 'ee/spec/views/groups/hooks/edit.html.haml_spec.rb'
- 'ee/spec/views/groups/security/discover/show.html.haml_spec.rb'
@@ -213,7 +211,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/views/layouts/group.html.haml_spec.rb'
- 'ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb'
- 'ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb'
- - 'ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb'
- 'ee/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb'
- 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb'
- 'ee/spec/views/layouts/project.html.haml_spec.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index 72336d8d0c4..c8e9dce6c74 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -529,7 +529,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/helpers/ee/wiki_helper_spec.rb'
- 'ee/spec/helpers/epics_helper_spec.rb'
- 'ee/spec/helpers/gitlab_subscriptions/upcoming_reconciliation_helper_spec.rb'
- - 'ee/spec/helpers/groups/feature_discovery_moments_helper_spec.rb'
- 'ee/spec/helpers/groups/ldap_sync_helper_spec.rb'
- 'ee/spec/helpers/groups/security_features_helper_spec.rb'
- 'ee/spec/helpers/groups/sso_helper_spec.rb'
@@ -1632,7 +1631,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/views/groups/compliance_frameworks/edit.html.haml_spec.rb'
- 'ee/spec/views/groups/compliance_frameworks/new.html.haml_spec.rb'
- 'ee/spec/views/groups/edit.html.haml_spec.rb'
- - 'ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb'
- 'ee/spec/views/groups/hook_logs/show.html.haml_spec.rb'
- 'ee/spec/views/groups/hooks/edit.html.haml_spec.rb'
- 'ee/spec/views/groups/security/discover/show.html.haml_spec.rb'
@@ -1645,7 +1643,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb'
- 'ee/spec/views/layouts/header/_ee_subscribable_banner.html.haml_spec.rb'
- 'ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb'
- - 'ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb'
- 'ee/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb'
- 'ee/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb'
- 'ee/spec/views/layouts/nav/sidebar/_push_rules_link.html.haml_spec.rb'
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml
index 7b0390f7d31..058e435c345 100644
--- a/.rubocop_todo/style/empty_method.yml
+++ b/.rubocop_todo/style/empty_method.yml
@@ -85,7 +85,6 @@ Style/EmptyMethod:
- 'ee/app/controllers/groups/analytics/cycle_analytics_controller.rb'
- 'ee/app/controllers/groups/analytics/devops_adoption_controller.rb'
- 'ee/app/controllers/groups/compliance_frameworks_controller.rb'
- - 'ee/app/controllers/groups/feature_discovery_moments_controller.rb'
- 'ee/app/controllers/groups/ldap_group_links_controller.rb'
- 'ee/app/controllers/groups/settings/reporting_controller.rb'
- 'ee/app/controllers/projects/analytics/code_reviews_controller.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index 208729cbce8..8afc3f97aee 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -668,7 +668,6 @@ Style/IfUnlessModifier:
- 'ee/spec/support/helpers/feature_approval_helper.rb'
- 'ee/spec/support/helpers/search_results_helpers.rb'
- 'ee/spec/support/http_io/http_io_helpers.rb'
- - 'ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb'
- 'ee/spec/workers/elastic/migration_worker_spec.rb'
- 'lib/api/api_guard.rb'
- 'lib/api/boards_responses.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index de0f925f962..7203cc8c611 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-d7baefaebfcd0beaee08f12d14080f64d4a7aae7
+763a199d4d1425815bdb7284356f2fe549edb1c3
diff --git a/Gemfile b/Gemfile
index f7962ba17d0..44c70bedf8a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -291,7 +291,7 @@ gem 'sanitize', '~> 6.0'
gem 'babosa', '~> 1.0.4'
# Sanitizes SVG input
-gem 'loofah', '~> 2.19.1'
+gem 'loofah', '~> 2.20.0'
# Working with license
# Detects the open source license the repository includes
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 0fccab1a66d..cc90d9da27d 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -338,7 +338,7 @@
{"name":"locale","version":"2.1.3","platform":"ruby","checksum":"b6ddee011e157817cb98e521b3ce7cb626424d5882f1e844aafdee3e8b212725"},
{"name":"lockbox","version":"1.1.1","platform":"ruby","checksum":"0af16b14c54f791c148615a0115387b51903d868c7fe622f49606c97071c2ac0"},
{"name":"lograge","version":"0.11.2","platform":"ruby","checksum":"4cbd1554b86f545d795eff15a0c24fd25057d2ac4e1caa5fc186168b3da932ef"},
-{"name":"loofah","version":"2.19.1","platform":"ruby","checksum":"6c6469efdefe3496010000a346f9d3bf710e11ac4661e353cf56852326fb1023"},
+{"name":"loofah","version":"2.20.0","platform":"ruby","checksum":"e8fa874c8e2cd2fbdbecc1b6badc3f713639796aaca3f6dd494c4737828a24d6"},
{"name":"lookbook","version":"1.5.3","platform":"ruby","checksum":"4a0ff475af85de0dcdf45a5541fbc40dd8f66669a559efe8297c1d7fee028b38"},
{"name":"lru_redux","version":"1.1.0","platform":"ruby","checksum":"ee71d0ccab164c51de146c27b480a68b3631d5b4297b8ffe8eda1c72de87affb"},
{"name":"lumberjack","version":"1.2.7","platform":"ruby","checksum":"a5c6aae6b4234f1420dbcd80b23e3bca0817bd239440dde097ebe3fa63c63b1f"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 6e3b013e787..4caa7b0af8c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -916,7 +916,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
- loofah (2.19.1)
+ loofah (2.20.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lookbook (1.5.3)
@@ -1803,7 +1803,7 @@ DEPENDENCIES
listen (~> 3.7)
lockbox (~> 1.1.1)
lograge (~> 0.5)
- loofah (~> 2.19.1)
+ loofah (~> 2.20.0)
lookbook (~> 1.5, >= 1.5.3)
lru_redux
mail (= 2.8.1)
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue
index c2ae7d7be49..c23a0b866d3 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue
@@ -20,14 +20,20 @@ export default {
<template>
<gl-accordion-item :title="$options.i18n.IMAGE">
<div class="gl-display-flex">
- <gl-form-group class="gl-flex-grow-1 gl-mr-3" :label="$options.i18n.IMAGE_NAME">
+ <gl-form-group
+ class="gl-flex-grow-1 gl-flex-basis-half gl-mr-3"
+ :label="$options.i18n.IMAGE_NAME"
+ >
<gl-form-input
:value="job.image.name"
data-testid="image-name-input"
@input="$emit('update-job', 'image.name', $event)"
/>
</gl-form-group>
- <gl-form-group class="gl-flex-grow-1" :label="$options.i18n.IMAGE_ENTRYPOINT">
+ <gl-form-group
+ class="gl-flex-grow-1 gl-flex-basis-half"
+ :label="$options.i18n.IMAGE_ENTRYPOINT"
+ >
<gl-form-input
:value="job.image.entrypoint.join(' ')"
data-testid="image-entrypoint-input"
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item.vue
new file mode 100644
index 00000000000..d068b370852
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item.vue
@@ -0,0 +1,105 @@
+<script>
+import {
+ GlFormGroup,
+ GlAccordionItem,
+ GlFormInput,
+ GlFormSelect,
+ GlFormCheckbox,
+} from '@gitlab/ui';
+import { i18n, JOB_RULES_WHEN, JOB_RULES_START_IN } from '../constants';
+
+export default {
+ i18n,
+ whenOptions: Object.values(JOB_RULES_WHEN),
+ unitOptions: Object.values(JOB_RULES_START_IN),
+ components: {
+ GlAccordionItem,
+ GlFormInput,
+ GlFormSelect,
+ GlFormCheckbox,
+ GlFormGroup,
+ },
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ isStartValid: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ startInNumber: 1,
+ startInUnit: JOB_RULES_START_IN.second.value,
+ };
+ },
+ computed: {
+ isDelayed() {
+ return this.job.rules[0].when === JOB_RULES_WHEN.delayed.value;
+ },
+ },
+ methods: {
+ updateStartIn() {
+ const plural = this.startInNumber > 1 ? 's' : '';
+ this.$emit(
+ 'update-job',
+ 'rules[0].start_in',
+ `${this.startInNumber} ${this.startInUnit}${plural}`,
+ );
+ },
+ },
+};
+</script>
+<template>
+ <gl-accordion-item :title="$options.i18n.RULES">
+ <div class="gl-display-flex">
+ <gl-form-group class="gl-flex-grow-1 gl-flex-basis-half gl-mr-3" :label="$options.i18n.WHEN">
+ <gl-form-select
+ class="gl-flex-grow-1 gl-flex-basis-half gl-mr-3"
+ :options="$options.whenOptions"
+ data-testid="rules-when-select"
+ :value="job.rules[0].when"
+ @input="$emit('update-job', 'rules[0].when', $event)"
+ />
+ </gl-form-group>
+ <gl-form-group
+ class="gl-flex-grow-1 gl-flex-basis-half"
+ :invalid-feedback="$options.i18n.INVALID_START_IN"
+ :state="isStartValid"
+ >
+ <div class="gl-display-flex gl-mt-5">
+ <gl-form-input
+ v-model="startInNumber"
+ class="gl-flex-grow-1 gl-flex-basis-half gl-mr-3"
+ data-testid="rules-start-in-number-input"
+ type="number"
+ :state="isStartValid"
+ :class="{ 'gl-visibility-hidden': !isDelayed }"
+ number
+ @input="updateStartIn"
+ />
+ <gl-form-select
+ v-model="startInUnit"
+ class="gl-flex-grow-1 gl-flex-basis-half"
+ data-testid="rules-start-in-unit-select"
+ :state="isStartValid"
+ :class="{ 'gl-visibility-hidden': !isDelayed }"
+ :options="$options.unitOptions"
+ @input="updateStartIn"
+ />
+ </div>
+ </gl-form-group>
+ </div>
+ <gl-form-group>
+ <gl-form-checkbox
+ :checked="job.rules[0].allow_failure"
+ data-testid="rules-allow-failure-checkbox"
+ @input="$emit('update-job', 'rules[0].allow_failure', $event)"
+ >
+ {{ $options.i18n.ALLOW_FAILURE }}
+ </gl-form-checkbox>
+ </gl-form-group>
+ </gl-accordion-item>
+</template>
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js
index 994a6e719fe..17ceea19e3b 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js
@@ -2,6 +2,59 @@ import { __, s__ } from '~/locale';
export const DRAWER_CONTAINER_CLASS = '.content-wrapper';
+export const JOB_RULES_WHEN = {
+ onSuccess: {
+ value: 'on_success',
+ text: s__('JobAssistant|on_success'),
+ },
+ onFailure: {
+ value: 'on_failure',
+ text: s__('JobAssistant|on_failure'),
+ },
+ manual: {
+ value: 'manual',
+ text: s__('JobAssistant|manual'),
+ },
+ always: {
+ value: 'always',
+ text: s__('JobAssistant|always'),
+ },
+ delayed: {
+ value: 'delayed',
+ text: s__('JobAssistant|delayed'),
+ },
+ never: {
+ value: 'never',
+ text: s__('JobAssistant|never'),
+ },
+};
+
+export const JOB_RULES_START_IN = {
+ second: {
+ value: 'second',
+ text: s__('JobAssistant|second(s)'),
+ },
+ minute: {
+ value: 'minute',
+ text: s__('JobAssistant|minute(s)'),
+ },
+ day: {
+ value: 'day',
+ text: s__('JobAssistant|day(s)'),
+ },
+ week: {
+ value: 'week',
+ text: s__('JobAssistant|week(s)'),
+ },
+};
+
+export const SECONDS_MULTIPLE_MAP = {
+ second: 1,
+ minute: 60,
+ day: 3600 * 24,
+ week: 3600 * 24 * 7,
+};
+
export const JOB_TEMPLATE = {
name: '',
stage: '',
@@ -25,6 +78,13 @@ export const JOB_TEMPLATE = {
paths: [''],
key: '',
},
+ rules: [
+ {
+ allow_failure: false,
+ when: 'on_success',
+ start_in: '',
+ },
+ ],
};
export const i18n = {
@@ -38,4 +98,8 @@ export const i18n = {
IMAGE_NAME: s__('JobAssistant|Image name (optional)'),
IMAGE_ENTRYPOINT: s__('JobAssistant|Image entrypoint (optional)'),
THIS_FIELD_IS_REQUIRED: __('This field is required'),
+ RULES: s__('JobAssistant|Rules'),
+ WHEN: s__('JobAssistant|When'),
+ ALLOW_FAILURE: s__('JobAssistant|Allow failure'),
+ INVALID_START_IN: s__('JobAssistant|Error - Valid value is between 1 second and 1 week'),
};
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
index 41f66c8d33c..8d42931ab2b 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
@@ -1,14 +1,15 @@
<script>
import { GlDrawer, GlAccordion, GlButton } from '@gitlab/ui';
import { stringify, parse } from 'yaml';
-import { set, omit, trim } from 'lodash';
+import { set, omit } from 'lodash';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import eventHub, { SCROLL_EDITOR_TO_BOTTOM } from '~/ci/pipeline_editor/event_hub';
import getRunnerTags from '../../graphql/queries/runner_tags.query.graphql';
-import { DRAWER_CONTAINER_CLASS, JOB_TEMPLATE, i18n } from './constants';
-import { removeEmptyObj, trimFields } from './utils';
+import { DRAWER_CONTAINER_CLASS, JOB_TEMPLATE, i18n, JOB_RULES_WHEN } from './constants';
+import { removeEmptyObj, trimFields, validateEmptyValue, validateStartIn } from './utils';
import JobSetupItem from './accordion_items/job_setup_item.vue';
import ImageItem from './accordion_items/image_item.vue';
+import RulesItem from './accordion_items/rules_item.vue';
export default {
i18n,
@@ -18,6 +19,7 @@ export default {
GlButton,
JobSetupItem,
ImageItem,
+ RulesItem,
},
props: {
isVisible: {
@@ -43,6 +45,7 @@ export default {
return {
isNameValid: true,
isScriptValid: true,
+ isStartValid: true,
job: JSON.parse(JSON.stringify(JOB_TEMPLATE)),
};
},
@@ -74,6 +77,21 @@ export default {
drawerHeightOffset() {
return getContentWrapperHeight(DRAWER_CONTAINER_CLASS);
},
+ isJobValid() {
+ return this.isNameValid && this.isScriptValid && this.isStartValid;
+ },
+ },
+
+ watch: {
+ 'job.name': function jobNameWatch(name) {
+ this.isNameValid = validateEmptyValue(name);
+ },
+ 'job.script': function jobScriptWatch(script) {
+ this.isScriptValid = validateEmptyValue(script);
+ },
+ 'job.rules.0.start_in': function JobRulesStartInWatch(startIn) {
+ this.isStartValid = validateStartIn(this.job.rules[0].when, startIn);
+ },
},
methods: {
closeDrawer() {
@@ -81,10 +99,9 @@ export default {
this.$emit('close-job-assistant-drawer');
},
addCiConfig() {
- this.isNameValid = this.validate(this.job.name);
- this.isScriptValid = this.validate(this.job.script);
+ this.validateJob();
- if (!this.isNameValid || !this.isScriptValid) {
+ if (!this.isJobValid) {
return;
}
@@ -97,27 +114,46 @@ export default {
generateYmlString() {
let job = JSON.parse(JSON.stringify(this.job));
const jobName = job.name;
- job = omit(job, ['name']);
+ job = this.removeUnnecessaryKeys(job);
job.tags = job.tags.map((tag) => tag.name); // Tag item is originally an option object, we need a string here to match `.gitlab-ci.yml` rules
const cleanedJob = trimFields(removeEmptyObj(job));
return stringify({ [jobName]: cleanedJob });
},
+ removeUnnecessaryKeys(job) {
+ const keys = ['name'];
+
+ // rules[0].allow_failure value should not be passed down
+ // if it equals the default value
+ if (this.job.rules[0].allow_failure === false) {
+ keys.push('rules[0].allow_failure');
+ }
+ // rules[0].when value should not be passed down
+ // if it equals the default value
+ if (this.job.rules[0].when === JOB_RULES_WHEN.onSuccess.value) {
+ keys.push('rules[0].when');
+ }
+ // rules[0].start_in value should not be passed down
+ // if rules[0].start_in doesn't equal 'delayed'
+ if (this.job.rules[0].when !== JOB_RULES_WHEN.delayed.value) {
+ keys.push('rules[0].start_in');
+ }
+ return omit(job, keys);
+ },
clearJob() {
this.job = JSON.parse(JSON.stringify(JOB_TEMPLATE));
- this.isNameValid = true;
- this.isScriptValid = true;
+ this.$nextTick(() => {
+ this.isNameValid = true;
+ this.isScriptValid = true;
+ this.isStartValid = true;
+ });
},
updateJob(key, value) {
set(this.job, key, value);
- if (key === 'name') {
- this.isNameValid = this.validate(this.job.name);
- }
- if (key === 'script') {
- this.isScriptValid = this.validate(this.job.script);
- }
},
- validate(value) {
- return trim(value) !== '';
+ validateJob() {
+ this.isNameValid = validateEmptyValue(this.job.name);
+ this.isScriptValid = validateEmptyValue(this.job.script);
+ this.isStartValid = validateStartIn(this.job.rules[0].when, this.job.rules[0].start_in);
},
},
};
@@ -143,6 +179,7 @@ export default {
@update-job="updateJob"
/>
<image-item :job="job" @update-job="updateJob" />
+ <rules-item :job="job" :is-start-valid="isStartValid" @update-job="updateJob" />
</gl-accordion>
<template #footer>
<div class="gl-display-flex gl-justify-content-end">
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/utils.js b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/utils.js
index 83e7574c4de..a604d79259d 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/utils.js
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/utils.js
@@ -1,4 +1,8 @@
import { isEmpty, isObject, isArray, isString, reject, omitBy, mapValues, map, trim } from 'lodash';
+import {
+ JOB_RULES_WHEN,
+ SECONDS_MULTIPLE_MAP,
+} from '~/ci/pipeline_editor/components/job_assistant_drawer/constants';
const isEmptyValue = (val) => (isObject(val) || isString(val)) && isEmpty(val);
const trimText = (val) => (isString(val) ? trim(val) : val);
@@ -20,3 +24,30 @@ export const trimFields = (data) => {
}
return trimText(data);
};
+
+export const validateEmptyValue = (value) => {
+ return trim(value) !== '';
+};
+
+export const validateStartIn = (when, startIn) => {
+ const hasNoValue = when !== JOB_RULES_WHEN.delayed.value;
+ if (hasNoValue) {
+ return true;
+ }
+
+ let [startInNumber, startInUnit] = startIn.split(' ');
+
+ startInNumber = Number(startInNumber);
+ if (!Number.isInteger(startInNumber)) {
+ return false;
+ }
+
+ const isPlural = startInUnit.slice(-1) === 's';
+ if (isPlural) {
+ startInUnit = startInUnit.slice(0, -1);
+ }
+
+ const multiple = SECONDS_MULTIPLE_MAP[startInUnit];
+
+ return startInNumber * multiple >= 1 && startInNumber * multiple <= SECONDS_MULTIPLE_MAP.week;
+};
diff --git a/app/assets/javascripts/nav/components/new_nav_toggle.vue b/app/assets/javascripts/nav/components/new_nav_toggle.vue
index ca6232fa4c4..30a415a8cfd 100644
--- a/app/assets/javascripts/nav/components/new_nav_toggle.vue
+++ b/app/assets/javascripts/nav/components/new_nav_toggle.vue
@@ -7,7 +7,7 @@ import Tracking from '~/tracking';
export default {
i18n: {
- badgeLabel: s__('NorthstarNavigation|Alpha'),
+ badgeLabel: s__('NorthstarNavigation|Beta'),
sectionTitle: s__('NorthstarNavigation|Navigation redesign'),
toggleMenuItemLabel: s__('NorthstarNavigation|New navigation'),
toggleLabel: s__('NorthstarNavigation|Toggle new navigation'),
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
index 440e11a99f2..05359128af4 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
@@ -39,5 +39,8 @@ export default {
<template #metadata-amount>
<metadata-item v-if="showPackageCount" icon="package" :text="packageAmountText" />
</template>
+ <template #right-actions>
+ <slot name="settings-link"></slot>
+ </template>
</title-area>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/index.js b/app/assets/javascripts/packages_and_registries/package_registry/index.js
index 15ed98122a0..e2f8d239bae 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/index.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/index.js
@@ -19,6 +19,7 @@ export default () => {
npmInstanceUrl,
projectListUrl,
groupListUrl,
+ settingsPath,
} = el.dataset;
const isGroupPage = pageType === 'groups';
@@ -48,6 +49,7 @@ export default () => {
projectListUrl,
groupListUrl,
breadCrumbState,
+ settingsPath,
},
render(createElement) {
return createElement(PackageRegistry);
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
index 6e92a6420ac..31380d4f925 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
@@ -1,5 +1,5 @@
<script>
-import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlButton, GlEmptyState, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { createAlert, VARIANT_INFO } from '~/alert';
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import { historyReplaceState } from '~/lib/utils/common_utils';
@@ -19,6 +19,7 @@ import PackageList from '~/packages_and_registries/package_registry/components/l
export default {
components: {
+ GlButton,
GlEmptyState,
GlLink,
GlSprintf,
@@ -27,7 +28,10 @@ export default {
PackageSearch,
DeletePackages,
},
- inject: ['emptyListIllustration', 'isGroupPage', 'fullPath'],
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['emptyListIllustration', 'isGroupPage', 'fullPath', 'settingsPath'],
data() {
return {
packages: {},
@@ -149,6 +153,7 @@ export default {
noResultsText: s__(
'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.',
),
+ settingsText: s__('PackageRegistry|Configure in settings'),
},
links: {
EMPTY_LIST_HELP_URL,
@@ -159,7 +164,16 @@ export default {
<template>
<div>
- <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount" />
+ <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount">
+ <template v-if="settingsPath" #settings-link>
+ <gl-button
+ v-gl-tooltip="$options.i18n.settingsText"
+ icon="settings"
+ :href="settingsPath"
+ :aria-label="$options.i18n.settingsText"
+ />
+ </template>
+ </package-title>
<package-search class="gl-mb-5" @update="handleSearchUpdate" />
<delete-packages
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 9623c51d51c..cc153747765 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -416,7 +416,7 @@ export default {
<div
v-if="referencedCommands && previewMarkdown && !markdownPreviewLoading"
v-safe-html:[$options.safeHtmlConfig]="referencedCommands"
- class="referenced-commands"
+ class="referenced-commands gl-mx-n5"
data-testid="referenced-commands"
></div>
<div v-if="shouldShowReferencedUsers" class="referenced-users">
diff --git a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
index 420d7ebe7d6..52d8aab30d5 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
@@ -157,6 +157,11 @@ export default {
this.notifyEditingModeChange(editingMode);
},
onEditingModeRestored(editingMode) {
+ if (editingMode === EDITING_MODE_CONTENT_EDITOR && !this.enableContentEditor) {
+ this.editingMode = EDITING_MODE_MARKDOWN_FIELD;
+ return;
+ }
+
this.editingMode = editingMode;
this.$emit(editingMode);
this.notifyEditingModeChange(editingMode);
@@ -197,7 +202,8 @@ export default {
<div>
<local-storage-sync
v-model="editingMode"
- storage-key="gl-content-editor-enabled"
+ as-string
+ storage-key="gl-markdown-editor-mode"
@input="onEditingModeRestored"
/>
<markdown-field
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 7c1ac389632..06ca90fa8c6 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -30,9 +30,9 @@ import TimelineEntryItem from './timeline_entry_item.vue';
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const MR_ICON_COLORS = {
- approval: 'gl-bg-green-100 gl-text-green-700',
- 'issue-close': 'gl-bg-red-100 gl-text-red-700',
- 'git-merge': 'gl-bg-blue-100 gl-text-blue-700',
+ check: 'gl-bg-green-100 gl-text-green-700',
+ 'merge-request-close': 'gl-bg-red-100 gl-text-red-700',
+ merge: 'gl-bg-blue-100 gl-text-blue-700',
};
const ICON_COLORS = {
'issue-close': 'gl-bg-blue-100 gl-text-blue-700',
@@ -91,9 +91,16 @@ export default {
descriptionVersion() {
return this.descriptionVersions[this.note.description_version_id];
},
+ isMergeRequest() {
+ return this.getNoteableData.noteableType === 'MergeRequest';
+ },
+ hasIconColors() {
+ if (!this.isMergeRequest) return true;
+
+ return this.isMergeRequest && MR_ICON_COLORS[this.note.system_note_icon_name];
+ },
iconBgClass() {
- const colors =
- this.getNoteableData.noteableType === 'MergeRequest' ? MR_ICON_COLORS : ICON_COLORS;
+ const colors = this.isMergeRequest ? MR_ICON_COLORS : ICON_COLORS;
return colors[this.note.system_note_icon_name] || 'gl-bg-gray-50 gl-text-gray-600';
},
@@ -129,12 +136,20 @@ export default {
class="note system-note note-wrapper"
>
<div
- :class="iconBgClass"
- class="gl-float-left gl--flex-center gl-rounded-full gl-mt-n1 gl-ml-2 gl-w-6 gl-h-6 timeline-icon"
+ :class="[
+ iconBgClass,
+ {
+ 'mr-system-note-empty gl-bg-gray-900!': !hasIconColors,
+ 'gl-w-6 gl-h-6 gl-mt-n1 gl-ml-2': !isMergeRequest,
+ 'mr-system-note-icon': isMergeRequest,
+ },
+ ]"
+ class="gl-float-left gl--flex-center gl-rounded-full gl-relative timeline-icon"
>
<gl-icon
- v-if="note.system_note_icon_name"
+ v-if="note.system_note_icon_name && hasIconColors"
:name="note.system_note_icon_name"
+ :size="isMergeRequest ? 12 : 16"
data-testid="timeline-icon"
/>
</div>
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index c40cadafb9c..48aacc9606e 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -120,9 +120,10 @@
}
.referenced-commands {
+ $radius: $border-radius-default - 1px;
background: $blue-50;
padding: $gl-padding-8 $gl-padding;
- border-radius: $border-radius-default;
+ border-radius: 0 0 $radius $radius;
p {
margin: 0;
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index c3f90abe018..3a4ac4257b6 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -1231,3 +1231,60 @@ $tabs-holder-z-index: 250;
vertical-align: middle;
}
}
+
+.mr-system-note-icon {
+ width: 20px;
+ height: 20px;
+ margin-left: 6px;
+
+ &.gl-bg-green-100 {
+ --bg-color: var(--green-100, #{$green-100});
+ }
+
+ &.gl-bg-red-100 {
+ --bg-color: var(--red-100, #{$red-100});
+ }
+
+ &.gl-bg-blue-100 {
+ --bg-color: var(--blue-100, #{$blue-100});
+ }
+}
+
+.mr-system-note-icon:not(.mr-system-note-empty)::before {
+ content: '';
+ display: block;
+ position: absolute;
+ left: calc(50% - 1px);
+ bottom: 100%;
+ width: 2px;
+ height: 20px;
+ background: linear-gradient(to bottom, transparent, var(--bg-color));
+
+ .system-note:first-child & {
+ display: none;
+ }
+}
+
+.mr-system-note-icon:not(.mr-system-note-empty)::after {
+ content: '';
+ display: block;
+ position: absolute;
+ left: calc(50% - 1px);
+ top: 100%;
+ width: 2px;
+ height: 20px;
+ background: linear-gradient(to bottom, var(--bg-color), transparent);
+
+ .system-note:last-child & {
+ display: none;
+ }
+}
+
+.mr-system-note-empty {
+ width: 8px;
+ height: 8px;
+ margin-top: 6px;
+ margin-left: 12px;
+ margin-right: 8px;
+ border: 2px solid var(--gray-50, $gray-50);
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index a7381f99bae..45f9729e785 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -7,7 +7,7 @@ $system-note-icon-size: 1.5rem;
$system-note-svg-size: 1rem;
$icon-size-diff: $avatar-icon-size - $system-note-icon-size;
-$system-note-icon-m-top: $avatar-m-top + $icon-size-diff - 0.3rem;
+$system-note-icon-m-top: $avatar-m-top + $icon-size-diff - 1.3rem;
$system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
@mixin vertical-line($left) {
@@ -15,10 +15,10 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
content: '';
border-left: 2px solid var(--gray-50, $gray-50);
position: absolute;
- top: $gl-padding-6;
+ top: 16px;
bottom: 0;
left: calc(#{$left} - 1px);
- height: calc(100% + 1.5rem);
+ height: calc(100% + 20px);
}
}
@@ -30,8 +30,30 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.issuable-discussion:not(.incident-timeline-events),
.limited-width-notes {
- .main-notes-list > li.timeline-entry:not(:last-of-type) {
- @include vertical-line(1rem);
+ .main-notes-list::before,
+ .timeline-entry:last-child::before {
+ content: '';
+ position: absolute;
+ width: 2px;
+ left: 15px;
+ top: 15px;
+ height: calc(100% - 15px);
+ }
+
+ .main-notes-list::before {
+ background: var(--gray-50, $gray-50);
+ }
+
+ .timeline-entry:last-child::before {
+ background: var(--white);
+
+ .gl-dark & {
+ background: var(--gray-10);
+ }
+
+ &.note-comment {
+ top: 30px;
+ }
}
}
@@ -63,6 +85,10 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
height: 2rem;
}
+ .gl-avatar {
+ border-color: var(--gray-50, $gray-50);
+ }
+
&.note-comment,
&.note-skeleton,
.draft-note {
@@ -265,7 +291,10 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
&.being-posted {
pointer-events: none;
- opacity: 0.5;
+
+ .timeline-entry-inner {
+ opacity: 0.5;
+ }
.dummy-avatar {
background-color: $gray-100;
@@ -370,9 +399,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-content {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-left: 30px;
- }
+ margin-left: 30px;
}
.note-header {
@@ -460,7 +487,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-icon {
- margin: 12px 0 0 20px;
+ margin: 20px 0 0 28px;
}
}
@@ -570,15 +597,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.system-note {
background-color: transparent;
padding: 0;
-
- .timeline-icon {
- margin-top: -2px;
- }
-
- .timeline-entry-inner .timeline-icon {
- margin-top: $system-note-icon-m-top;
- margin-left: $system-note-icon-m-left;
- }
}
}
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index b8d47586a15..a86a8a0415a 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -190,7 +190,7 @@ module IssuableActions
end
def discussion_cache_context
- [current_user&.cache_key, project.team.human_max_access(current_user&.id)].join(':')
+ [current_user&.cache_key, project.team.human_max_access(current_user&.id), 'v2'].join(':')
end
def discussion_serializer
diff --git a/app/controllers/groups/usage_quotas_controller.rb b/app/controllers/groups/usage_quotas_controller.rb
index 4f858cd130a..125c8fde004 100644
--- a/app/controllers/groups/usage_quotas_controller.rb
+++ b/app/controllers/groups/usage_quotas_controller.rb
@@ -6,7 +6,7 @@ module Groups
before_action :verify_usage_quotas_enabled!
before_action :push_frontend_feature_flags
- feature_category :subscription_cost_management
+ feature_category :consumables_cost_management
urgency :low
def index
diff --git a/app/controllers/projects/usage_quotas_controller.rb b/app/controllers/projects/usage_quotas_controller.rb
index d3757eaf481..6e2514e89c8 100644
--- a/app/controllers/projects/usage_quotas_controller.rb
+++ b/app/controllers/projects/usage_quotas_controller.rb
@@ -5,7 +5,7 @@ class Projects::UsageQuotasController < Projects::ApplicationController
layout "project_settings"
- feature_category :subscription_cost_management
+ feature_category :consumables_cost_management
urgency :low
def index
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index dec1943db54..8861f1ffe9a 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -69,6 +69,11 @@ module PackagesHelper
Ability.allowed?(current_user, :admin_package, project)
end
+ def show_group_package_registry_settings(group)
+ group.packages_feature_enabled? &&
+ Ability.allowed?(current_user, :admin_group, group)
+ end
+
def cleanup_settings_data
{
project_id: @project.id,
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index a1b6e896475..3d31d697452 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -2,13 +2,13 @@
module SystemNoteHelper
ICON_NAMES_BY_ACTION = {
- 'approved' => 'approval',
+ 'approved' => 'check',
'unapproved' => 'unapproval',
'cherry_pick' => 'cherry-pick-commit',
'commit' => 'commit',
'description' => 'pencil',
- 'merge' => 'git-merge',
- 'merged' => 'git-merge',
+ 'merge' => 'merge',
+ 'merged' => 'merge',
'opened' => 'issues',
'closed' => 'issue-close',
'time_tracking' => 'timer',
@@ -51,7 +51,11 @@ module SystemNoteHelper
}.freeze
def system_note_icon_name(note)
- ICON_NAMES_BY_ACTION[note.system_note_metadata&.action]
+ if note.system_note_metadata&.action == 'closed' && note.for_merge_request?
+ 'merge-request-close'
+ else
+ ICON_NAMES_BY_ACTION[note.system_note_metadata&.action]
+ end
end
def icon_for_system_note(note)
diff --git a/app/views/groups/packages/index.html.haml b/app/views/groups/packages/index.html.haml
index 7d169b2675e..b6cf26c3677 100644
--- a/app/views/groups/packages/index.html.haml
+++ b/app/views/groups/packages/index.html.haml
@@ -9,4 +9,5 @@
empty_list_illustration: image_path('illustrations/no-packages.svg'),
npm_instance_url: package_registry_instance_url(:npm),
project_list_url: '',
+ settings_path: show_group_package_registry_settings(@group) ? group_settings_packages_and_registries_path(@group) : '',
group_list_url: group_packages_path(@group) } }
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index db8a5fa44c1..bf72f986664 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -103,7 +103,7 @@
= gl_badge_tag({ size: :sm, variant: :info }, { class: "js-todos-count gl-ml-n2 #{'hidden' if todos_pending_count == 0}", "aria-label": _("Todos count") }) do
= todos_count_format(todos_pending_count)
%li.nav-item.header-help.dropdown.d-none.d-md-block
- = link_to help_path, class: 'header-help-dropdown-toggle gl-relative', data: { toggle: "dropdown", track_action: 'click_question_mark_link', track_label: 'main_navigation', track_property: 'navigation_top', track_experiment: 'cross_stage_fdm' } do
+ = link_to help_path, class: 'header-help-dropdown-toggle gl-relative', data: { toggle: "dropdown" } do
%span.gl-sr-only
= s_('Nav|Help')
= sprite_icon('question-o')
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index f50df72afbc..38b9a9a5383 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -2,7 +2,6 @@
- if current_user_menu?(:help)
%li
= render 'layouts/header/gitlab_version'
- = render_if_exists 'layouts/header/help_dropdown/cross_stage_fdm'
= render 'layouts/header/whats_new_dropdown_item'
%li
= link_to _("Help"), help_path, data: {track_action: 'click_link', track_label: 'help', track_property: 'navigation_top'}
diff --git a/app/views/projects/packages/packages/index.html.haml b/app/views/projects/packages/packages/index.html.haml
index e120e8ccb18..48aaf0884c8 100644
--- a/app/views/projects/packages/packages/index.html.haml
+++ b/app/views/projects/packages/packages/index.html.haml
@@ -9,4 +9,5 @@
empty_list_illustration: image_path('illustrations/no-packages.svg'),
npm_instance_url: package_registry_instance_url(:npm),
project_list_url: project_packages_path(@project),
+ settings_path: show_package_registry_settings(@project) ? project_settings_packages_and_registries_path(@project) : '',
group_list_url: '' } }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 84bcec52d7f..8f8ae48293a 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -842,7 +842,7 @@
:tags: []
- :name: cronjob:users_deactivate_dormant_users
:worker_name: Users::DeactivateDormantUsersWorker
- :feature_category: :subscription_cost_management
+ :feature_category: :seat_cost_management
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@@ -3452,7 +3452,7 @@
:tags: []
- :name: update_highest_role
:worker_name: UpdateHighestRoleWorker
- :feature_category: :subscription_cost_management
+ :feature_category: :seat_cost_management
:has_external_dependencies: false
:urgency: :high
:resource_boundary: :unknown
diff --git a/app/workers/update_highest_role_worker.rb b/app/workers/update_highest_role_worker.rb
index dccf88e1b1a..ec24ee15895 100644
--- a/app/workers/update_highest_role_worker.rb
+++ b/app/workers/update_highest_role_worker.rb
@@ -7,7 +7,7 @@ class UpdateHighestRoleWorker
sidekiq_options retry: 3
- feature_category :subscription_cost_management
+ feature_category :seat_cost_management
urgency :high
weight 2
diff --git a/app/workers/users/deactivate_dormant_users_worker.rb b/app/workers/users/deactivate_dormant_users_worker.rb
index c3799480b12..d024109e754 100644
--- a/app/workers/users/deactivate_dormant_users_worker.rb
+++ b/app/workers/users/deactivate_dormant_users_worker.rb
@@ -8,7 +8,7 @@ module Users
include CronjobQueue
- feature_category :subscription_cost_management
+ feature_category :seat_cost_management
def perform
return if Gitlab.com?
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 06f95ea46aa..eb20a65caf7 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -23,7 +23,6 @@
- capacity_planning
- cell
- cloud_native_installation
-- cluster_cost_management
- code_quality
- code_review_workflow
- code_search
@@ -31,11 +30,11 @@
- code_testing
- commerce_integrations
- compliance_management
+- consumables_cost_management
- container_registry
- container_scanning
- continuous_delivery
- continuous_integration
-- continuous_verification
- customersdot_application
- database
- dataops
@@ -116,6 +115,7 @@
- saas_provisioning
- sbom
- scalability
+- seat_cost_management
- secret_detection
- secrets_management
- security_benchmarking
@@ -126,7 +126,6 @@
- source_code_management
- static_application_security_testing
- subgroups
-- subscription_cost_management
- subscription_management
- system_access
- team_planning
diff --git a/config/metrics/settings/20221015152126_deactivate_dormant_users_enabled.yml b/config/metrics/settings/20221015152126_deactivate_dormant_users_enabled.yml
index 0f77cde4014..7861560c26a 100644
--- a/config/metrics/settings/20221015152126_deactivate_dormant_users_enabled.yml
+++ b/config/metrics/settings/20221015152126_deactivate_dormant_users_enabled.yml
@@ -4,7 +4,7 @@ description: Whether Dormant User Deactivation is enabled
product_section: fulfillment
product_stage: fulfillment
product_group: utilization
-product_category: subscription_cost_management
+product_category: seat_cost_management
value_type: boolean
status: active
milestone: "15.6"
diff --git a/config/metrics/settings/20221015161233_deactivate_dormant_users_period.yml b/config/metrics/settings/20221015161233_deactivate_dormant_users_period.yml
index 6cd7e0f0da7..ac05d0ef80c 100644
--- a/config/metrics/settings/20221015161233_deactivate_dormant_users_period.yml
+++ b/config/metrics/settings/20221015161233_deactivate_dormant_users_period.yml
@@ -4,7 +4,7 @@ description: The value of the dormant users period being used
product_section: fulfillment
product_stage: fulfillment
product_group: utilization
-product_category: subscription_cost_management
+product_category: seat_cost_management
value_type: boolean
status: active
milestone: "15.6"
diff --git a/db/docs/namespace_aggregation_schedules.yml b/db/docs/namespace_aggregation_schedules.yml
index 56ac5ee3ed6..d57311fff8f 100644
--- a/db/docs/namespace_aggregation_schedules.yml
+++ b/db/docs/namespace_aggregation_schedules.yml
@@ -3,7 +3,7 @@ table_name: namespace_aggregation_schedules
classes:
- Namespace::AggregationSchedule
feature_categories:
-- subscription_cost_management
+- consumables_cost_management
description: Keeps update schedules for namespace_root_storage_statistics
introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/29570
milestone: '12.1'
diff --git a/db/docs/namespace_limits.yml b/db/docs/namespace_limits.yml
index bcc687de858..5bb06b53cb2 100644
--- a/db/docs/namespace_limits.yml
+++ b/db/docs/namespace_limits.yml
@@ -3,7 +3,7 @@ table_name: namespace_limits
classes:
- NamespaceLimit
feature_categories:
-- subscription_cost_management
+- consumables_cost_management
description: Contains limits for namespace features like storage and ci
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34746
milestone: '13.2'
diff --git a/db/docs/namespaces_storage_limit_exclusions.yml b/db/docs/namespaces_storage_limit_exclusions.yml
index d66da2797dd..b96c15112ec 100644
--- a/db/docs/namespaces_storage_limit_exclusions.yml
+++ b/db/docs/namespaces_storage_limit_exclusions.yml
@@ -3,7 +3,7 @@ table_name: namespaces_storage_limit_exclusions
classes:
- Namespaces::Storage::LimitExclusion
feature_categories:
- - subscription_cost_management
+ - consumables_cost_management
description: |
Stores namespaces that are excluded from the storage limit.
Any namespaces that are included in this table will not have storage limitations applied.
diff --git a/doc/development/database/multiple_databases.md b/doc/development/database/multiple_databases.md
index 8a6f1630769..6adfdc90cf2 100644
--- a/doc/development/database/multiple_databases.md
+++ b/doc/development/database/multiple_databases.md
@@ -580,6 +580,24 @@ or records that point to nowhere, which might lead to bugs. As such we created
["loose foreign keys"](loose_foreign_keys.md) which is an asynchronous
process of cleaning up orphaned records.
+## Testing for multiple databases
+
+In our testing CI pipelines, we test GitLab by default with multiple databases set up, using
+both `main` and `ci` databases. But in merge requests, for example when we modify some database-related code or
+add the label `~"pipeline:run-single-db"` to the MR, we additionally run our tests in
+[two other database modes](../pipelines/index.md#single-database-testing):
+`single-db` and `single-db-ci-connection`.
+
+To handle situations where our tests need to run in specific database modes, we have some RSpec helpers
+to limit the modes where tests can run, and skip them on any other modes.
+
+| Helper name | Test runs |
+|---------------------------------------------| --- |
+| `skip_if_shared_database(:ci)` | On **multiple databases** |
+| `skip_if_database_exists(:ci)` | On **single-db** and **single-db-ci-connection** |
+| `skip_if_multiple_databases_are_setup(:ci)` | Only on **single-db** |
+| `skip_if_multiple_databases_not_setup(:ci)` | On **single-db-ci-connection** and **multiple databases** |
+
## Locking writes on the tables that don't belong to the database schemas
When the CI database is promoted and the two databases are fully split,
diff --git a/doc/user/profile/achievements.md b/doc/user/profile/achievements.md
index fcbbbe65639..c06700bd2ef 100644
--- a/doc/user/profile/achievements.md
+++ b/doc/user/profile/achievements.md
@@ -223,3 +223,12 @@ mutation {
}
}
```
+
+## Hide achievements
+
+If you don't want to display achievements on your profile, you can opt out. To do this:
+
+1. On the top bar, in the upper-right corner, select your avatar.
+1. Select **Edit profile**.
+1. In the **Main settings** section, clear the **Display achievements on your profile** checkbox.
+1. Select **Update profile settings**.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a0c5f5ba6c7..27f422cff6a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22196,24 +22196,12 @@ msgstr ""
msgid "InProductMarketing|3 ways to dive into GitLab CI/CD"
msgstr ""
-msgid "InProductMarketing|A single application eliminates complex integrations, data chokepoints, and toolchain maintenance, resulting in greater productivity and lower cost."
-msgstr ""
-
-msgid "InProductMarketing|Access advanced features, build more efficiently, strengthen security and compliance."
-msgstr ""
-
-msgid "InProductMarketing|Access advanced features."
-msgstr ""
-
msgid "InProductMarketing|Actually, GitLab makes the team work (better)"
msgstr ""
msgid "InProductMarketing|And finally %{deploy_link} a Python application."
msgstr ""
-msgid "InProductMarketing|And many more..."
-msgstr ""
-
msgid "InProductMarketing|Are your runners ready?"
msgstr ""
@@ -22232,33 +22220,15 @@ msgstr ""
msgid "InProductMarketing|Blog"
msgstr ""
-msgid "InProductMarketing|Break down silos to coordinate seamlessly across development, operations, and security with a consistent experience across the development lifecycle."
-msgstr ""
-
msgid "InProductMarketing|Building for iOS? We've got you covered."
msgstr ""
-msgid "InProductMarketing|Burn up/down charts"
-msgstr ""
-
msgid "InProductMarketing|By enabling code owners and required merge approvals the right person will review the right MR. This is a win-win: cleaner code and a more efficient review process."
msgstr ""
-msgid "InProductMarketing|Code owners"
-msgstr ""
-
msgid "InProductMarketing|Code owners and required merge approvals are part of the paid tiers of GitLab. You can start a free 30-day trial of GitLab Ultimate and enable these features in less than 5 minutes with no credit card required."
msgstr ""
-msgid "InProductMarketing|Code review analytics"
-msgstr ""
-
-msgid "InProductMarketing|Collaboration across stages in GitLab"
-msgstr ""
-
-msgid "InProductMarketing|Collaboration made easy"
-msgstr ""
-
msgid "InProductMarketing|Create a custom CI runner with just a few clicks"
msgstr ""
@@ -22268,42 +22238,21 @@ msgstr ""
msgid "InProductMarketing|Create a project in GitLab in 5 minutes"
msgstr ""
-msgid "InProductMarketing|Create well-defined workflows by using scoped labels on issues, merge requests, and epics. Labels with the same scope cannot be used together, which prevents conflicts."
-msgstr ""
-
msgid "InProductMarketing|Create your first project!"
msgstr ""
-msgid "InProductMarketing|Define who owns specific files or directories, so the right reviewers are suggested when a merge request introduces changes to those files."
-msgstr ""
-
msgid "InProductMarketing|Deliver Better Products Faster"
msgstr ""
-msgid "InProductMarketing|Dependency scanning"
-msgstr ""
-
msgid "InProductMarketing|Did you know teams that use GitLab are far more efficient?"
msgstr ""
msgid "InProductMarketing|Dig in and create a project and a repo"
msgstr ""
-msgid "InProductMarketing|Discover Premium & Ultimate"
-msgstr ""
-
-msgid "InProductMarketing|Discover Premium & Ultimate."
-msgstr ""
-
msgid "InProductMarketing|Do you have a teammate who would be perfect for this task?"
msgstr ""
-msgid "InProductMarketing|Dynamic application security testing"
-msgstr ""
-
-msgid "InProductMarketing|Epics"
-msgstr ""
-
msgid "InProductMarketing|Expand your DevOps journey with a free GitLab trial"
msgstr ""
@@ -22322,15 +22271,9 @@ msgstr ""
msgid "InProductMarketing|Feel the need for speed?"
msgstr ""
-msgid "InProductMarketing|Find and fix bottlenecks in your code review process by understanding how long open merge requests have been in review."
-msgstr ""
-
msgid "InProductMarketing|Find out how your teams are really doing"
msgstr ""
-msgid "InProductMarketing|Find out if your external libraries are safe. Run dependency scanning jobs that check for known vulnerabilities in your external libraries."
-msgstr ""
-
msgid "InProductMarketing|Follow our steps"
msgstr ""
@@ -22367,9 +22310,6 @@ msgstr ""
msgid "InProductMarketing|GitHub Enterprise projects to GitLab"
msgstr ""
-msgid "InProductMarketing|GitLab is infrastructure agnostic (supporting GCP, AWS, Azure, OpenShift, VMWare, On Prem, Bare Metal, and more), offering a consistent workflow experience – irrespective of the environment."
-msgstr ""
-
msgid "InProductMarketing|GitLab provides static application security testing (SAST), dynamic application security testing (DAST), container scanning, and dependency scanning to help you deliver secure applications along with license compliance."
msgstr ""
@@ -22451,39 +22391,24 @@ msgstr ""
msgid "InProductMarketing|It's also possible to simply %{external_repo_link} in order to take advantage of GitLab's CI/CD."
msgstr ""
-msgid "InProductMarketing|Keep your code quality high by defining who should approve merge requests and how many approvals are required."
-msgstr ""
-
msgid "InProductMarketing|Launch GitLab CI/CD in 20 minutes or less"
msgstr ""
msgid "InProductMarketing|Learn how to build for iOS"
msgstr ""
-msgid "InProductMarketing|Lower cost of development"
-msgstr ""
-
-msgid "InProductMarketing|Make it easier to collaborate on high-level ideas by grouping related issues in an epic."
-msgstr ""
-
msgid "InProductMarketing|Making the switch? It's easier than you think to import your projects into GitLab. Move %{github_link}, or import something %{bitbucket_link}."
msgstr ""
msgid "InProductMarketing|Master the art of importing!"
msgstr ""
-msgid "InProductMarketing|Merge request approval rule"
-msgstr ""
-
msgid "InProductMarketing|Move on to easily creating a Pages website %{ci_template_link}"
msgstr ""
msgid "InProductMarketing|Multiple owners, confusing workstreams? We've got you covered"
msgstr ""
-msgid "InProductMarketing|Multiple required approvers"
-msgstr ""
-
msgid "InProductMarketing|Need an alternative to importing?"
msgstr ""
@@ -22493,33 +22418,18 @@ msgstr ""
msgid "InProductMarketing|Our tool brings all the things together"
msgstr ""
-msgid "InProductMarketing|Protect your web application by using DAST to examine for vulnerabilities in deployed environments."
-msgstr ""
-
msgid "InProductMarketing|Rapid development, simplified"
msgstr ""
msgid "InProductMarketing|Reduce Security and Compliance Risk"
msgstr ""
-msgid "InProductMarketing|Require multiple approvers on a merge request, so you know it's in good shape before it's merged."
-msgstr ""
-
-msgid "InProductMarketing|Roadmaps"
-msgstr ""
-
-msgid "InProductMarketing|Scoped labels"
-msgstr ""
-
msgid "InProductMarketing|Security that's integrated into your development lifecycle"
msgstr ""
msgid "InProductMarketing|Sometimes you're not ready to make a full transition to a new tool. If you're not ready to fully commit, %{mirroring_link} gives you a safe way to try out GitLab in parallel with your current tool."
msgstr ""
-msgid "InProductMarketing|Speed. Efficiency. Trust."
-msgstr ""
-
msgid "InProductMarketing|Spin up an autoscaling runner in GitLab"
msgstr ""
@@ -22529,9 +22439,6 @@ msgstr ""
msgid "InProductMarketing|Start a Self-Managed trial"
msgstr ""
-msgid "InProductMarketing|Start a free trial"
-msgstr ""
-
msgid "InProductMarketing|Start a free trial of GitLab Ultimate – no credit card required"
msgstr ""
@@ -22598,9 +22505,6 @@ msgstr ""
msgid "InProductMarketing|To understand and get the most out of GitLab, start at the beginning and %{project_link}. In GitLab, repositories are part of a project, so after you've created your project you can go ahead and %{repo_link}."
msgstr ""
-msgid "InProductMarketing|Track completed issues in a chart, so you can see how a milestone is progressing at a glance."
-msgstr ""
-
msgid "InProductMarketing|Try GitLab Ultimate for free"
msgstr ""
@@ -22631,9 +22535,6 @@ msgstr ""
msgid "InProductMarketing|Used by more than 100,000 organizations from around the globe:"
msgstr ""
-msgid "InProductMarketing|Visualize your epics and milestones in a timeline."
-msgstr ""
-
msgid "InProductMarketing|Want to get your iOS app up and running, including publishing all the way to TestFlight? Follow our guide to set up GitLab and fastlane to publish iOS apps to the App Store."
msgstr ""
@@ -22658,9 +22559,6 @@ msgstr ""
msgid "InProductMarketing|YouTube"
msgstr ""
-msgid "InProductMarketing|Your software, deployed your way"
-msgstr ""
-
msgid "InProductMarketing|Your teams can be more efficient"
msgstr ""
@@ -24849,6 +24747,12 @@ msgstr ""
msgid "JobAssistant|Add job"
msgstr ""
+msgid "JobAssistant|Allow failure"
+msgstr ""
+
+msgid "JobAssistant|Error - Valid value is between 1 second and 1 week"
+msgstr ""
+
msgid "JobAssistant|Image"
msgstr ""
@@ -24867,6 +24771,9 @@ msgstr ""
msgid "JobAssistant|Job name"
msgstr ""
+msgid "JobAssistant|Rules"
+msgstr ""
+
msgid "JobAssistant|Script"
msgstr ""
@@ -24876,6 +24783,39 @@ msgstr ""
msgid "JobAssistant|Tags (optional)"
msgstr ""
+msgid "JobAssistant|When"
+msgstr ""
+
+msgid "JobAssistant|always"
+msgstr ""
+
+msgid "JobAssistant|day(s)"
+msgstr ""
+
+msgid "JobAssistant|delayed"
+msgstr ""
+
+msgid "JobAssistant|manual"
+msgstr ""
+
+msgid "JobAssistant|minute(s)"
+msgstr ""
+
+msgid "JobAssistant|never"
+msgstr ""
+
+msgid "JobAssistant|on_failure"
+msgstr ""
+
+msgid "JobAssistant|on_success"
+msgstr ""
+
+msgid "JobAssistant|second(s)"
+msgstr ""
+
+msgid "JobAssistant|week(s)"
+msgstr ""
+
msgid "Jobs"
msgstr ""
@@ -29285,9 +29225,6 @@ msgstr ""
msgid "Normal text"
msgstr ""
-msgid "NorthstarNavigation|Alpha"
-msgstr ""
-
msgid "NorthstarNavigation|Beta"
msgstr ""
@@ -30824,6 +30761,9 @@ msgstr ""
msgid "PackageRegistry|Conan Command"
msgstr ""
+msgid "PackageRegistry|Configure in settings"
+msgstr ""
+
msgid "PackageRegistry|Configure package forwarding and package file size limits."
msgstr ""
@@ -39764,12 +39704,18 @@ msgstr ""
msgid "SecurityReports|Download %{artifactName}"
msgstr ""
+msgid "SecurityReports|Download patch to resolve"
+msgstr ""
+
msgid "SecurityReports|Download results"
msgstr ""
msgid "SecurityReports|Download scanned URLs"
msgstr ""
+msgid "SecurityReports|Download the patch to apply it manually"
+msgstr ""
+
msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
msgstr ""
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index cb213aff31d..7b6a3f953e5 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -83,7 +83,17 @@ module QA
end
def go_to_groups
- click_element(:sidebar_menu_link, menu_item: 'Groups')
+ # Use new functionality to visit Groups where possible
+ if has_element?(:sidebar_menu_link, menu_item: 'Groups')
+ click_element(:sidebar_menu_link, menu_item: 'Groups')
+ else
+ # Otherwise fallback to previous functionality
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/403589
+ # and related issues
+ within_groups_menu do
+ click_element(:menu_item_link, title: 'View all groups')
+ end
+ end
end
def go_to_snippets
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb
deleted file mode 100644
index 5543e39e38c..00000000000
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- RSpec.describe 'Verify', :runner, product_group: :pipeline_security do
- describe 'Artifacts' do
- context 'when locked' do
- let(:file_name) { 'artifact.txt' }
- let(:directory_name) { 'my_artifacts' }
- let(:executor) { "qa-runner-#{Time.now.to_i}" }
-
- let(:project) do
- Resource::Project.fabricate_via_api! do |project|
- project.name = 'project-with-locked-artifacts'
- end
- end
-
- let!(:runner) do
- Resource::ProjectRunner.fabricate! do |runner|
- runner.project = project
- runner.name = executor
- runner.tags = [executor]
- end
- end
-
- before do
- Flow::Login.sign_in
- end
-
- after do
- runner.remove_via_api!
- end
-
- it 'can be browsed', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348003' do
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.project = project
- commit.commit_message = 'Add .gitlab-ci.yml'
- commit.add_files(
- [
- {
- file_path: '.gitlab-ci.yml',
- content: <<~YAML
- test-artifacts:
- tags:
- - '#{executor}'
- artifacts:
- paths:
- - '#{directory_name}'
- expire_in: 1 sec
- script:
- - |
- mkdir #{directory_name}
- echo "CONTENTS" > #{directory_name}/#{file_name}
- YAML
- }
- ]
- )
- end.project.visit!
-
- Flow::Pipeline.visit_latest_pipeline
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.click_job('test-artifacts')
- end
-
- Page::Project::Job::Show.perform do |show|
- expect(show).to have_browse_button
- show.click_browse_button
- end
-
- Page::Project::Artifact::Show.perform do |show|
- show.go_to_directory(directory_name)
- expect(show).to have_content(file_name)
- end
- end
- end
- end
- end
-end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 23a9ab74a7a..66014e676d5 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe "Admin Health Check", :js, feature_category: :continuous_verification do
+RSpec.describe "Admin Health Check", :js, feature_category: :error_budgets do
include StubENV
include Spec::Support::Helpers::ModalHelpers
let_it_be(:admin) { create(:admin) }
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index d6507e68692..34208cca113 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Admin::HookLogs', feature_category: :continuous_verification do
+RSpec.describe 'Admin::HookLogs', feature_category: :integrations do
let_it_be(:system_hook) { create(:system_hook) }
let_it_be(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb
index dee63be8119..006b7ce45d4 100644
--- a/spec/features/boards/board_filters_spec.rb
+++ b/spec/features/boards/board_filters_spec.rb
@@ -50,7 +50,7 @@ RSpec.describe 'Issue board filters', :js, feature_category: :team_planning do
set_filter('assignee')
end
- it_behaves_like 'loads all the users when opened' do
+ it_behaves_like 'loads all the users when opened', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351426' do
let(:issue) { issue_2 }
end
end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 4f6ce6e8f71..caf13c4111b 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'User can display performance bar', :js, feature_category: :continuous_verification do
+RSpec.describe 'User can display performance bar', :js, feature_category: :application_performance do
shared_examples 'performance bar cannot be displayed' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#js-peek')
diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item_spec.js
new file mode 100644
index 00000000000..659ccb25996
--- /dev/null
+++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item_spec.js
@@ -0,0 +1,70 @@
+import RulesItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import {
+ JOB_TEMPLATE,
+ JOB_RULES_WHEN,
+ JOB_RULES_START_IN,
+} from '~/ci/pipeline_editor/components/job_assistant_drawer/constants';
+
+describe('Rules item', () => {
+ let wrapper;
+
+ const findRulesWhenSelect = () => wrapper.findByTestId('rules-when-select');
+ const findRulesStartInNumberInput = () => wrapper.findByTestId('rules-start-in-number-input');
+ const findRulesStartInUnitSelect = () => wrapper.findByTestId('rules-start-in-unit-select');
+ const findRulesAllowFailureCheckBox = () => wrapper.findByTestId('rules-allow-failure-checkbox');
+
+ const dummyRulesWhen = JOB_RULES_WHEN.delayed.value;
+ const dummyRulesStartInNumber = 2;
+ const dummyRulesStartInUnit = JOB_RULES_START_IN.week.value;
+ const dummyRulesAllowFailure = true;
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(RulesItem, {
+ propsData: {
+ isStartValid: true,
+ job: JSON.parse(JSON.stringify(JOB_TEMPLATE)),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should emit update job event when filling inputs', () => {
+ expect(wrapper.emitted('update-job')).toBeUndefined();
+
+ findRulesWhenSelect().vm.$emit('input', dummyRulesWhen);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(1);
+ expect(wrapper.emitted('update-job')[0]).toEqual([
+ 'rules[0].when',
+ JOB_RULES_WHEN.delayed.value,
+ ]);
+
+ findRulesStartInNumberInput().vm.$emit('input', dummyRulesStartInNumber);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(2);
+ expect(wrapper.emitted('update-job')[1]).toEqual([
+ 'rules[0].start_in',
+ `2 ${JOB_RULES_START_IN.second.value}s`,
+ ]);
+
+ findRulesStartInUnitSelect().vm.$emit('input', dummyRulesStartInUnit);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(3);
+ expect(wrapper.emitted('update-job')[2]).toEqual([
+ 'rules[0].start_in',
+ `2 ${dummyRulesStartInUnit}s`,
+ ]);
+
+ findRulesAllowFailureCheckBox().vm.$emit('input', dummyRulesAllowFailure);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(4);
+ expect(wrapper.emitted('update-job')[3]).toEqual([
+ 'rules[0].allow_failure',
+ dummyRulesAllowFailure,
+ ]);
+ });
+});
diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js
index 356de5b11e9..e03cd19da65 100644
--- a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js
@@ -5,6 +5,8 @@ import { stringify } from 'yaml';
import JobAssistantDrawer from '~/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue';
import JobSetupItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item.vue';
import ImageItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue';
+import RulesItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item.vue';
+import { JOB_RULES_WHEN } from '~/ci/pipeline_editor/components/job_assistant_drawer/constants';
import getRunnerTags from '~/ci/pipeline_editor/graphql/queries/runner_tags.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -23,10 +25,14 @@ describe('Job assistant drawer', () => {
const dummyJobScript = 'b';
const dummyImageName = 'c';
const dummyImageEntrypoint = 'd';
+ const dummyRulesWhen = JOB_RULES_WHEN.delayed.value;
+ const dummyRulesStartIn = '1 second';
+ const dummyRulesAllowFailure = true;
const findDrawer = () => wrapper.findComponent(GlDrawer);
const findJobSetupItem = () => wrapper.findComponent(JobSetupItem);
const findImageItem = () => wrapper.findComponent(ImageItem);
+ const findRulesItem = () => wrapper.findComponent(RulesItem);
const findConfirmButton = () => wrapper.findByTestId('confirm-button');
const findCancelButton = () => wrapper.findByTestId('cancel-button');
@@ -68,6 +74,10 @@ describe('Job assistant drawer', () => {
expect(findImageItem().exists()).toBe(true);
});
+ it('should contain rules accordion', () => {
+ expect(findRulesItem().exists()).toBe(true);
+ });
+
it('should emit close job assistant drawer event when closing the drawer', () => {
expect(wrapper.emitted('close-job-assistant-drawer')).toBeUndefined();
@@ -84,7 +94,7 @@ describe('Job assistant drawer', () => {
expect(wrapper.emitted('close-job-assistant-drawer')).toHaveLength(1);
});
- it('trigger validate if job name is empty', async () => {
+ it('should block submit if job name is empty', async () => {
findJobSetupItem().vm.$emit('update-job', 'script', 'b');
findConfirmButton().trigger('click');
@@ -95,12 +105,25 @@ describe('Job assistant drawer', () => {
expect(wrapper.emitted('updateCiConfig')).toBeUndefined();
});
+ it('should block submit if rules when is delayed and start in is out of range', async () => {
+ findRulesItem().vm.$emit('update-job', 'rules[0].when', JOB_RULES_WHEN.delayed.value);
+ findRulesItem().vm.$emit('update-job', 'rules[0].start_in', '2 weeks');
+ findConfirmButton().trigger('click');
+
+ await nextTick();
+
+ expect(wrapper.emitted('updateCiConfig')).toBeUndefined();
+ });
+
describe('when enter valid input', () => {
beforeEach(() => {
findJobSetupItem().vm.$emit('update-job', 'name', dummyJobName);
findJobSetupItem().vm.$emit('update-job', 'script', dummyJobScript);
findImageItem().vm.$emit('update-job', 'image.name', dummyImageName);
findImageItem().vm.$emit('update-job', 'image.entrypoint', [dummyImageEntrypoint]);
+ findRulesItem().vm.$emit('update-job', 'rules[0].allow_failure', dummyRulesAllowFailure);
+ findRulesItem().vm.$emit('update-job', 'rules[0].when', dummyRulesWhen);
+ findRulesItem().vm.$emit('update-job', 'rules[0].start_in', dummyRulesStartIn);
});
it('passes correct prop to accordions', () => {
@@ -113,6 +136,13 @@ describe('Job assistant drawer', () => {
name: dummyImageName,
entrypoint: [dummyImageEntrypoint],
},
+ rules: [
+ {
+ allow_failure: dummyRulesAllowFailure,
+ when: dummyRulesWhen,
+ start_in: dummyRulesStartIn,
+ },
+ ],
});
});
});
@@ -138,6 +168,24 @@ describe('Job assistant drawer', () => {
expect(findJobSetupItem().props('job')).toMatchObject({ name: '', script: '' });
});
+ it('should omit keys with default value when click add button', () => {
+ findRulesItem().vm.$emit('update-job', 'rules[0].allow_failure', false);
+ findRulesItem().vm.$emit('update-job', 'rules[0].when', JOB_RULES_WHEN.onSuccess.value);
+ findRulesItem().vm.$emit('update-job', 'rules[0].start_in', dummyRulesStartIn);
+ findConfirmButton().trigger('click');
+
+ expect(wrapper.emitted('updateCiConfig')).toStrictEqual([
+ [
+ `${wrapper.props('ciFileContent')}\n${stringify({
+ [dummyJobName]: {
+ script: dummyJobScript,
+ image: { name: dummyImageName, entrypoint: [dummyImageEntrypoint] },
+ },
+ })}`,
+ ],
+ ]);
+ });
+
it('should update correct ci content when click add button', () => {
findConfirmButton().trigger('click');
@@ -147,6 +195,13 @@ describe('Job assistant drawer', () => {
[dummyJobName]: {
script: dummyJobScript,
image: { name: dummyImageName, entrypoint: [dummyImageEntrypoint] },
+ rules: [
+ {
+ allow_failure: dummyRulesAllowFailure,
+ when: dummyRulesWhen,
+ start_in: dummyRulesStartIn,
+ },
+ ],
},
})}`,
],
diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
index 2ccf2c0392f..8cd651c5b36 100644
--- a/spec/frontend/fixtures/projects.rb
+++ b/spec/frontend/fixtures/projects.rb
@@ -67,7 +67,7 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
end
end
- describe 'Storage', feature_category: :subscription_cost_management do
+ describe 'Storage', feature_category: :consumables_cost_management do
describe GraphQL::Query, type: :request do
include GraphqlHelpers
context 'project storage statistics query' do
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
index 60bb055b1db..6cfc80966b5 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
@@ -1,9 +1,11 @@
-import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import { GlButton, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { s__ } from '~/locale';
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import ListPage from '~/packages_and_registries/package_registry/pages/list.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue';
@@ -30,6 +32,7 @@ describe('PackagesListApp', () => {
emptyListIllustration: 'emptyListIllustration',
isGroupPage: true,
fullPath: 'gitlab-org',
+ settingsPath: 'settings-path',
};
const PackageList = {
@@ -49,6 +52,7 @@ describe('PackagesListApp', () => {
const findListComponent = () => wrapper.findComponent(PackageList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findDeletePackages = () => wrapper.findComponent(DeletePackages);
+ const findSettingsLink = () => wrapper.findComponent(GlButton);
const mountComponent = ({
resolver = jest.fn().mockResolvedValue(packagesListQuery()),
@@ -71,9 +75,13 @@ describe('PackagesListApp', () => {
GlLoadingIcon,
GlSprintf,
GlLink,
+ PackageTitle,
PackageList,
DeletePackages,
},
+ directives: {
+ GlTooltip: createMockDirective('gl-tooltip'),
+ },
});
};
@@ -103,6 +111,52 @@ describe('PackagesListApp', () => {
});
});
+ describe('link to settings', () => {
+ describe('when settings path is not provided', () => {
+ beforeEach(() => {
+ mountComponent({
+ provide: {
+ ...defaultProvide,
+ settingsPath: '',
+ },
+ });
+ });
+
+ it('is not rendered', () => {
+ expect(findSettingsLink().exists()).toBe(false);
+ });
+ });
+
+ describe('when settings path is provided', () => {
+ const label = s__('PackageRegistry|Configure in settings');
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('is rendered', () => {
+ expect(findSettingsLink().exists()).toBe(true);
+ });
+
+ it('has the right icon', () => {
+ expect(findSettingsLink().props('icon')).toBe('settings');
+ });
+
+ it('has the right attributes', () => {
+ expect(findSettingsLink().attributes()).toMatchObject({
+ 'aria-label': label,
+ href: defaultProvide.settingsPath,
+ });
+ });
+
+ it('sets tooltip with right label', () => {
+ const tooltip = getBinding(findSettingsLink().element, 'gl-tooltip');
+
+ expect(tooltip.value).toBe(label);
+ });
+ });
+ });
+
describe('search component', () => {
it('exists', () => {
mountComponent();
diff --git a/spec/frontend/projects/commit/components/branches_dropdown_spec.js b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
index 0e68bd21cd4..5210abe154d 100644
--- a/spec/frontend/projects/commit/components/branches_dropdown_spec.js
+++ b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
@@ -68,13 +68,11 @@ describe('BranchesDropdown', () => {
describe('When searching', () => {
it('invokes fetchBranches', async () => {
- const spy = jest.spyOn(wrapper.vm, 'fetchBranches');
-
findDropdown().vm.$emit('search', '_anything_');
await nextTick();
- expect(spy).toHaveBeenCalledWith('_anything_');
+ expect(spyFetchBranches).toHaveBeenCalledWith(expect.any(Object), '_anything_');
});
});
});
diff --git a/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js b/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js
index cae21189ee0..b644b7a9421 100644
--- a/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js
+++ b/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js
@@ -1,23 +1,24 @@
import { GlToggle } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking } from 'helpers/tracking_helper';
import Subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
describe('Subscriptions', () => {
let wrapper;
+ let trackingSpy;
const findToggleButton = () => wrapper.findComponent(GlToggle);
+ const findTooltip = () => wrapper.findComponent({ ref: 'tooltip' });
- const mountComponent = (propsData) =>
- extendedWrapper(
- shallowMount(Subscriptions, {
- propsData,
- }),
- );
+ const mountComponent = (propsData) => {
+ wrapper = shallowMountExtended(Subscriptions, {
+ propsData,
+ });
+ };
it('shows loading spinner when loading', () => {
- wrapper = mountComponent({
+ mountComponent({
loading: true,
subscribed: undefined,
});
@@ -26,7 +27,7 @@ describe('Subscriptions', () => {
});
it('is toggled "off" when currently not subscribed', () => {
- wrapper = mountComponent({
+ mountComponent({
subscribed: false,
});
@@ -34,7 +35,7 @@ describe('Subscriptions', () => {
});
it('is toggled "on" when currently subscribed', () => {
- wrapper = mountComponent({
+ mountComponent({
subscribed: true,
});
@@ -43,44 +44,38 @@ describe('Subscriptions', () => {
it('toggleSubscription method emits `toggleSubscription` event on eventHub and Component', () => {
const id = 42;
- wrapper = mountComponent({ subscribed: true, id });
+ mountComponent({ subscribed: true, id });
const eventHubSpy = jest.spyOn(eventHub, '$emit');
- const wrapperEmitSpy = jest.spyOn(wrapper.vm, '$emit');
- wrapper.vm.toggleSubscription();
+ findToggleButton().vm.$emit('change');
expect(eventHubSpy).toHaveBeenCalledWith('toggleSubscription', id);
- expect(wrapperEmitSpy).toHaveBeenCalledWith('toggleSubscription', id);
- eventHubSpy.mockRestore();
- wrapperEmitSpy.mockRestore();
+ expect(wrapper.emitted('toggleSubscription')).toEqual([[id]]);
});
it('tracks the event when toggled', () => {
- wrapper = mountComponent({ subscribed: true });
-
- const wrapperTrackSpy = jest.spyOn(wrapper.vm, 'track');
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+ mountComponent({ subscribed: true });
- wrapper.vm.toggleSubscription();
+ findToggleButton().vm.$emit('change');
- expect(wrapperTrackSpy).toHaveBeenCalledWith('toggle_button', {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'toggle_button', {
+ category: undefined,
+ label: 'right_sidebar',
property: 'notifications',
value: 0,
});
- wrapperTrackSpy.mockRestore();
});
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
- wrapper = mountComponent({ subscribed: true });
- const spy = jest.spyOn(wrapper.vm, '$emit');
-
- wrapper.vm.onClickCollapsedIcon();
+ mountComponent({ subscribed: true });
+ findTooltip().trigger('click');
- expect(spy).toHaveBeenCalledWith('toggleSidebar');
- spy.mockRestore();
+ expect(wrapper.emitted('toggleSidebar')).toHaveLength(1);
});
it('has visually hidden label', () => {
- wrapper = mountComponent();
+ mountComponent();
expect(findToggleButton().props()).toMatchObject({
label: 'Notifications',
@@ -92,7 +87,7 @@ describe('Subscriptions', () => {
const subscribeDisabledDescription = 'Notifications have been disabled';
beforeEach(() => {
- wrapper = mountComponent({
+ mountComponent({
subscribed: false,
projectEmailsDisabled: true,
subscribeDisabledDescription,
@@ -103,9 +98,7 @@ describe('Subscriptions', () => {
expect(wrapper.findByTestId('subscription-title').text()).toContain(
subscribeDisabledDescription,
);
- expect(wrapper.findComponent({ ref: 'tooltip' }).attributes('title')).toBe(
- subscribeDisabledDescription,
- );
+ expect(findTooltip().attributes('title')).toBe(subscribeDisabledDescription);
});
it('does not render the toggle button', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
index 7bda37bcaa8..3964d895b4b 100644
--- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
@@ -346,6 +346,16 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
});
});
+ describe('when contentEditor is disabled', () => {
+ it('resets the editingMode to markdownField', async () => {
+ localStorage.setItem('gl-markdown-editor-mode', 'contentEditor');
+
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234', enableContentEditor: false } });
+
+ expect(wrapper.vm.editingMode).toBe(EDITING_MODE_MARKDOWN_FIELD);
+ });
+ });
+
describe(`when editingMode is ${EDITING_MODE_CONTENT_EDITOR}`, () => {
beforeEach(async () => {
buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
diff --git a/spec/helpers/packages_helper_spec.rb b/spec/helpers/packages_helper_spec.rb
index b6546a2eaf3..dcc5e336253 100644
--- a/spec/helpers/packages_helper_spec.rb
+++ b/spec/helpers/packages_helper_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe PackagesHelper, feature_category: :package_registry do
using RSpec::Parameterized::TableSyntax
+ include AdminModeHelper
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:base_url) { "#{Gitlab.config.gitlab.url}/api/v4/" }
@@ -127,4 +128,72 @@ RSpec.describe PackagesHelper, feature_category: :package_registry do
it { is_expected.to eq(expected_result) }
end
end
+
+ describe '#show_group_package_registry_settings' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+
+ before do
+ allow(helper).to receive(:current_user) { user }
+ end
+
+ subject { helper.show_group_package_registry_settings(group) }
+
+ context 'with package registry config enabled' do
+ before do
+ stub_config(packages: { enabled: true })
+ end
+
+ context "with admin", :enable_admin_mode do
+ before do
+ allow(helper).to receive(:current_user) { admin }
+ end
+
+ it { is_expected.to be(true) }
+ end
+
+ context "with owner" do
+ before do
+ group.add_owner(user)
+ end
+
+ it { is_expected.to be(true) }
+ end
+
+ %i[maintainer developer reporter guest].each do |role|
+ context "with #{role}" do
+ before do
+ group.public_send("add_#{role}", user)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+ end
+
+ context 'with package registry config disabled' do
+ before do
+ stub_config(packages: { enabled: false })
+ end
+
+ context "with admin", :enable_admin_mode do
+ before do
+ allow(helper).to receive(:current_user) { admin }
+ end
+
+ it { is_expected.to be(false) }
+ end
+
+ %i[owner maintainer developer reporter guest].each do |role|
+ context "with #{role}" do
+ before do
+ group.public_send("add_#{role}", user)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+ end
+ end
end
diff --git a/spec/migrations/20220802204737_remove_deactivated_user_highest_role_stats_spec.rb b/spec/migrations/20220802204737_remove_deactivated_user_highest_role_stats_spec.rb
index 36c65612bb9..b731a8c8c18 100644
--- a/spec/migrations/20220802204737_remove_deactivated_user_highest_role_stats_spec.rb
+++ b/spec/migrations/20220802204737_remove_deactivated_user_highest_role_stats_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_migration!
-RSpec.describe RemoveDeactivatedUserHighestRoleStats, feature_category: :subscription_cost_management do
+RSpec.describe RemoveDeactivatedUserHighestRoleStats, feature_category: :seat_cost_management do
let!(:users) { table(:users) }
let!(:user_highest_roles) { table(:user_highest_roles) }
diff --git a/spec/migrations/20221104115712_backfill_project_statistics_storage_size_without_uploads_size_spec.rb b/spec/migrations/20221104115712_backfill_project_statistics_storage_size_without_uploads_size_spec.rb
index d86720365c4..9658b5a699a 100644
--- a/spec/migrations/20221104115712_backfill_project_statistics_storage_size_without_uploads_size_spec.rb
+++ b/spec/migrations/20221104115712_backfill_project_statistics_storage_size_without_uploads_size_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_migration!
RSpec.describe BackfillProjectStatisticsStorageSizeWithoutUploadsSize,
- feature_category: :subscription_cost_management do
+ feature_category: :consumables_cost_management do
let!(:batched_migration) { described_class::MIGRATION_CLASS }
it 'does not schedule background jobs when Gitlab.org_or_com? is false' do
diff --git a/spec/migrations/20221221110733_remove_temp_index_for_project_statistics_upload_size_migration_spec.rb b/spec/migrations/20221221110733_remove_temp_index_for_project_statistics_upload_size_migration_spec.rb
index 1b6899b5b30..440a932c76b 100644
--- a/spec/migrations/20221221110733_remove_temp_index_for_project_statistics_upload_size_migration_spec.rb
+++ b/spec/migrations/20221221110733_remove_temp_index_for_project_statistics_upload_size_migration_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_migration!
RSpec.describe RemoveTempIndexForProjectStatisticsUploadSizeMigration,
- feature_category: :subscription_cost_management do
+ feature_category: :consumables_cost_management do
let(:table_name) { 'project_statistics' }
let(:index_name) { described_class::INDEX_NAME }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 5759d9b06f5..8fd9cb0be28 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -3733,7 +3733,7 @@ RSpec.describe Group, feature_category: :subgroups do
end
end
- describe '#usage_quotas_enabled?', feature_category: :subscription_cost_management, unless: Gitlab.ee? do
+ describe '#usage_quotas_enabled?', feature_category: :consumables_cost_management, unless: Gitlab.ee? do
using RSpec::Parameterized::TableSyntax
where(:feature_enabled, :root_group, :result) do
diff --git a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
index 16dd0dfcfcb..c1ac0367853 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe 'getting Alert Management Alert Notes', feature_category: :team_p
expect(first_notes_result.first).to include(
'id' => first_system_note.to_global_id.to_s,
- 'systemNoteIconName' => 'git-merge',
+ 'systemNoteIconName' => 'merge',
'body' => first_system_note.note
)
end
diff --git a/spec/requests/groups/usage_quotas_controller_spec.rb b/spec/requests/groups/usage_quotas_controller_spec.rb
index a329398aab3..67aef23704a 100644
--- a/spec/requests/groups/usage_quotas_controller_spec.rb
+++ b/spec/requests/groups/usage_quotas_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Groups::UsageQuotasController, :with_license, feature_category: :subscription_cost_management do
+RSpec.describe Groups::UsageQuotasController, :with_license, feature_category: :consumables_cost_management do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/projects/usage_quotas_spec.rb b/spec/requests/projects/usage_quotas_spec.rb
index 60ab64c30c3..33b206c8dc0 100644
--- a/spec/requests/projects/usage_quotas_spec.rb
+++ b/spec/requests/projects/usage_quotas_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Project Usage Quotas', feature_category: :subscription_cost_management do
+RSpec.describe 'Project Usage Quotas', feature_category: :consumables_cost_management do
let_it_be(:project) { create(:project) }
let_it_be(:role) { :maintainer }
let_it_be(:user) { create(:user) }
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 0e50ee953b7..5e0481e92f7 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -285,7 +285,6 @@
- './ee/spec/features/groups/audit_events_spec.rb'
- './ee/spec/features/groups/billing_spec.rb'
- './ee/spec/features/groups/contribution_analytics_spec.rb'
-- './ee/spec/features/groups/feature_discovery_moments_spec.rb'
- './ee/spec/features/groups/group_overview_spec.rb'
- './ee/spec/features/groups/group_page_with_external_authorization_service_spec.rb'
- './ee/spec/features/groups/group_projects_spec.rb'
@@ -989,7 +988,6 @@
- './ee/spec/helpers/ee/wiki_helper_spec.rb'
- './ee/spec/helpers/epics_helper_spec.rb'
- './ee/spec/helpers/gitlab_subscriptions/upcoming_reconciliation_helper_spec.rb'
-- './ee/spec/helpers/groups/feature_discovery_moments_helper_spec.rb'
- './ee/spec/helpers/groups/ldap_sync_helper_spec.rb'
- './ee/spec/helpers/groups/security_features_helper_spec.rb'
- './ee/spec/helpers/groups/sso_helper_spec.rb'
@@ -2327,7 +2325,6 @@
- './ee/spec/requests/groups_controller_spec.rb'
- './ee/spec/requests/groups/epics/epic_links_controller_spec.rb'
- './ee/spec/requests/groups/epics/related_epic_links_controller_spec.rb'
-- './ee/spec/requests/groups/feature_discovery_moments_spec.rb'
- './ee/spec/requests/groups/group_members_controller_spec.rb'
- './ee/spec/requests/groups/hook_logs_controller_spec.rb'
- './ee/spec/requests/groups/labels_spec.rb'
@@ -3102,7 +3099,6 @@
- './ee/spec/views/groups/_compliance_frameworks.html.haml_spec.rb'
- './ee/spec/views/groups/compliance_frameworks/new.html.haml_spec.rb'
- './ee/spec/views/groups/edit.html.haml_spec.rb'
-- './ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb'
- './ee/spec/views/groups/hook_logs/show.html.haml_spec.rb'
- './ee/spec/views/groups/hooks/edit.html.haml_spec.rb'
- './ee/spec/views/groups/security/discover/show.html.haml_spec.rb'
@@ -3112,7 +3108,6 @@
- './ee/spec/views/layouts/checkout.html.haml_spec.rb'
- './ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb'
- './ee/spec/views/layouts/header/_ee_subscribable_banner.html.haml_spec.rb'
-- './ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb'
- './ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb'
- './ee/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb'
- './ee/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb'
diff --git a/spec/views/groups/packages/index.html.haml_spec.rb b/spec/views/groups/packages/index.html.haml_spec.rb
new file mode 100644
index 00000000000..26f6268a224
--- /dev/null
+++ b/spec/views/groups/packages/index.html.haml_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/packages/index.html.haml', feature_category: :package_registry do
+ let_it_be(:group) { build(:group) }
+
+ subject { rendered }
+
+ before do
+ assign(:group, group)
+ end
+
+ it 'renders vue entrypoint' do
+ render
+
+ expect(rendered).to have_selector('#js-vue-packages-list')
+ end
+
+ describe 'settings path' do
+ it 'without permission sets empty settings path' do
+ allow(view).to receive(:show_group_package_registry_settings).and_return(false)
+
+ render
+
+ expect(rendered).to have_selector('[data-settings-path=""]')
+ end
+
+ it 'with permission sets group settings path' do
+ allow(view).to receive(:show_group_package_registry_settings).and_return(true)
+
+ render
+
+ expect(rendered).to have_selector(
+ "[data-settings-path=\"#{group_settings_packages_and_registries_path(group)}\"]"
+ )
+ end
+ end
+end
diff --git a/spec/views/projects/packages/index.html.haml_spec.rb b/spec/views/projects/packages/index.html.haml_spec.rb
new file mode 100644
index 00000000000..2557ceb70b3
--- /dev/null
+++ b/spec/views/projects/packages/index.html.haml_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'projects/packages/packages/index.html.haml', feature_category: :package_registry do
+ let_it_be(:project) { build(:project) }
+
+ subject { rendered }
+
+ before do
+ assign(:project, project)
+ end
+
+ it 'renders vue entrypoint' do
+ render
+
+ expect(rendered).to have_selector('#js-vue-packages-list')
+ end
+
+ describe 'settings path' do
+ it 'without permission sets empty settings path' do
+ allow(view).to receive(:show_package_registry_settings).and_return(false)
+
+ render
+
+ expect(rendered).to have_selector('[data-settings-path=""]')
+ end
+
+ it 'with permission sets project settings path' do
+ allow(view).to receive(:show_package_registry_settings).and_return(true)
+
+ render
+
+ expect(rendered).to have_selector(
+ "[data-settings-path=\"#{project_settings_packages_and_registries_path(project)}\"]"
+ )
+ end
+ end
+end
diff --git a/spec/workers/update_highest_role_worker_spec.rb b/spec/workers/update_highest_role_worker_spec.rb
index 94811260f0e..3e4a2f6be36 100644
--- a/spec/workers/update_highest_role_worker_spec.rb
+++ b/spec/workers/update_highest_role_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state, feature_category: :subscription_cost_management do
+RSpec.describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state, feature_category: :seat_cost_management do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/users/deactivate_dormant_users_worker_spec.rb b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
index bd6c6dfc6b2..fdcbb624562 100644
--- a/spec/workers/users/deactivate_dormant_users_worker_spec.rb
+++ b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :subscription_cost_management do
+RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :seat_cost_management do
using RSpec::Parameterized::TableSyntax
describe '#perform' do
diff --git a/spec/workers/users/migrate_records_to_ghost_user_in_batches_worker_spec.rb b/spec/workers/users/migrate_records_to_ghost_user_in_batches_worker_spec.rb
index 73faffb5387..38ea7c43267 100644
--- a/spec/workers/users/migrate_records_to_ghost_user_in_batches_worker_spec.rb
+++ b/spec/workers/users/migrate_records_to_ghost_user_in_batches_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Users::MigrateRecordsToGhostUserInBatchesWorker, feature_category: :subscription_cost_management do
+RSpec.describe Users::MigrateRecordsToGhostUserInBatchesWorker, feature_category: :seat_cost_management do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }