summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml8
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js2
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item.vue1
-rw-r--r--app/helpers/projects_helper.rb12
-rw-r--r--app/helpers/sidebars_helper.rb16
-rw-r--r--app/mailers/emails/projects.rb32
-rw-r--r--app/mailers/notify.rb11
-rw-r--r--app/models/ci/commit_with_pipeline.rb2
-rw-r--r--app/models/concerns/require_email_verification.rb1
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/policies/issuable_policy.rb2
-rw-r--r--app/services/event_create_service.rb2
-rw-r--r--app/services/projects/android_target_platform_detector_service.rb35
-rw-r--r--app/services/projects/open_issues_count_service.rb2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/workers/projects/record_target_platforms_worker.rb22
-rw-r--r--config/feature_flags/development/allow_dots_on_tf_state_names.yml8
-rw-r--r--config/feature_flags/development/last_pipeline_from_pipeline_status.yml8
-rw-r--r--config/feature_flags/development/scan_result_role_action.yml2
-rw-r--r--config/feature_flags/development/sidekiq_execution_application_slis.yml (renamed from config/feature_flags/development/detect_android_projects.yml)10
-rw-r--r--doc/api/job_artifacts.md6
-rw-r--r--doc/tutorials/build_application.md31
-rw-r--r--doc/tutorials/develop.md17
-rw-r--r--doc/tutorials/gitlab_navigation.md20
-rw-r--r--doc/tutorials/index.md128
-rw-r--r--doc/tutorials/infrastructure.md15
-rw-r--r--doc/tutorials/learn_git.md17
-rw-r--r--doc/tutorials/more_tutorials.md20
-rw-r--r--doc/tutorials/plan_and_track.md18
-rw-r--r--doc/tutorials/secure_application.md17
-rw-r--r--doc/user/infrastructure/iac/terraform_state.md6
-rw-r--r--doc/user/infrastructure/iac/troubleshooting.md9
-rw-r--r--lib/api/terraform/state.rb19
-rw-r--r--lib/banzai/reference_parser/user_parser.rb2
-rw-r--r--lib/gitlab/metrics/sidekiq_slis.rb39
-rw-r--r--lib/gitlab/sidekiq_middleware/server_metrics.rb13
-rw-r--r--lib/gitlab/workhorse.rb9
-rw-r--r--lib/sidebars/menu_item.rb3
-rw-r--r--lib/sidebars/projects/menus/issues_menu.rb1
-rw-r--r--lib/sidebars/projects/menus/repository_menu.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb12
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_spec.js7
-rw-r--r--spec/helpers/projects_helper_spec.rb74
-rw-r--r--spec/helpers/sidebars_helper_spec.rb44
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_slis_spec.rb65
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb79
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb32
-rw-r--r--spec/lib/sidebars/menu_item_spec.rb10
-rw-r--r--spec/lib/sidebars/menu_spec.rb6
-rw-r--r--spec/lib/sidebars/static_menu_spec.rb6
-rw-r--r--spec/mailers/emails/merge_requests_spec.rb15
-rw-r--r--spec/mailers/emails/pipelines_spec.rb18
-rw-r--r--spec/mailers/notify_spec.rb60
-rw-r--r--spec/models/ci/commit_with_pipeline_spec.rb45
-rw-r--r--spec/models/concerns/require_email_verification_spec.rb16
-rw-r--r--spec/requests/api/terraform/state_spec.rb82
-rw-r--r--spec/services/projects/android_target_platform_detector_service_spec.rb30
-rw-r--r--spec/services/projects/record_target_platforms_service_spec.rb68
-rw-r--r--spec/support/rspec_order_todo.yml1
-rw-r--r--spec/workers/projects/record_target_platforms_worker_spec.rb67
-rw-r--r--workhorse/internal/sendurl/sendurl.go15
-rw-r--r--workhorse/internal/sendurl/sendurl_test.go48
63 files changed, 932 insertions, 444 deletions
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index 178902731e6..365128bf6c5 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -506,8 +506,6 @@ Layout/ArgumentAlignment:
- 'app/graphql/types/work_items/widgets/start_and_due_date_update_input_type.rb'
- 'app/graphql/types/x509_certificate_type.rb'
- 'app/graphql/types/x509_issuer_type.rb'
- - 'app/mailers/emails/projects.rb'
- - 'app/mailers/notify.rb'
- 'app/models/abuse_report.rb'
- 'app/models/achievements/achievement.rb'
- 'app/models/achievements/user_achievement.rb'
@@ -1053,8 +1051,6 @@ Layout/ArgumentAlignment:
- 'ee/app/graphql/types/work_items/widgets/status_filter_input_type.rb'
- 'ee/app/graphql/types/work_items/widgets/status_input_type.rb'
- 'ee/app/graphql/types/work_items/widgets/weight_input_type.rb'
- - 'ee/app/mailers/ee/emails/projects.rb'
- - 'ee/app/mailers/emails/namespace_storage_usage_mailer.rb'
- 'ee/app/models/approval_wrapped_rule.rb'
- 'ee/app/models/dast/pre_scan_verification.rb'
- 'ee/app/models/deployments/approval.rb'
@@ -1371,7 +1367,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/lib/gitlab/zoekt/search_results_spec.rb'
- 'ee/spec/lib/incident_management/oncall_shift_generator_spec.rb'
- 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
- - 'ee/spec/mailers/notify_spec.rb'
- 'ee/spec/models/approval_wrapped_code_owner_rule_spec.rb'
- 'ee/spec/models/dast/pre_scan_verification_step_spec.rb'
- 'ee/spec/models/dast_site_profile_spec.rb'
@@ -2326,9 +2321,6 @@ Layout/ArgumentAlignment:
- 'spec/lib/security/weak_passwords_spec.rb'
- 'spec/lib/sidebars/projects/menus/repository_menu_spec.rb'
- 'spec/lib/uploaded_file_spec.rb'
- - 'spec/mailers/emails/merge_requests_spec.rb'
- - 'spec/mailers/emails/pipelines_spec.rb'
- - 'spec/mailers/notify_spec.rb'
- 'spec/models/analytics/cycle_analytics/stage_spec.rb'
- 'spec/models/application_setting_spec.rb'
- 'spec/models/clusters/cluster_spec.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 8504aaee864..72cc2ec4c89 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-f7fb715670e142388ce27fc6915639ec81a687b6
+a69ac6a466120a1e7f50ec21fe09d546cf22f279
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index 7bb6bc7e9bc..33a71c3a65b 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -33,7 +33,7 @@ export default class ShortcutsNavigation extends Shortcuts {
findAndFollowLink('.shortcuts-project-activity'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_RELEASES), () =>
- findAndFollowLink('.shortcuts-project-releases'),
+ findAndFollowLink('.shortcuts-deployments-releases'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_FILES), () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind(keysFor(GO_TO_PROJECT_COMMITS), () => findAndFollowLink('.shortcuts-commits'));
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue
index 223fbe6d078..ee09a07f5be 100644
--- a/app/assets/javascripts/super_sidebar/components/nav_item.vue
+++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue
@@ -126,6 +126,7 @@ export default {
'gl-bg-t-gray-a-08': this.isActive,
'gl-py-2': this.isPinnable,
'gl-py-3': !this.isPinnable,
+ [this.item.link_classes]: this.item.link_classes,
...this.linkClasses,
};
},
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 54f266f901d..867cb1da41c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -229,6 +229,18 @@ module ProjectsHelper
.load_in_batch_for_projects(projects)
end
+ def last_pipeline_from_status_cache(project)
+ if Feature.enabled?(:last_pipeline_from_pipeline_status, project)
+ pipeline_status = project.pipeline_status
+ return unless pipeline_status.has_status?
+
+ # commits have far more attributes than id, but last_pipeline only requires sha
+ return Commit.from_hash({ id: pipeline_status.sha }, project).last_pipeline
+ end
+
+ project.last_pipeline
+ end
+
def show_no_ssh_key_message?
Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
cookies[:hide_no_ssh_message].blank? &&
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 5bbc89a9d65..81ff29846c5 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -90,7 +90,7 @@ module SidebarsHelper
update_pins_url: pins_url,
is_impersonating: impersonating?,
stop_impersonation_path: admin_impersonation_path,
- shortcut_links: shortcut_links
+ shortcut_links: shortcut_links(user, project: project)
}
end
@@ -340,8 +340,8 @@ module SidebarsHelper
!!session[:impersonator_id]
end
- def shortcut_links
- [
+ def shortcut_links(user, project: nil)
+ shortcut_links = [
{
title: _('Milestones'),
href: dashboard_milestones_path,
@@ -358,6 +358,16 @@ module SidebarsHelper
css_class: 'dashboard-shortcuts-activity'
}
]
+
+ if project && can?(user, :create_issue, project)
+ shortcut_links << {
+ title: _('Create a new issue'),
+ href: new_project_issue_path(project),
+ css_class: 'shortcuts-new-issue'
+ }
+ end
+
+ shortcut_links
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 4bb624c27e9..63eb4eb8fd8 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -7,29 +7,37 @@ module Emails
@project = Project.find project_id
@target_url = project_url(@project)
@old_path_with_namespace = old_path_with_namespace
- mail_with_locale(to: @user.notification_email_for(@project.group),
- subject: subject("Project was moved"))
+ mail_with_locale(
+ to: @user.notification_email_for(@project.group),
+ subject: subject("Project was moved")
+ )
end
def project_was_exported_email(current_user, project)
@project = project
- mail_with_locale(to: current_user.notification_email_for(project.group),
- subject: subject("Project was exported"))
+ mail_with_locale(
+ to: current_user.notification_email_for(project.group),
+ subject: subject("Project was exported")
+ )
end
def project_was_not_exported_email(current_user, project, errors)
@project = project
@errors = errors
- mail_with_locale(to: current_user.notification_email_for(@project.group),
- subject: subject("Project export error"))
+ mail_with_locale(
+ to: current_user.notification_email_for(@project.group),
+ subject: subject("Project export error")
+ )
end
def repository_cleanup_success_email(project, user)
@project = project
@user = user
- mail_with_locale(to: user.notification_email_for(project.group),
- subject: subject("Project cleanup has completed"))
+ mail_with_locale(
+ to: user.notification_email_for(project.group),
+ subject: subject("Project cleanup has completed")
+ )
end
def repository_cleanup_failure_email(project, user, error)
@@ -52,9 +60,11 @@ module Emails
add_project_headers
headers['X-GitLab-Author'] = @message.author_username
- mail_with_locale(from: sender(@message.author_id, send_from_user_email: @message.send_from_committer_email?),
- reply_to: @message.reply_to,
- subject: @message.subject)
+ mail_with_locale(
+ from: sender(@message.author_id, send_from_user_email: @message.send_from_committer_email?),
+ reply_to: @message.reply_to,
+ subject: @message.subject
+ )
end
def prometheus_alert_fired_email(project, user, alert)
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 65fdc233ea1..036a0fc012e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -41,11 +41,12 @@ class Notify < ApplicationMailer
helper InProductMarketingHelper
def test_email(recipient_email, subject, body)
- mail_with_locale(to: recipient_email,
- subject: subject,
- body: body.html_safe,
- content_type: 'text/html'
- )
+ mail_with_locale(
+ to: recipient_email,
+ subject: subject,
+ body: body.html_safe,
+ content_type: 'text/html'
+ )
end
# Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com",
diff --git a/app/models/ci/commit_with_pipeline.rb b/app/models/ci/commit_with_pipeline.rb
index dde4b534aaa..2aa249df321 100644
--- a/app/models/ci/commit_with_pipeline.rb
+++ b/app/models/ci/commit_with_pipeline.rb
@@ -19,7 +19,7 @@ class Ci::CommitWithPipeline < SimpleDelegator
end
def lazy_latest_pipeline
- BatchLoader.for(sha).batch do |shas, loader|
+ BatchLoader.for(sha).batch(key: project.id) do |shas, loader|
preload_pipelines = project.ci_pipelines.latest_pipeline_per_commit(shas.compact)
shas.each do |sha|
diff --git a/app/models/concerns/require_email_verification.rb b/app/models/concerns/require_email_verification.rb
index 5ff4f520d24..d7182778b36 100644
--- a/app/models/concerns/require_email_verification.rb
+++ b/app/models/concerns/require_email_verification.rb
@@ -47,6 +47,7 @@ module RequireEmailVerification
def override_devise_lockable?
Feature.enabled?(:require_email_verification, self) &&
!two_factor_enabled? &&
+ identities.none? &&
Feature.disabled?(:skip_require_email_verification, self, type: :ops)
end
strong_memoize_attr :override_devise_lockable?
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 8ed5513aab9..100d6f037a9 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -156,7 +156,7 @@ class Snippet < ApplicationRecord
def for_project_with_user(project, user = nil)
return none unless project.snippets_visible?(user)
- if user && project.team.member?(user)
+ if project.team.member?(user)
project.snippets
else
project.snippets.public_to_user(user)
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index c9b936f9b06..400ac528018 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -4,7 +4,7 @@ class IssuablePolicy < BasePolicy
delegate { subject_container }
condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? }
- condition(:is_project_member) { @user && subject_container.member?(@user) }
+ condition(:is_project_member) { subject_container.member?(@user) }
condition(:can_read_issuable) { can?(:"read_#{@subject.to_ability_name}") }
desc "User is the assignee or author"
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 96edaa06fc2..1893cfcfcff 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -12,7 +12,7 @@ class EventCreateService
DEGIGN_EVENT_LABEL = 'usage_activity_by_stage_monthly.create.action_monthly_active_users_design_management'
MR_EVENT_LABEL = 'usage_activity_by_stage_monthly.create.merge_requests_users'
- MR_EVENT_PROPERTY = 'merge_requests_users'
+ MR_EVENT_PROPERTY = 'merge_request_action'
def open_issue(issue, current_user)
create_record_event(issue, current_user, :created)
diff --git a/app/services/projects/android_target_platform_detector_service.rb b/app/services/projects/android_target_platform_detector_service.rb
deleted file mode 100644
index 11635ad18d5..00000000000
--- a/app/services/projects/android_target_platform_detector_service.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- # Service class to detect if a project is made to run on the Android platform.
- #
- # This service searches for an AndroidManifest.xml file which all Android app
- # project must have. It returns the symbol :android if the given project is an
- # Android app project.
- #
- # Ref: https://developer.android.com/guide/topics/manifest/manifest-intro
- #
- # Example usage:
- # > AndroidTargetPlatformDetectorService.new(a_project).execute
- # => nil
- # > AndroidTargetPlatformDetectorService.new(an_android_project).execute
- # => :android
- class AndroidTargetPlatformDetectorService < BaseService
- # <manifest> element is required and must occur once inside AndroidManifest.xml
- MANIFEST_FILE_SEARCH_QUERY = '<manifest filename:AndroidManifest.xml'
-
- def execute
- detect
- end
-
- private
-
- def file_finder
- @file_finder ||= ::Gitlab::FileFinder.new(project, project.default_branch)
- end
-
- def detect
- return :android if file_finder.find(MANIFEST_FILE_SEARCH_QUERY).present?
- end
- end
-end
diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb
index 925512f31d7..b373a099020 100644
--- a/app/services/projects/open_issues_count_service.rb
+++ b/app/services/projects/open_issues_count_service.rb
@@ -26,7 +26,7 @@ module Projects
def user_is_at_least_reporter?
strong_memoize(:user_is_at_least_reporter) do
- @user && @project.team.member?(@user, Gitlab::Access::REPORTER)
+ @project.team.member?(@user, Gitlab::Access::REPORTER)
end
end
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 141118110ea..79a33316b1a 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,7 +12,7 @@
- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
-- last_pipeline = project.last_pipeline if show_pipeline_status_icon
+- last_pipeline = last_pipeline_from_status_cache(project) if show_pipeline_status_icon
- css_controls_class = "with-pipeline-status" if show_pipeline_status_icon && last_pipeline.present?
- css_metadata_classes = "gl-display-flex gl-align-items-center gl-ml-5 gl-reset-color! icon-wrapper has-tooltip"
diff --git a/app/workers/projects/record_target_platforms_worker.rb b/app/workers/projects/record_target_platforms_worker.rb
index e69450692d9..2e523ccc992 100644
--- a/app/workers/projects/record_target_platforms_worker.rb
+++ b/app/workers/projects/record_target_platforms_worker.rb
@@ -7,7 +7,6 @@ module Projects
LEASE_TIMEOUT = 1.hour.to_i
APPLE_PLATFORM_LANGUAGES = %w(swift objective-c).freeze
- ANDROID_PLATFORM_LANGUAGES = %w(java kotlin).freeze
feature_category :projects
data_consistency :always
@@ -32,28 +31,13 @@ module Projects
attr_reader :target_platforms, :project
def detector_service
- if uses_apple_platform_languages?
- AppleTargetPlatformDetectorService
- elsif uses_android_platform_languages? && detect_android_projects_enabled?
- AndroidTargetPlatformDetectorService
- end
- end
-
- def detect_android_projects_enabled?
- Feature.enabled?(:detect_android_projects, project)
- end
-
- def uses_apple_platform_languages?
- target_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES).present?
- end
+ return if target_languages.blank?
- def uses_android_platform_languages?
- target_languages.with_programming_language(*ANDROID_PLATFORM_LANGUAGES).present?
+ AppleTargetPlatformDetectorService
end
def target_languages
- languages = APPLE_PLATFORM_LANGUAGES + ANDROID_PLATFORM_LANGUAGES
- @target_languages ||= project.repository_languages.with_programming_language(*languages)
+ @target_languages ||= project.repository_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES)
end
def log_target_platforms_metadata
diff --git a/config/feature_flags/development/allow_dots_on_tf_state_names.yml b/config/feature_flags/development/allow_dots_on_tf_state_names.yml
new file mode 100644
index 00000000000..f7a981d11db
--- /dev/null
+++ b/config/feature_flags/development/allow_dots_on_tf_state_names.yml
@@ -0,0 +1,8 @@
+---
+name: allow_dots_on_tf_state_names
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106861
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/385597
+milestone: '15.7'
+type: development
+group: group::configure
+default_enabled: true
diff --git a/config/feature_flags/development/last_pipeline_from_pipeline_status.yml b/config/feature_flags/development/last_pipeline_from_pipeline_status.yml
new file mode 100644
index 00000000000..79bc83c73fe
--- /dev/null
+++ b/config/feature_flags/development/last_pipeline_from_pipeline_status.yml
@@ -0,0 +1,8 @@
+---
+name: last_pipeline_from_pipeline_status
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117742
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/407789
+milestone: '16.0'
+type: development
+group: group::pipeline execution
+default_enabled: false
diff --git a/config/feature_flags/development/scan_result_role_action.yml b/config/feature_flags/development/scan_result_role_action.yml
index e6a4a552350..62129692f80 100644
--- a/config/feature_flags/development/scan_result_role_action.yml
+++ b/config/feature_flags/development/scan_result_role_action.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/377866
milestone: '15.6'
type: development
group: group::security policies
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/detect_android_projects.yml b/config/feature_flags/development/sidekiq_execution_application_slis.yml
index 4270cbdff7c..4c1dcda82c0 100644
--- a/config/feature_flags/development/detect_android_projects.yml
+++ b/config/feature_flags/development/sidekiq_execution_application_slis.yml
@@ -1,8 +1,8 @@
---
-name: detect_android_projects
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85681
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360902
-milestone: '15.0'
+name: sidekiq_execution_application_slis
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116827
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/407325
+milestone: '15.11'
type: development
-group: group::activation
+group: group::scalability
default_enabled: false
diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md
index f121246b169..3b6eaf9b670 100644
--- a/doc/api/job_artifacts.md
+++ b/doc/api/job_artifacts.md
@@ -64,7 +64,7 @@ Possible response status codes:
> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.5.
-Download the artifacts zipped archive from the latest successful pipeline for
+Download the artifacts zipped archive from the latest **successful** pipeline for
the given reference name and job, provided the job finished successfully. This
is the same as [getting the job's artifacts](#get-job-artifacts), but by
defining the job's name instead of its ID.
@@ -167,8 +167,8 @@ Possible response status codes:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23538) in GitLab 11.5.
> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55042) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.10.
-Download a single artifact file for a specific job of the latest successful
-pipeline for the given reference name from inside the job's artifacts archive.
+Download a single artifact file for a specific job of the latest **successful** pipeline
+for the given reference name from inside the job's artifacts archive.
The file is extracted from the archive and streamed to the client.
The artifact file provides more detail than what is available in the
diff --git a/doc/tutorials/build_application.md b/doc/tutorials/build_application.md
new file mode 100644
index 00000000000..685cf408e77
--- /dev/null
+++ b/doc/tutorials/build_application.md
@@ -0,0 +1,31 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Build your application
+
+## Learn about CI/CD pipelines
+
+Use CI/CD pipelines to automatically build, test, and deploy your code.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [Create and run your first GitLab CI/CD pipeline](../ci/quick_start/index.md) | Create a `.gitlab-ci.yml` file and start a pipeline. | **{star}** |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Get started: Learn about CI/CD](https://www.youtube.com/watch?v=sIegJaLy2ug) (9m 02s) | Learn about the `.gitlab-ci.yml` file and how it's used. | **{star}** |
+| [GitLab CI/CD](https://levelup.gitlab.com/courses/continuous-integration-and-delivery-ci-cd-with-gitlab) | Learn about GitLab CI/CD and build a pipeline in this self-paced course. | **{star}** |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [CI deep dive](https://www.youtube.com/watch?v=ZVUbmVac-m8&list=PL05JrBw4t0KorkxIFgZGnzzxjZRCGROt_&index=27) (22m 51s) | Take a closer look at pipelines and continuous integration concepts. | |
+| [Set up CI/CD in the cloud](../ci/examples/index.md#cicd-in-the-cloud) | Learn how to set up CI/CD in different cloud-based environments. | |
+| [Find CI/CD examples and templates](../ci/examples/index.md#cicd-examples) | Use these examples and templates to set up CI/CD for your use case. | |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Understand CI/CD rules](https://www.youtube.com/watch?v=QjQc-zeL16Q) (8m 56s) | Learn more about how to use CI/CD rules. | |
+| [Use Auto DevOps to deploy an application](../topics/autodevops/cloud_deployments/auto_devops_with_gke.md) | Deploy an application to Google Kubernetes Engine (GKE). | |
+
+## Publish a static website
+
+Use GitLab Pages to publish a static website directly from your project.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [Create a Pages website from a CI/CD template](../user/project/pages/getting_started/pages_ci_cd_template.md) | Quickly generate a Pages website for your project using a CI/CD template for a popular Static Site Generator (SSG). | **{star}** |
+| [Create a Pages website from scratch](../user/project/pages/getting_started/pages_from_scratch.md) | Create all the components of a Pages website from a blank project. | |
diff --git a/doc/tutorials/develop.md b/doc/tutorials/develop.md
new file mode 100644
index 00000000000..08497a09830
--- /dev/null
+++ b/doc/tutorials/develop.md
@@ -0,0 +1,17 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Develop with GitLab
+
+## Integrate with GitLab
+
+GitLab [integrates](../user/project/integrations/index.md) with a number of third-party services,
+enabling you to work with those services directly from GitLab.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [Integrate with Jira](https://about.gitlab.com/blog/2021/04/12/gitlab-jira-integration-selfmanaged/) | Configure the Jira integration, so you can work with Jira issues from GitLab. | |
+| [Integrate with Gitpod](https://about.gitlab.com/blog/2021/07/19/teams-gitpod-integration-gitlab-speed-up-development/) | Integrate with Gitpod, to help speed up your development. | |
diff --git a/doc/tutorials/gitlab_navigation.md b/doc/tutorials/gitlab_navigation.md
new file mode 100644
index 00000000000..f481b8ff209
--- /dev/null
+++ b/doc/tutorials/gitlab_navigation.md
@@ -0,0 +1,20 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Find your way around GitLab
+
+Get to know the features of GitLab and where to find them so you can get up
+and running quickly.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Introduction to GitLab](https://youtu.be/_4SmIyQ5eis?t=90) (59m 51s) | Walk through recommended processes and example workflows for using GitLab. | **{star}** |
+| [GitLab with Git Essentials](https://levelup.gitlab.com/courses/gitlab-with-git-essentials) | Learn the basics of Git and GitLab in this self-paced course. | **{star}** |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Use GitLab for DevOps](https://www.youtube.com/watch?v=7q9Y1Cv-ib0) (12m 34s) | Use GitLab through the entire DevOps lifecycle, from planning to monitoring. | **{star}** |
+| [Use Markdown at GitLab](../user/markdown.md) | GitLab Flavored Markdown (GLFM) is used in many areas of GitLab, for example, in merge requests. | **{star}** |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Learn GitLab project walkthrough](https://www.youtube.com/watch?v=-oaI2WEKdI4&list=PL05JrBw4t0KofkHq4GZJ05FnNGa11PQ4d) (59m 2s) | Step through the tutorial-style issues in the **Learn GitLab** project. If you don't have this project, download [the export file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/project_templates/learn_gitlab_ultimate.tar.gz) and [import it to a new project](../user/project/settings/import_export.md#import-a-project-and-its-data). | |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [GitLab Continuous Delivery overview](https://www.youtube.com/watch?v=g-gO93PMZvk&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED&index=134) (17m 2s) | Learn how to use GitLab features to continuously build, test, and deploy iterative code changes. | |
+| [Productivity tips](https://about.gitlab.com/blog/2021/02/18/improve-your-gitlab-productivity-with-these-10-tips/) | Get tips to help make you a productive GitLab user. | |
diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md
index aee3adba919..b06cd224d0c 100644
--- a/doc/tutorials/index.md
+++ b/doc/tutorials/index.md
@@ -8,123 +8,11 @@ info: For assistance with this tutorials page, see https://about.gitlab.com/hand
These tutorials can help you learn how to use GitLab.
-## Find your way around GitLab
-
-Get to know the features of GitLab and where to find them so you can get up
-and running quickly.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Introduction to GitLab](https://youtu.be/_4SmIyQ5eis?t=90) (59m 51s) | Walk through recommended processes and example workflows for using GitLab. | **{star}** |
-| [GitLab with Git Essentials](https://levelup.gitlab.com/courses/gitlab-with-git-essentials) | Learn the basics of Git and GitLab in this self-paced course. | **{star}** |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Use GitLab for DevOps](https://www.youtube.com/watch?v=7q9Y1Cv-ib0) (12m 34s) | Use GitLab through the entire DevOps lifecycle, from planning to monitoring. | **{star}** |
-| [Use Markdown at GitLab](../user/markdown.md) | GitLab Flavored Markdown (GLFM) is used in many areas of GitLab, for example, in merge requests. | **{star}** |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Learn GitLab project walkthrough](https://www.youtube.com/watch?v=-oaI2WEKdI4&list=PL05JrBw4t0KofkHq4GZJ05FnNGa11PQ4d) (59m 2s) | Step through the tutorial-style issues in the **Learn GitLab** project. If you don't have this project, download [the export file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/project_templates/learn_gitlab_ultimate.tar.gz) and [import it to a new project](../user/project/settings/import_export.md#import-a-project-and-its-data). | |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [GitLab Continuous Delivery overview](https://www.youtube.com/watch?v=g-gO93PMZvk&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED&index=134) (17m 2s) | Learn how to use GitLab features to continuously build, test, and deploy iterative code changes. | |
-| [Productivity tips](https://about.gitlab.com/blog/2021/02/18/improve-your-gitlab-productivity-with-these-10-tips/) | Get tips to help make you a productive GitLab user. | |
-
-## Use Git
-
-GitLab is a Git-based platform, so understanding Git is important to get
-the most out of GitLab.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Make your first Git commit](make_your_first_git_commit.md) | Create a project, edit a file, and commit changes to a Git repository from the command line. | **{star}** |
-| [Start using Git on the command line](../gitlab-basics/start-using-git.md) | Learn how to set up Git, clone repositories, and work with branches. | **{star}** |
-| [Take advantage of Git rebase](https://about.gitlab.com/blog/2022/10/06/take-advantage-of-git-rebase/)| Learn how to use the `rebase` command in your workflow. | |
-| [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF of common Git commands. | |
-
-## Plan and track your work
-
-Create a project to host your code, and plan your work using
-issues, epics, and more.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [GitLab Agile Project Management](https://levelup.gitlab.com/courses/gitlab-agile-project-management) | Learn how to use planning features to manage your projects in this self-paced course. | **{star}** |
-| [Create a project from a template](https://gitlab.com/projects/new#create_from_template) | Choose a project template and create a project with files to get you started. | |
-| [Migrate to GitLab](../user/project/import/index.md) | If you are coming to GitLab from another platform, you can import or convert your projects. | |
-| [Run an agile iteration](agile_sprint.md) | Use group, projects, and iterations to run an agile development iteration. |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Epics and Issue Boards](https://www.youtube.com/watch?v=I1bFIAQBHB8) | Find out how to use epics and issue boards for project management. | |
-
-## Use CI/CD pipelines
-
-CI/CD pipelines are used to automatically build, test, and deploy your code.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Create and run your first GitLab CI/CD pipeline](../ci/quick_start/index.md) | Create a `.gitlab-ci.yml` file and start a pipeline. | **{star}** |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Get started: Learn about CI/CD](https://www.youtube.com/watch?v=sIegJaLy2ug) (9m 02s) | Learn about the `.gitlab-ci.yml` file and how it's used. | **{star}** |
-| [GitLab CI/CD](https://levelup.gitlab.com/courses/continuous-integration-and-delivery-ci-cd-with-gitlab) | Learn about GitLab CI/CD and build a pipeline in this self-paced course. | **{star}** |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [CI deep dive](https://www.youtube.com/watch?v=ZVUbmVac-m8&list=PL05JrBw4t0KorkxIFgZGnzzxjZRCGROt_&index=27) (22m 51s) | Take a closer look at pipelines and continuous integration concepts. | |
-| [Set up CI/CD in the cloud](../ci/examples/index.md#cicd-in-the-cloud) | Learn how to set up CI/CD in different cloud-based environments. | |
-| [Find CI/CD examples and templates](../ci/examples/index.md#cicd-examples) | Use these examples and templates to set up CI/CD for your use case. | |
-| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Understand CI/CD rules](https://www.youtube.com/watch?v=QjQc-zeL16Q) (8m 56s) | Learn more about how to use CI/CD rules. | |
-
-## Configure your applications and infrastructure
-
-Use GitLab configuration features to reduce the effort needed to
-configure the infrastructure for your application.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Use GitOps with GitLab](https://about.gitlab.com/blog/2022/04/07/the-ultimate-guide-to-gitops-with-gitlab/) | Learn how to provision and manage a base infrastructure, connect to a Kubernetes cluster, deploy third-party applications, and carry out other infrastructure automation tasks. | |
-| [Use Auto DevOps to deploy an application](../topics/autodevops/cloud_deployments/auto_devops_with_gke.md) | Deploy an application to Google Kubernetes Engine (GKE). | |
-
-## Publish a static website
-
-Use GitLab Pages to publish a static website directly from your project.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Create a Pages website from a CI/CD template](../user/project/pages/getting_started/pages_ci_cd_template.md) | Quickly generate a Pages website for your project using a CI/CD template for a popular Static Site Generator (SSG). | **{star}** |
-| [Create a Pages website from scratch](../user/project/pages/getting_started/pages_from_scratch.md) | Create all the components of a Pages website from a blank project. | |
-
-## Secure your application and check compliance
-
-GitLab can check your application for security vulnerabilities and that it meets compliance requirements.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Set up dependency scanning](https://about.gitlab.com/blog/2021/01/14/try-dependency-scanning/) | Try out dependency scanning, which checks for known vulnerabilities in dependencies. | **{star}** |
-| [Create a compliance pipeline](create_compliance_pipeline.md) | Learn how to create compliance pipelines for your groups. | **{star}** |
-| [Set up a scan result policy](scan_result_policy.md) | Learn how to configure a scan result policy that takes action based on scan results. | **{star}** |
-| [Get started with GitLab application security](../user/application_security/get-started-security.md) | Follow recommended steps to set up security tools. | |
-| [GitLab Security Essentials](https://levelup.gitlab.com/courses/security-essentials) | Learn about the essential security capabilities of GitLab in this self-paced course. | |
-
-## Work with a self-managed instance
-
-If you're an administrator of a self-managed instance of GitLab, these tutorials
-can help you manage and configure your instance.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Install GitLab](../install/install_methods.md) | Install GitLab according to your requirements.| |
-| [Get started administering GitLab](../administration/get_started.md) | Configure your organization and its authentication, then secure, monitor, and back up GitLab. | |
-
-## Integrate with GitLab
-
-GitLab [integrates](../user/project/integrations/index.md) with a number of third-party services,
-enabling you to work with those services directly from GitLab.
-
-| Topic | Description | Good for beginners |
-|-------|-------------|--------------------|
-| [Integrate with Jira](https://about.gitlab.com/blog/2021/04/12/gitlab-jira-integration-selfmanaged/) | Configure the Jira integration, so you can work with Jira issues from GitLab. | |
-| [Integrate with Gitpod](https://about.gitlab.com/blog/2021/07/19/teams-gitpod-integration-gitlab-speed-up-development/) | Integrate with Gitpod, to help speed up your development. | |
-
-## Find more tutorial content
-
-If you're learning about GitLab, here are some ways you can find more tutorial
-content:
-
-- Find learning tracks and certification options at [Level Up](https://levelup.gitlab.com/).
- GitLab learning platform login required (email and password for non-GitLab team members).
-
-- Find recent tutorials on the GitLab blog by [searching by the `tutorial` tag](https://about.gitlab.com/blog/tags.html#tutorial).
-
-- Browse the **Learn@GitLab** [playlist on YouTube](https://www.youtube.com/playlist?list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
- to find video tutorials.
-
-If you find an article, video, or other resource that would be a
-great addition to this page, add it in a [merge request](../development/documentation/index.md).
+- [Find your way around GitLab](gitlab_navigation.md)
+- [Learn Git](learn_git.md)
+- [Plan and track your work](plan_and_track.md)
+- [Build your application](build_application.md)
+- [Secure your application and check compliance](secure_application.md)
+- [Manage your infrastructure](infrastructure.md)
+- [Develop with GitLab](develop.md)
+- [Find more tutorials](more_tutorials.md)
diff --git a/doc/tutorials/infrastructure.md b/doc/tutorials/infrastructure.md
new file mode 100644
index 00000000000..e9035461596
--- /dev/null
+++ b/doc/tutorials/infrastructure.md
@@ -0,0 +1,15 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Manage your infrastructure
+
+Use GitLab configuration features to reduce the effort needed to
+configure the infrastructure for your application.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [Use GitOps with GitLab](https://about.gitlab.com/blog/2022/04/07/the-ultimate-guide-to-gitops-with-gitlab/) | Learn how to provision and manage a base infrastructure, connect to a Kubernetes cluster, deploy third-party applications, and carry out other infrastructure automation tasks. | |
+| [Set up Flux for GitOps](../user/clusters/agent/gitops/flux_tutorial.md) | Learn how to set up Flux for GitOps in a sample project. | |
diff --git a/doc/tutorials/learn_git.md b/doc/tutorials/learn_git.md
new file mode 100644
index 00000000000..484ab2b50b2
--- /dev/null
+++ b/doc/tutorials/learn_git.md
@@ -0,0 +1,17 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Learn Git
+
+GitLab is a Git-based platform, so understanding Git is important to get
+the most out of GitLab.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [Make your first Git commit](make_your_first_git_commit.md) | Create a project, edit a file, and commit changes to a Git repository from the command line. | **{star}** |
+| [Start using Git on the command line](../gitlab-basics/start-using-git.md) | Learn how to set up Git, clone repositories, and work with branches. | **{star}** |
+| [Take advantage of Git rebase](https://about.gitlab.com/blog/2022/10/06/take-advantage-of-git-rebase/)| Learn how to use the `rebase` command in your workflow. | |
+| [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF of common Git commands. | |
diff --git a/doc/tutorials/more_tutorials.md b/doc/tutorials/more_tutorials.md
new file mode 100644
index 00000000000..c52de180bff
--- /dev/null
+++ b/doc/tutorials/more_tutorials.md
@@ -0,0 +1,20 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Find more tutorial content
+
+If you're learning about GitLab, to find more tutorial content:
+
+- Find learning tracks and certification options at [Level Up](https://levelup.gitlab.com/).
+ GitLab learning platform login required (email and password for non-GitLab team members).
+
+- Find recent tutorials on the GitLab blog by [searching by the `tutorial` tag](https://about.gitlab.com/blog/tags.html#tutorial).
+
+- Browse the **Learn@GitLab** [playlist on YouTube](https://www.youtube.com/playlist?list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
+ to find video tutorials.
+
+If you find an article, video, or other resource that would be a
+great addition to this page, add it in a [merge request](../development/documentation/index.md).
diff --git a/doc/tutorials/plan_and_track.md b/doc/tutorials/plan_and_track.md
new file mode 100644
index 00000000000..de510fff2de
--- /dev/null
+++ b/doc/tutorials/plan_and_track.md
@@ -0,0 +1,18 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Plan and track your work
+
+Create a project to host your code, and plan your work using
+issues, epics, and more.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [GitLab Agile Project Management](https://levelup.gitlab.com/courses/gitlab-agile-project-management) | Learn how to use planning features to manage your projects in this self-paced course. | **{star}** |
+| [Create a project from a template](https://gitlab.com/projects/new#create_from_template) | Choose a project template and create a project with files to get you started. | |
+| [Migrate to GitLab](../user/project/import/index.md) | If you are coming to GitLab from another platform, you can import or convert your projects. | |
+| [Run an agile iteration](agile_sprint.md) | Use group, projects, and iterations to run an agile development iteration. |
+| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Epics and Issue Boards](https://www.youtube.com/watch?v=I1bFIAQBHB8) | Find out how to use epics and issue boards for project management. | |
diff --git a/doc/tutorials/secure_application.md b/doc/tutorials/secure_application.md
new file mode 100644
index 00000000000..19fcb64085a
--- /dev/null
+++ b/doc/tutorials/secure_application.md
@@ -0,0 +1,17 @@
+---
+stage: none
+group: Tutorials
+info: For assistance with this tutorials page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+---
+
+# Secure your application and check compliance
+
+GitLab can check your application for security vulnerabilities and that it meets compliance requirements.
+
+| Topic | Description | Good for beginners |
+|-------|-------------|--------------------|
+| [Set up dependency scanning](https://about.gitlab.com/blog/2021/01/14/try-dependency-scanning/) | Try out dependency scanning, which checks for known vulnerabilities in dependencies. | **{star}** |
+| [Create a compliance pipeline](create_compliance_pipeline.md) | Learn how to create compliance pipelines for your groups. | **{star}** |
+| [Set up a scan result policy](scan_result_policy.md) | Learn how to configure a scan result policy that takes action based on scan results. | **{star}** |
+| [Get started with GitLab application security](../user/application_security/get-started-security.md) | Follow recommended steps to set up security tools. | |
+| [GitLab Security Essentials](https://levelup.gitlab.com/courses/security-essentials) | Learn about the essential security capabilities of GitLab in this self-paced course. | |
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index 7b95a7267af..87404699ad7 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -7,8 +7,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab-managed Terraform state **(FREE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 13.0.
-> - Support for state names that contain periods introduced in GitLab 15.7 [with a flag](../../../administration/feature_flags.md) named `allow_dots_on_tf_state_names`. Disabled by default.
-> - Support for state names that contain periods [generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/385597) in GitLab 16.0. Feature flag `allow_dots_on_tf_state_names` removed.
+> - Support for state names that contain periods introduced in GitLab 15.7 [with a flag](../../../administration/feature_flags.md) named `allow_dots_on_tf_state_names`. Disabled by default. [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106861) in GitLab 15.7.
+
+FLAG:
+On self-managed GitLab, by default support for state names that contain periods is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `allow_dots_on_tf_state_names`. On GitLab.com, support for state names that contain periods is available. Requests for state files might generate HTTP 404 errors after enabling this feature. For more information, see [Troubleshooting the Terraform integration with GitLab](troubleshooting.md#state-not-found-if-the-state-name-contains-a-period).
Terraform uses state files to store details about your infrastructure configuration.
With Terraform remote [backends](https://www.terraform.io/language/settings/backends/configuration),
diff --git a/doc/user/infrastructure/iac/troubleshooting.md b/doc/user/infrastructure/iac/troubleshooting.md
index d770c0111d0..624bb5ff276 100644
--- a/doc/user/infrastructure/iac/troubleshooting.md
+++ b/doc/user/infrastructure/iac/troubleshooting.md
@@ -160,3 +160,12 @@ If your `TF_HTTP_ADDRESS`, `TF_HTTP_LOCK_ADDRESS` and `TF_HTTP_UNLOCK_ADDRESS` a
to update the state names there.
Alternatively, you can [migrate your terraform state](terraform_state.md#migrate-to-a-gitlab-managed-terraform-state).
+
+#### Self-managed GitLab instances
+
+By default, support for state names with periods is not enabled on self-managed GitLab.
+You can enable it from the Rails console:
+
+```ruby
+Feature.enable(:allow_dots_on_tf_state_names)
+```
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index 8017a195f28..184690f9979 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -55,6 +55,17 @@ module API
def remote_state_handler
::Terraform::RemoteStateHandler.new(user_project, current_user, name: params[:name], lock_id: params[:ID])
end
+
+ def not_found_for_dots?
+ Feature.disabled?(:allow_dots_on_tf_state_names) && params[:name].include?(".")
+ end
+
+ # Change the state name to behave like before, https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105674
+ # has been introduced. This behavior can be controlled via `allow_dots_on_tf_state_names` FF.
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106861
+ def legacy_state_name!
+ params[:name] = params[:name].split('.').first
+ end
end
desc 'Get a Terraform state by its name' do
@@ -72,6 +83,8 @@ module API
end
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get do
+ legacy_state_name! if not_found_for_dots?
+
remote_state_handler.find_with_lock do |state|
no_content! unless state.latest_file && state.latest_file.exists?
@@ -96,6 +109,7 @@ module API
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
post do
authorize! :admin_terraform_state, user_project
+ legacy_state_name! if not_found_for_dots?
data = request.body.read
no_content! if data.empty?
@@ -124,6 +138,7 @@ module API
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
delete do
authorize! :admin_terraform_state, user_project
+ legacy_state_name! if not_found_for_dots?
remote_state_handler.find_with_lock do |state|
::Terraform::States::TriggerDestroyService.new(state, current_user: current_user).execute
@@ -155,6 +170,8 @@ module API
requires :Path, type: String, desc: 'Terraform path'
end
post '/lock' do
+ not_found! if not_found_for_dots?
+
authorize! :admin_terraform_state, user_project
status_code = :ok
@@ -198,6 +215,8 @@ module API
optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID'
end
delete '/lock' do
+ not_found! if not_found_for_dots?
+
authorize! :admin_terraform_state, user_project
remote_state_handler.unlock!
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index c40ca9dc7cd..48e2bcc9a11 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -81,7 +81,7 @@ module Banzai
project = projects[node]
user = users[node]
- project && user ? project.team.member?(user) : false
+ project&.member?(user)
else
true
end
diff --git a/lib/gitlab/metrics/sidekiq_slis.rb b/lib/gitlab/metrics/sidekiq_slis.rb
new file mode 100644
index 00000000000..f28cf4ac967
--- /dev/null
+++ b/lib/gitlab/metrics/sidekiq_slis.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module SidekiqSlis
+ EXECUTION_URGENCY_DURATIONS = {
+ "high" => 10,
+ "low" => 300,
+ "throttled" => 300
+ }.freeze
+ # workers without urgency attribute have "low" urgency by default in
+ # WorkerAttributes.get_urgency, just mirroring it here
+ DEFAULT_EXECUTION_URGENCY_DURATION = EXECUTION_URGENCY_DURATIONS["low"]
+
+ class << self
+ def initialize_slis!(possible_labels)
+ Gitlab::Metrics::Sli::Apdex.initialize_sli(:sidekiq_execution, possible_labels)
+ Gitlab::Metrics::Sli::ErrorRate.initialize_sli(:sidekiq_execution, possible_labels)
+ end
+
+ def record_execution_apdex(labels, job_completion_duration)
+ urgency_requirement = execution_duration_for_urgency(labels[:urgency])
+ Gitlab::Metrics::Sli::Apdex[:sidekiq_execution].increment(
+ labels: labels,
+ success: job_completion_duration < urgency_requirement
+ )
+ end
+
+ def record_execution_error(labels, error)
+ Gitlab::Metrics::Sli::ErrorRate[:sidekiq_execution].increment(labels: labels, error: error)
+ end
+
+ def execution_duration_for_urgency(urgency)
+ EXECUTION_URGENCY_DURATIONS.fetch(urgency, DEFAULT_EXECUTION_URGENCY_DURATION)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/server_metrics.rb b/lib/gitlab/sidekiq_middleware/server_metrics.rb
index e36f61be3b3..b3c3c94a0a3 100644
--- a/lib/gitlab/sidekiq_middleware/server_metrics.rb
+++ b/lib/gitlab/sidekiq_middleware/server_metrics.rb
@@ -17,6 +17,9 @@ module Gitlab
SIDEKIQ_JOB_DURATION_BUCKETS = [10, 300].freeze
SIDEKIQ_QUEUE_DURATION_BUCKETS = [10, 60].freeze
+ # These labels from Gitlab::SidekiqMiddleware::MetricsHelper are included in SLI metrics
+ SIDEKIQ_SLI_LABELS = [:worker, :feature_category, :urgency].freeze
+
class << self
include ::Gitlab::SidekiqMiddleware::MetricsHelper
@@ -47,17 +50,21 @@ module Gitlab
return unless ::Feature.enabled?(:sidekiq_job_completion_metric_initialize)
+ possible_sli_labels = []
::Gitlab::SidekiqConfig.current_worker_queue_mappings.each do |worker, queue|
worker_class = worker.safe_constantize
next unless worker_class
base_labels = create_labels(worker_class, queue, {})
+ possible_sli_labels << base_labels.slice(*SIDEKIQ_SLI_LABELS)
%w[done fail].each do |status|
metrics[:sidekiq_jobs_completion_seconds].get(base_labels.merge(job_status: status))
end
end
+
+ Gitlab::Metrics::SidekiqSlis.initialize_slis!(possible_sli_labels) if ::Feature.enabled?(:sidekiq_execution_application_slis)
end
end
@@ -134,6 +141,12 @@ module Gitlab
@metrics[:sidekiq_load_balancing_count].increment(labels.merge(load_balancing_labels), 1)
end
+
+ if ::Feature.enabled?(:sidekiq_execution_application_slis)
+ sli_labels = labels.slice(*SIDEKIQ_SLI_LABELS)
+ Gitlab::Metrics::SidekiqSlis.record_execution_apdex(sli_labels, monotonic_time) if job_succeeded
+ Gitlab::Metrics::SidekiqSlis.record_execution_error(sli_labels, !job_succeeded)
+ end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 02418c45e73..79131404465 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -156,11 +156,14 @@ module Gitlab
]
end
- def send_url(url, allow_redirects: false)
+ def send_url(url, allow_redirects: false, method: 'GET', body: nil, headers: nil)
params = {
'URL' => url,
- 'AllowRedirects' => allow_redirects
- }
+ 'AllowRedirects' => allow_redirects,
+ 'Body' => body.to_s,
+ 'Header' => headers,
+ 'Method' => method
+ }.compact
[
SEND_DATA_HEADER,
diff --git a/lib/sidebars/menu_item.rb b/lib/sidebars/menu_item.rb
index bc10c4fb257..227a6970b2c 100644
--- a/lib/sidebars/menu_item.rb
+++ b/lib/sidebars/menu_item.rb
@@ -38,7 +38,8 @@ module Sidebars
icon: sprite_icon,
link: link,
active_routes: active_routes,
- pill_count: has_pill ? pill_count : nil
+ pill_count: has_pill ? pill_count : nil,
+ link_classes: container_html_options[:class]
# Check whether support is needed for the following properties,
# in order to get feature parity with the HAML renderer
# https://gitlab.com/gitlab-org/gitlab/-/issues/391864
diff --git a/lib/sidebars/projects/menus/issues_menu.rb b/lib/sidebars/projects/menus/issues_menu.rb
index 38eab0e3b68..dd5d4458fbb 100644
--- a/lib/sidebars/projects/menus/issues_menu.rb
+++ b/lib/sidebars/projects/menus/issues_menu.rb
@@ -113,6 +113,7 @@ module Sidebars
link: project_boards_path(context.project),
super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::PlanMenu,
active_routes: { controller: :boards },
+ container_html_options: { class: 'shortcuts-issue-boards' },
item_id: :boards
)
end
diff --git a/lib/sidebars/projects/menus/repository_menu.rb b/lib/sidebars/projects/menus/repository_menu.rb
index 157dd379ed7..22f7b553884 100644
--- a/lib/sidebars/projects/menus/repository_menu.rb
+++ b/lib/sidebars/projects/menus/repository_menu.rb
@@ -57,6 +57,7 @@ module Sidebars
link: project_tree_path(context.project, context.current_ref),
super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::CodeMenu,
active_routes: { controller: %w[tree blob blame edit_tree new_tree find_file] },
+ container_html_options: { class: 'shortcuts-tree' },
item_id: :files
)
end
@@ -70,7 +71,7 @@ module Sidebars
super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::CodeMenu,
active_routes: { controller: %w(commit commits) },
item_id: :commits,
- container_html_options: { id: 'js-onboarding-commits-link' }
+ container_html_options: { id: 'js-onboarding-commits-link', class: 'shortcuts-commits' }
)
end
@@ -117,6 +118,7 @@ module Sidebars
link: link,
super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::CodeMenu,
active_routes: { controller: :network },
+ container_html_options: { class: 'shortcuts-network' },
item_id: :graphs
)
end
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb
index aadca29de0c..695a73de078 100644
--- a/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb
+++ b/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb
@@ -8,12 +8,16 @@ module QA
Page::Project::Menu.perform(&:go_to_monitor_incidents)
Page::Project::Monitor::Incidents::Index.perform do |index|
# Open tab is displayed by default
- expect(index).to have_incident(title: unresolve_title)
- expect(index).to have_no_incident(title: resolve_title)
+ expect(index).to have_incident(title: unresolve_title),
+ 'Expected to see the unresolved incident in the Open tab'
+ expect(index).to have_no_incident(title: resolve_title),
+ 'Did not expect to see the resolved incident in the Open tab'
index.go_to_tab('Closed')
- expect(index).to have_incident(title: resolve_title)
- expect(index).to have_no_incident(title: unresolve_title)
+ expect(index).to have_incident(title: resolve_title),
+ 'Expected to see the resolved incident in the Closed tab'
+ expect(index).to have_no_incident(title: unresolve_title),
+ 'Did not expect to see the unresolved incident in the Closed tab'
end
end
end
diff --git a/spec/frontend/super_sidebar/components/nav_item_spec.js b/spec/frontend/super_sidebar/components/nav_item_spec.js
index d96d2b77d21..ffbdaa326f9 100644
--- a/spec/frontend/super_sidebar/components/nav_item_spec.js
+++ b/spec/frontend/super_sidebar/components/nav_item_spec.js
@@ -53,6 +53,13 @@ describe('NavItem component', () => {
expect(findLink().attributes('class')).toContain(customClass);
});
+ it('applies custom classes set in the backend', () => {
+ const customClass = 'customBackendClass';
+ createWrapper({ title: 'Foo', link_classes: customClass });
+
+ expect(findLink().attributes('class')).toContain(customClass);
+ });
+
describe('Data Tracking Attributes', () => {
it('adds no labels on sections', () => {
const id = 'my-id';
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index c4ab9fb115f..2d3bb06df41 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -212,6 +212,80 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
end
end
+ describe '#last_pipeline_from_status_cache' do
+ before do
+ # clear cross-example caches
+ project_with_repo.pipeline_status.delete_from_cache
+ project_with_repo.instance_variable_set(:@pipeline_status, nil)
+ end
+
+ context 'without a pipeline' do
+ it 'returns nil', :aggregate_failures do
+ expect(::Gitlab::GitalyClient).to receive(:call).at_least(:once).and_call_original
+ actual_pipeline = last_pipeline_from_status_cache(project_with_repo)
+ expect(actual_pipeline).to be_nil
+ end
+
+ context 'when pipeline_status is loaded' do
+ before do
+ project_with_repo.pipeline_status # this loads the status
+ end
+
+ it 'returns nil without calling gitaly when there is no pipeline', :aggregate_failures do
+ expect(::Gitlab::GitalyClient).not_to receive(:call)
+ actual_pipeline = last_pipeline_from_status_cache(project_with_repo)
+ expect(actual_pipeline).to be_nil
+ end
+ end
+
+ context 'when FF load_last_pipeline_from_pipeline_status is disabled' do
+ before do
+ stub_feature_flags(last_pipeline_from_pipeline_status: false)
+ end
+
+ it 'returns nil', :aggregate_failures do
+ expect(project_with_repo).not_to receive(:pipeline_status)
+ actual_pipeline = last_pipeline_from_status_cache(project_with_repo)
+ expect(actual_pipeline).to be_nil
+ end
+ end
+ end
+
+ context 'with a pipeline' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project_with_repo) }
+
+ it 'returns the latest pipeline', :aggregate_failures do
+ expect(::Gitlab::GitalyClient).to receive(:call).at_least(:once).and_call_original
+ actual_pipeline = last_pipeline_from_status_cache(project_with_repo)
+ expect(actual_pipeline).to eq pipeline
+ end
+
+ context 'when pipeline_status is loaded' do
+ before do
+ project_with_repo.pipeline_status # this loads the status
+ end
+
+ it 'returns the latest pipeline without calling gitaly' do
+ expect(::Gitlab::GitalyClient).not_to receive(:call)
+ actual_pipeline = last_pipeline_from_status_cache(project_with_repo)
+ expect(actual_pipeline).to eq pipeline
+ end
+
+ context 'when FF load_last_pipeline_from_pipeline_status is disabled' do
+ before do
+ stub_feature_flags(last_pipeline_from_pipeline_status: false)
+ end
+
+ it 'returns the latest pipeline', :aggregate_failures do
+ expect(project_with_repo).not_to receive(:pipeline_status)
+ actual_pipeline = last_pipeline_from_status_cache(project_with_repo)
+ expect(actual_pipeline).to eq pipeline
+ end
+ end
+ end
+ end
+ end
+
describe '#show_no_ssh_key_message?' do
before do
allow(helper).to receive(:current_user).and_return(user)
diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb
index 5323b041a9e..6c0ac024944 100644
--- a/spec/helpers/sidebars_helper_spec.rb
+++ b/spec/helpers/sidebars_helper_spec.rb
@@ -66,9 +66,10 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
let_it_be(:group) { build(:group) }
let_it_be(:panel) { {} }
let_it_be(:panel_type) { 'project' }
+ let(:project) { nil }
subject do
- helper.super_sidebar_context(user, group: group, project: nil, panel: panel, panel_type: panel_type)
+ helper.super_sidebar_context(user, group: group, project: project, panel: panel, panel_type: panel_type)
end
before do
@@ -160,6 +161,47 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
})
end
+ describe "shortcut links" do
+ let(:global_shortcut_links) do
+ [
+ {
+ title: _('Milestones'),
+ href: dashboard_milestones_path,
+ css_class: 'dashboard-shortcuts-milestones'
+ },
+ {
+ title: _('Snippets'),
+ href: dashboard_snippets_path,
+ css_class: 'dashboard-shortcuts-snippets'
+ },
+ {
+ title: _('Activity'),
+ href: activity_dashboard_path,
+ css_class: 'dashboard-shortcuts-activity'
+ }
+ ]
+ end
+
+ it 'returns global shortcut links' do
+ expect(subject[:shortcut_links]).to eq(global_shortcut_links)
+ end
+
+ context 'in a project' do
+ let(:project) { build(:project) }
+
+ it 'returns project-specific shortcut links' do
+ expect(subject[:shortcut_links]).to eq([
+ *global_shortcut_links,
+ {
+ title: _('Create a new issue'),
+ href: new_project_issue_path(project),
+ css_class: 'shortcuts-new-issue'
+ }
+ ])
+ end
+ end
+ end
+
it 'returns "Merge requests" menu', :use_clean_rails_memory_store_caching do
expect(subject[:merge_request_menu]).to eq([
{
diff --git a/spec/lib/gitlab/metrics/sidekiq_slis_spec.rb b/spec/lib/gitlab/metrics/sidekiq_slis_spec.rb
new file mode 100644
index 00000000000..eef9a9c79e6
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sidekiq_slis_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::SidekiqSlis, feature_category: :error_budgets do
+ using RSpec::Parameterized::TableSyntax
+
+ describe ".initialize_slis!" do
+ let(:possible_labels) do
+ [
+ {
+ worker: "Projects::RecordTargetPlatformsWorker",
+ feature_category: "projects",
+ urgency: "low"
+ }
+ ]
+ end
+
+ it "initializes the apdex and error rate SLIs" do
+ expect(Gitlab::Metrics::Sli::Apdex).to receive(:initialize_sli).with(:sidekiq_execution, possible_labels)
+ expect(Gitlab::Metrics::Sli::ErrorRate).to receive(:initialize_sli).with(:sidekiq_execution, possible_labels)
+
+ described_class.initialize_slis!(possible_labels)
+ end
+ end
+
+ describe ".record_execution_apdex" do
+ where(:urgency, :duration, :success) do
+ "high" | 5 | true
+ "high" | 11 | false
+ "low" | 295 | true
+ "low" | 400 | false
+ "throttled" | 295 | true
+ "throttled" | 400 | false
+ "not_found" | 295 | true
+ "not_found" | 400 | false
+ end
+
+ with_them do
+ it "increments the apdex SLI with success based on urgency requirement" do
+ labels = { urgency: urgency }
+ expect(Gitlab::Metrics::Sli::Apdex[:sidekiq_execution]).to receive(:increment).with(
+ labels: labels,
+ success: success
+ )
+
+ described_class.record_execution_apdex(labels, duration)
+ end
+ end
+ end
+
+ describe ".record_execution_error" do
+ it "increments the error rate SLI with the given labels and error" do
+ labels = { urgency: :throttled }
+ error = StandardError.new("something went wrong")
+
+ expect(Gitlab::Metrics::Sli::ErrorRate[:sidekiq_execution]).to receive(:increment).with(
+ labels: labels,
+ error: error
+ )
+
+ described_class.record_execution_error(labels, error)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 80c1af1b913..965ca612b3f 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -59,6 +59,45 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
described_class.initialize_process_metrics
end
+ context 'when sidekiq_execution_application_slis FF is turned on' do
+ it 'initializes sidekiq SLIs for the workers in the current Sidekiq process' do
+ allow(Gitlab::SidekiqConfig)
+ .to receive(:current_worker_queue_mappings)
+ .and_return('MergeWorker' => 'merge', 'Ci::BuildFinishedWorker' => 'default')
+
+ allow(completion_seconds_metric).to receive(:get)
+
+ expect(Gitlab::Metrics::SidekiqSlis)
+ .to receive(:initialize_slis!).with([
+ {
+ worker: 'MergeWorker',
+ urgency: 'high',
+ feature_category: 'source_code_management'
+ },
+ {
+ worker: 'Ci::BuildFinishedWorker',
+ urgency: 'high',
+ feature_category: 'continuous_integration'
+ }
+ ])
+
+ described_class.initialize_process_metrics
+ end
+ end
+
+ context 'when sidekiq_execution_application_slis FF is turned off' do
+ before do
+ stub_feature_flags(sidekiq_execution_application_slis: false)
+ end
+
+ it 'does not initialize sidekiq SLIs' do
+ expect(Gitlab::Metrics::SidekiqSlis)
+ .not_to receive(:initialize_slis!)
+
+ described_class.initialize_process_metrics
+ end
+ end
+
context 'when the sidekiq_job_completion_metric_initialize feature flag is disabled' do
before do
stub_feature_flags(sidekiq_job_completion_metric_initialize: false)
@@ -79,6 +118,17 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
described_class.initialize_process_metrics
end
+
+ it 'does not initializes sidekiq SLIs' do
+ allow(Gitlab::SidekiqConfig)
+ .to receive(:current_worker_queue_mappings)
+ .and_return('MergeWorker' => 'merge', 'Ci::BuildFinishedWorker' => 'default')
+
+ expect(Gitlab::Metrics::SidekiqSlis)
+ .not_to receive(:initialize_slis!)
+
+ described_class.initialize_process_metrics
+ end
end
end
@@ -110,6 +160,12 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
expect(redis_requests_total).to receive(:increment).with(labels_with_job_status, redis_calls)
expect(elasticsearch_requests_total).to receive(:increment).with(labels_with_job_status, elasticsearch_calls)
expect(sidekiq_mem_total_bytes).to receive(:set).with(labels_with_job_status, mem_total_bytes)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_apdex).with(labels.slice(:worker,
+ :feature_category,
+ :urgency), monotonic_time_duration)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_error).with(labels.slice(:worker,
+ :feature_category,
+ :urgency), false)
subject.call(worker, job, :test) { nil }
end
@@ -159,6 +215,16 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
end
+
+ it 'records sidekiq SLI error but does not record sidekiq SLI apdex' do
+ expect(failed_total_metric).to receive(:increment)
+ expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_execution_apdex)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_error).with(labels.slice(:worker,
+ :feature_category,
+ :urgency), true)
+
+ expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
+ end
end
context 'when job is retried' do
@@ -180,6 +246,19 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
subject.call(worker, job, :test) { nil }
end
end
+
+ context 'when sidekiq_execution_application_slis FF is turned off' do
+ before do
+ stub_feature_flags(sidekiq_execution_application_slis: false)
+ end
+
+ it 'does not call record_execution_apdex nor record_execution_error' do
+ expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_execution_apdex)
+ expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_execution_error)
+
+ subject.call(worker, job, :test) { nil }
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 3c7542ea5f9..a1c2f7d667f 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -458,18 +458,42 @@ RSpec.describe Gitlab::Workhorse do
describe '.send_url' do
let(:url) { 'http://example.com' }
- subject { described_class.send_url(url) }
-
it 'sets the header correctly' do
- key, command, params = decode_workhorse_header(subject)
+ key, command, params = decode_workhorse_header(
+ described_class.send_url(url)
+ )
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("send-url")
expect(params).to eq({
'URL' => url,
- 'AllowRedirects' => false
+ 'AllowRedirects' => false,
+ 'Body' => '',
+ 'Method' => 'GET'
}.deep_stringify_keys)
end
+
+ context 'when body, headers and method are specified' do
+ let(:body) { 'body' }
+ let(:headers) { { Authorization: ['Bearer token'] } }
+ let(:method) { 'POST' }
+
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(
+ described_class.send_url(url, body: body, headers: headers, method: method)
+ )
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("send-url")
+ expect(params).to eq({
+ 'URL' => url,
+ 'AllowRedirects' => false,
+ 'Body' => body,
+ 'Header' => headers,
+ 'Method' => method
+ }.deep_stringify_keys)
+ end
+ end
end
describe '.send_scaled_image' do
diff --git a/spec/lib/sidebars/menu_item_spec.rb b/spec/lib/sidebars/menu_item_spec.rb
index 15804f51934..84bc3430260 100644
--- a/spec/lib/sidebars/menu_item_spec.rb
+++ b/spec/lib/sidebars/menu_item_spec.rb
@@ -18,4 +18,14 @@ RSpec.describe Sidebars::MenuItem do
expect(menu_item.container_html_options).to eq html_options
end
end
+
+ describe "#serialize_for_super_sidebar" do
+ let(:html_options) { { class: 'custom-class' } }
+
+ subject { menu_item.serialize_for_super_sidebar }
+
+ it 'includes custom CSS classes' do
+ expect(subject[:link_classes]).to be('custom-class')
+ end
+ end
end
diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb
index 74ed344dd24..21aec10ec27 100644
--- a/spec/lib/sidebars/menu_spec.rb
+++ b/spec/lib/sidebars/menu_spec.rb
@@ -61,7 +61,8 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
icon: nil,
link: "foo2",
is_active: true,
- pill_count: nil
+ pill_count: nil,
+ link_classes: nil
},
{
id: 'id2',
@@ -69,7 +70,8 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
icon: nil,
link: "foo3",
is_active: false,
- pill_count: 10
+ pill_count: 10,
+ link_classes: nil
}
]
})
diff --git a/spec/lib/sidebars/static_menu_spec.rb b/spec/lib/sidebars/static_menu_spec.rb
index b336b457302..3d9feee0494 100644
--- a/spec/lib/sidebars/static_menu_spec.rb
+++ b/spec/lib/sidebars/static_menu_spec.rb
@@ -25,7 +25,8 @@ RSpec.describe Sidebars::StaticMenu, feature_category: :navigation do
icon: nil,
link: "foo2",
is_active: true,
- pill_count: nil
+ pill_count: nil,
+ link_classes: nil
},
{
id: 'id2',
@@ -33,7 +34,8 @@ RSpec.describe Sidebars::StaticMenu, feature_category: :navigation do
icon: nil,
link: "foo3",
is_active: false,
- pill_count: nil
+ pill_count: nil,
+ link_classes: nil
}
]
)
diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb
index 7682cf39450..9aece9538dc 100644
--- a/spec/mailers/emails/merge_requests_spec.rb
+++ b/spec/mailers/emails/merge_requests_spec.rb
@@ -13,12 +13,15 @@ RSpec.describe Emails::MergeRequests do
let_it_be(:reviewer, reload: true) { create(:user, email: 'reviewer@example.com', name: 'Jane Doe') }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:merge_request) do
- create(:merge_request, source_project: project,
- target_project: project,
- author: current_user,
- assignees: [assignee],
- reviewers: [reviewer],
- description: 'Awesome description')
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ author: current_user,
+ assignees: [assignee],
+ reviewers: [reviewer],
+ description: 'Awesome description'
+ )
end
let(:recipient) { assignee }
diff --git a/spec/mailers/emails/pipelines_spec.rb b/spec/mailers/emails/pipelines_spec.rb
index 1ac989cc46b..ae0876338f5 100644
--- a/spec/mailers/emails/pipelines_spec.rb
+++ b/spec/mailers/emails/pipelines_spec.rb
@@ -25,8 +25,13 @@ RSpec.describe Emails::Pipelines do
let(:pipeline) { create(:ci_pipeline, ref: 'master', sha: sha, project: project) }
let!(:merge_request) do
- create(:merge_request, source_branch: 'master', target_branch: 'feature',
- source_project: project, target_project: project)
+ create(
+ :merge_request,
+ source_branch: 'master',
+ target_branch: 'feature',
+ source_project: project,
+ target_project: project
+ )
end
it 'has correct information that there is no merge request link' do
@@ -55,8 +60,13 @@ RSpec.describe Emails::Pipelines do
context 'when branch pipeline is set to a merge request as a head pipeline' do
let(:pipeline) do
- create(:ci_pipeline, project: project, ref: ref, sha: sha,
- merge_requests_as_head_pipeline: [merge_request])
+ create(
+ :ci_pipeline,
+ project: project,
+ ref: ref,
+ sha: sha,
+ merge_requests_as_head_pipeline: [merge_request]
+ )
end
let(:merge_request) do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index eb681846e82..c2c32abbdc4 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -21,19 +21,25 @@ RSpec.describe Notify do
let_it_be(:reviewer, reload: true) { create(:user, email: 'reviewer@example.com', name: 'Jane Doe') }
let_it_be(:merge_request) do
- create(:merge_request, source_project: project,
- target_project: project,
- author: current_user,
- assignees: [assignee],
- reviewers: [reviewer],
- description: 'Awesome description')
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ author: current_user,
+ assignees: [assignee],
+ reviewers: [reviewer],
+ description: 'Awesome description'
+ )
end
let_it_be(:issue, reload: true) do
- create(:issue, author: current_user,
- assignees: [assignee],
- project: project,
- description: 'My awesome description!')
+ create(
+ :issue,
+ author: current_user,
+ assignees: [assignee],
+ project: project,
+ description: 'My awesome description!'
+ )
end
describe 'with HTML-encoded entities' do
@@ -824,9 +830,11 @@ RSpec.describe Notify do
end
it 'has References header including the notes and issue of the discussion' do
- expect(subject.header['References'].message_ids).to include("issue_#{first_note.noteable.id}@#{host}",
- "note_#{first_note.id}@#{host}",
- "note_#{second_note.id}@#{host}")
+ expect(subject.header['References'].message_ids).to include(
+ "issue_#{first_note.noteable.id}@#{host}",
+ "note_#{first_note.id}@#{host}",
+ "note_#{second_note.id}@#{host}"
+ )
end
it 'has X-GitLab-Discussion-ID header' do
@@ -899,9 +907,11 @@ RSpec.describe Notify do
end
it 'links to the project snippet' do
- target_url = project_snippet_url(project,
- project_snippet_note.noteable,
- { anchor: "note_#{project_snippet_note.id}" })
+ target_url = project_snippet_url(
+ project,
+ project_snippet_note.noteable,
+ { anchor: "note_#{project_snippet_note.id}" }
+ )
is_expected.to have_body_text target_url
end
end
@@ -910,9 +920,7 @@ RSpec.describe Notify do
let_it_be(:design) { create(:design, :with_file) }
let_it_be(:recipient) { create(:user) }
let_it_be(:note) do
- create(:diff_note_on_design,
- noteable: design,
- note: "Hello #{recipient.to_reference}")
+ create(:diff_note_on_design, noteable: design, note: "Hello #{recipient.to_reference}")
end
let(:header_name) { 'X-Gitlab-DesignManagement-Design-ID' }
@@ -1078,9 +1086,10 @@ RSpec.describe Notify do
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
- is_expected.to have_link('Join now',
- href: invite_url(project_member.invite_token,
- invite_type: Emails::Members::INITIAL_INVITE))
+ is_expected.to have_link(
+ 'Join now',
+ href: invite_url(project_member.invite_token, invite_type: Emails::Members::INITIAL_INVITE)
+ )
is_expected.to have_content("#{inviter.name} invited you to join the")
is_expected.to have_content('Project details')
is_expected.to have_content("What's it about?")
@@ -1096,9 +1105,10 @@ RSpec.describe Notify do
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
- is_expected.to have_link('Join now',
- href: invite_url(project_member.invite_token,
- invite_type: Emails::Members::INITIAL_INVITE))
+ is_expected.to have_link(
+ 'Join now',
+ href: invite_url(project_member.invite_token, invite_type: Emails::Members::INITIAL_INVITE)
+ )
is_expected.to have_content('Project details')
is_expected.to have_content("What's it about?")
end
diff --git a/spec/models/ci/commit_with_pipeline_spec.rb b/spec/models/ci/commit_with_pipeline_spec.rb
index 320143535e2..766e99288c0 100644
--- a/spec/models/ci/commit_with_pipeline_spec.rb
+++ b/spec/models/ci/commit_with_pipeline_spec.rb
@@ -2,9 +2,9 @@
require 'spec_helper'
-RSpec.describe Ci::CommitWithPipeline do
- let(:project) { create(:project, :public, :repository) }
- let(:commit) { described_class.new(project.commit) }
+RSpec.describe Ci::CommitWithPipeline, feature_category: :continuous_integration do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let(:commit) { described_class.new(project.commit) }
describe '#last_pipeline' do
let!(:first_pipeline) do
@@ -27,28 +27,43 @@ RSpec.describe Ci::CommitWithPipeline do
end
describe '#lazy_latest_pipeline' do
- let(:commit_1) do
- described_class.new(Commit.new(RepoHelpers.sample_commit, project))
+ let_it_be(:other_project) { create(:project, :repository) }
+
+ let_it_be(:commits_with_pipelines) do
+ [
+ described_class.new(Commit.new(RepoHelpers.sample_commit, project)),
+ described_class.new(Commit.new(RepoHelpers.another_sample_commit, project)),
+ described_class.new(Commit.new(RepoHelpers.sample_big_commit, project)),
+ described_class.new(Commit.new(RepoHelpers.sample_commit, other_project))
+ ]
end
- let(:commit_2) do
- described_class.new(Commit.new(RepoHelpers.another_sample_commit, project))
+ let_it_be(:commits) do
+ commits_with_pipelines + [
+ described_class.new(Commit.new(RepoHelpers.another_sample_commit, other_project))
+ ]
end
- let!(:commits) { [commit_1, commit_2] }
+ before(:all) do
+ commits_with_pipelines.each do |commit|
+ create(:ci_empty_pipeline, project: commit.project, sha: commit.sha)
+ end
+ end
- it 'executes only 1 SQL query' do
+ it 'returns the correct pipelines with only 1 SQL query per project', :aggregate_failures do
recorder = ActiveRecord::QueryRecorder.new do
- # Running this first ensures we don't run one query for every
- # commit.
+ # batch commits
commits.each(&:lazy_latest_pipeline)
- # This forces the execution of the SQL queries necessary to load the
- # data.
- commits.each { |c| c.latest_pipeline.try(:id) }
+ # assert result correctness
+ commits_with_pipelines.each do |commit|
+ expect(commit.lazy_latest_pipeline.project).to eq(commit.project)
+ end
+
+ expect(commits.last.lazy_latest_pipeline&.itself).to be_nil
end
- expect(recorder.count).to eq(1)
+ expect(recorder.count).to eq(2)
end
end
diff --git a/spec/models/concerns/require_email_verification_spec.rb b/spec/models/concerns/require_email_verification_spec.rb
index 0a6293f852e..1fb54e4276f 100644
--- a/spec/models/concerns/require_email_verification_spec.rb
+++ b/spec/models/concerns/require_email_verification_spec.rb
@@ -15,24 +15,20 @@ RSpec.describe RequireEmailVerification, feature_category: :insider_threat do
using RSpec::Parameterized::TableSyntax
- where(:feature_flag_enabled, :two_factor_enabled, :skipped, :overridden) do
- false | false | false | false
- false | false | true | false
- false | true | false | false
- false | true | true | false
- true | false | false | true
- true | false | true | false
- true | true | false | false
- true | true | true | false
- end
+ where(feature_flag_enabled: [true, false],
+ two_factor_enabled: [true, false],
+ oauth_user: [true, false],
+ skipped: [true, false])
with_them do
let(:instance) { model.new(id: 1) }
let(:another_instance) { model.new(id: 2) }
+ let(:overridden) { feature_flag_enabled && !two_factor_enabled && !oauth_user && !skipped }
before do
stub_feature_flags(require_email_verification: feature_flag_enabled ? instance : another_instance)
allow(instance).to receive(:two_factor_enabled?).and_return(two_factor_enabled)
+ allow(instance).to receive(:identities).and_return(oauth_user ? [:google] : [])
stub_feature_flags(skip_require_email_verification: skipped ? instance : another_instance)
end
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 4c9f930df2f..bce004dcd48 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -114,6 +114,17 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
end
end
+ context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do
+ let(:state_name) { 'state-name-with-dot' }
+ let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}.tfstate" }
+
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it_behaves_like 'can access terraform state'
+ end
+
context 'for a project that does not exist' do
let(:project_id) { '0000' }
@@ -266,6 +277,21 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
expect(Gitlab::Json.parse(response.body)).to be_empty
end
end
+
+ context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do
+ let(:non_existing_state_name) { 'state-name-with-dot.tfstate' }
+
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it 'strips characters after the dot' do
+ expect { request }.to change { Terraform::State.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Terraform::State.last.name).to eq('state-name-with-dot')
+ end
+ end
end
context 'without body' do
@@ -373,6 +399,18 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
it_behaves_like 'schedules the state for deletion'
end
+ context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do
+ let(:state_name) { 'state-name-with-dot' }
+ let(:state_name_with_dot) { "#{state_name}.tfstate" }
+ let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name_with_dot}" }
+
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it_behaves_like 'schedules the state for deletion'
+ end
+
context 'with invalid state name' do
let(:state_name) { 'foo/bar' }
@@ -462,10 +500,30 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
context 'with a dot in the state name' do
let(:state_name) { 'test.state' }
- it 'locks the terraform state' do
- request
+ context 'with allow_dots_on_tf_state_names ff enabled' do
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: true)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ let(:state_name) { 'test.state' }
+
+ it 'locks the terraform state' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with allow_dots_on_tf_state_names ff disabled' do
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it 'returns 404' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
end
@@ -486,6 +544,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
before do
state.lock_xid = '123.456'
state.save!
+ stub_feature_flags(allow_dots_on_tf_state_names: true)
end
subject(:request) { delete api("#{state_path}/lock"), headers: auth_header, params: params }
@@ -516,6 +575,23 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu
end
end
+ context 'with allow_dots_on_tf_state_names ff disabled' do
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ context 'with dots in the state name' do
+ let(:lock_id) { '123.456' }
+ let(:state_name) { 'test.state' }
+
+ it 'returns 404' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
context 'with no lock id (force-unlock)' do
let(:params) { {} }
diff --git a/spec/services/projects/android_target_platform_detector_service_spec.rb b/spec/services/projects/android_target_platform_detector_service_spec.rb
deleted file mode 100644
index d5feef4ec3d..00000000000
--- a/spec/services/projects/android_target_platform_detector_service_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::AndroidTargetPlatformDetectorService, feature_category: :projects do
- let_it_be(:project) { build(:project) }
-
- subject { described_class.new(project).execute }
-
- before do
- allow(Gitlab::FileFinder).to receive(:new) { finder }
- end
-
- context 'when project is not an Android project' do
- let(:finder) { instance_double(Gitlab::FileFinder, find: []) }
-
- it { is_expected.to be_nil }
- end
-
- context 'when project is an Android project' do
- let(:finder) { instance_double(Gitlab::FileFinder) }
-
- before do
- query = described_class::MANIFEST_FILE_SEARCH_QUERY
- allow(finder).to receive(:find).with(query) { [instance_double(Gitlab::Search::FoundBlob)] }
- end
-
- it { is_expected.to eq :android }
- end
-end
diff --git a/spec/services/projects/record_target_platforms_service_spec.rb b/spec/services/projects/record_target_platforms_service_spec.rb
index dd4e9fe19e0..17aa7fd7009 100644
--- a/spec/services/projects/record_target_platforms_service_spec.rb
+++ b/spec/services/projects/record_target_platforms_service_spec.rb
@@ -9,34 +9,23 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute', feature_categ
subject(:execute) { described_class.new(project, detector_service).execute }
- context 'when detector returns target platform values' do
- let(:detector_result) { [:ios, :osx] }
- let(:service_result) { detector_result.map(&:to_s) }
+ context 'when project is an XCode project' do
+ def project_setting
+ ProjectSetting.find_by_project_id(project.id)
+ end
before do
- double = instance_double(detector_service, execute: detector_result)
- allow(detector_service).to receive(:new) { double }
+ double = instance_double(detector_service, execute: [:ios, :osx])
+ allow(Projects::AppleTargetPlatformDetectorService).to receive(:new) { double }
end
- shared_examples 'saves and returns detected target platforms' do
- it 'creates a new setting record for the project', :aggregate_failures do
- expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
- expect(ProjectSetting.last.target_platforms).to match_array(service_result)
- end
-
- it 'returns the array of stored target platforms' do
- expect(execute).to match_array service_result
- end
+ it 'creates a new setting record for the project', :aggregate_failures do
+ expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
+ expect(ProjectSetting.last.target_platforms).to match_array(%w(ios osx))
end
- it_behaves_like 'saves and returns detected target platforms'
-
- context 'when detector returns a non-array value' do
- let(:detector_service) { Projects::AndroidTargetPlatformDetectorService }
- let(:detector_result) { :android }
- let(:service_result) { [detector_result.to_s] }
-
- it_behaves_like 'saves and returns detected target platforms'
+ it 'returns array of detected target platforms' do
+ expect(execute).to match_array %w(ios osx)
end
context 'when a project has an existing setting record' do
@@ -44,10 +33,6 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute', feature_categ
create(:project_setting, project: project, target_platforms: saved_target_platforms)
end
- def project_setting
- ProjectSetting.find_by_project_id(project.id)
- end
-
context 'when target platforms changed' do
let(:saved_target_platforms) { %w(tvos) }
@@ -98,44 +83,23 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute', feature_categ
it_behaves_like 'tracks experiment assignment event'
end
- shared_examples 'does not send email' do
- it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do
- expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new)
-
- execute
- end
- end
-
context 'experiment control' do
before do
stub_experiments(build_ios_app_guide_email: :control)
end
- it_behaves_like 'does not send email'
- it_behaves_like 'tracks experiment assignment event'
- end
-
- context 'when project is not an iOS project' do
- let(:detector_service) { Projects::AppleTargetPlatformDetectorService }
- let(:detector_result) { :android }
-
- before do
- stub_experiments(build_ios_app_guide_email: :candidate)
- end
-
- it_behaves_like 'does not send email'
-
- it 'does not track experiment assignment event', :experiment do
- expect(experiment(:build_ios_app_guide_email))
- .not_to track(:assignment)
+ it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do
+ expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new)
execute
end
+
+ it_behaves_like 'tracks experiment assignment event'
end
end
end
- context 'when detector does not return any target platform values' do
+ context 'when project is not an XCode project' do
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new).with(project) { double }
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index e488ca19903..a6b069bc93b 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -9495,7 +9495,6 @@
- './spec/services/projects/alerting/notify_service_spec.rb'
- './spec/services/projects/all_issues_count_service_spec.rb'
- './spec/services/projects/all_merge_requests_count_service_spec.rb'
-- './spec/services/projects/android_target_platform_detector_service_spec.rb'
- './spec/services/projects/apple_target_platform_detector_service_spec.rb'
- './spec/services/projects/autocomplete_service_spec.rb'
- './spec/services/projects/auto_devops/disable_service_spec.rb'
diff --git a/spec/workers/projects/record_target_platforms_worker_spec.rb b/spec/workers/projects/record_target_platforms_worker_spec.rb
index c1e99d52473..0e106fe32f9 100644
--- a/spec/workers/projects/record_target_platforms_worker_spec.rb
+++ b/spec/workers/projects/record_target_platforms_worker_spec.rb
@@ -7,11 +7,11 @@ RSpec.describe Projects::RecordTargetPlatformsWorker, feature_category: :project
let_it_be(:swift) { create(:programming_language, name: 'Swift') }
let_it_be(:objective_c) { create(:programming_language, name: 'Objective-C') }
- let_it_be(:java) { create(:programming_language, name: 'Java') }
- let_it_be(:kotlin) { create(:programming_language, name: 'Kotlin') }
let_it_be(:project) { create(:project, :repository, detected_repository_languages: true) }
let(:worker) { described_class.new }
+ let(:service_result) { %w(ios osx watchos) }
+ let(:service_double) { instance_double(Projects::RecordTargetPlatformsService, execute: service_result) }
let(:lease_key) { "#{described_class.name.underscore}:#{project.id}" }
let(:lease_timeout) { described_class::LEASE_TIMEOUT }
@@ -49,68 +49,19 @@ RSpec.describe Projects::RecordTargetPlatformsWorker, feature_category: :project
end
end
- def create_language(language)
- create(:repository_language, project: project, programming_language: language)
- end
-
- context 'when project uses programming language for Apple platform' do
- let(:service_result) { %w(ios osx watchos) }
-
- context 'when project uses Swift programming language' do
- before do
- create_language(swift)
- end
-
- it_behaves_like 'performs detection', Projects::AppleTargetPlatformDetectorService
- end
+ context 'when project uses Swift programming language' do
+ let!(:repository_language) { create(:repository_language, project: project, programming_language: swift) }
- context 'when project uses Objective-C programming language' do
- before do
- create_language(objective_c)
- end
-
- it_behaves_like 'performs detection', Projects::AppleTargetPlatformDetectorService
- end
+ include_examples 'performs detection', Projects::AppleTargetPlatformDetectorService
end
- context 'when project uses programming language for Android platform' do
- let(:feature_enabled) { true }
- let(:service_result) { %w(android) }
-
- before do
- stub_feature_flags(detect_android_projects: feature_enabled)
- end
+ context 'when project uses Objective-C programming language' do
+ let!(:repository_language) { create(:repository_language, project: project, programming_language: objective_c) }
- context 'when project uses Java' do
- before do
- create_language(java)
- end
-
- it_behaves_like 'performs detection', Projects::AndroidTargetPlatformDetectorService
-
- context 'when feature flag is disabled' do
- let(:feature_enabled) { false }
-
- it_behaves_like 'does nothing'
- end
- end
-
- context 'when project uses Kotlin' do
- before do
- create_language(kotlin)
- end
-
- it_behaves_like 'performs detection', Projects::AndroidTargetPlatformDetectorService
-
- context 'when feature flag is disabled' do
- let(:feature_enabled) { false }
-
- it_behaves_like 'does nothing'
- end
- end
+ include_examples 'performs detection', Projects::AppleTargetPlatformDetectorService
end
- context 'when the project does not use programming languages for Apple or Android platforms' do
+ context 'when the project does not contain programming languages for Apple platforms' do
it_behaves_like 'does nothing'
end
diff --git a/workhorse/internal/sendurl/sendurl.go b/workhorse/internal/sendurl/sendurl.go
index e689fc84a0f..9cc1cd352b7 100644
--- a/workhorse/internal/sendurl/sendurl.go
+++ b/workhorse/internal/sendurl/sendurl.go
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
@@ -21,6 +22,9 @@ type entry struct{ senddata.Prefix }
type entryParams struct {
URL string
AllowRedirects bool
+ Body string
+ Header http.Header
+ Method string
}
var SendURL = &entry{"send-url:"}
@@ -86,6 +90,9 @@ func (e *entry) Inject(w http.ResponseWriter, r *http.Request, sendData string)
fail.Request(w, r, fmt.Errorf("SendURL: unpack sendData: %v", err))
return
}
+ if params.Method == "" {
+ params.Method = http.MethodGet
+ }
log.WithContextFields(r.Context(), log.Fields{
"url": mask.URL(params.URL),
@@ -99,7 +106,7 @@ func (e *entry) Inject(w http.ResponseWriter, r *http.Request, sendData string)
}
// create new request and copy range headers
- newReq, err := http.NewRequest("GET", params.URL, nil)
+ newReq, err := http.NewRequest(params.Method, params.URL, strings.NewReader(params.Body))
if err != nil {
sendURLRequestsInvalidData.Inc()
fail.Request(w, r, fmt.Errorf("SendURL: NewRequest: %v", err))
@@ -111,6 +118,12 @@ func (e *entry) Inject(w http.ResponseWriter, r *http.Request, sendData string)
newReq.Header[header] = r.Header[header]
}
+ for key, values := range params.Header {
+ for _, value := range values {
+ newReq.Header.Add(key, value)
+ }
+ }
+
// execute new request
var resp *http.Response
if params.AllowRedirects {
diff --git a/workhorse/internal/sendurl/sendurl_test.go b/workhorse/internal/sendurl/sendurl_test.go
index bca6b7d3075..ea0c6a43af6 100644
--- a/workhorse/internal/sendurl/sendurl_test.go
+++ b/workhorse/internal/sendurl/sendurl_test.go
@@ -2,7 +2,9 @@ package sendurl
import (
"encoding/base64"
+ "encoding/json"
"fmt"
+ "io"
"net/http"
"net/http/httptest"
"os"
@@ -194,3 +196,49 @@ func TestDownloadingNonExistingRemoteFileWithSendURL(t *testing.T) {
response := testEntryServer(t, "/get/file-not-existing", nil, false)
require.Equal(t, http.StatusNotFound, response.Code)
}
+
+func TestPostRequest(t *testing.T) {
+ body := "any string"
+ header := map[string][]string{"Authorization": []string{"Bearer token"}}
+ postRequestHandler := func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "POST", r.Method)
+
+ url := r.URL.String() + "/external/url"
+
+ jsonParams, err := json.Marshal(entryParams{URL: url, Body: body, Header: header, Method: "POST"})
+ require.NoError(t, err)
+
+ data := base64.URLEncoding.EncodeToString([]byte(jsonParams))
+
+ SendURL.Inject(w, r, data)
+ }
+ externalPostUrlHandler := func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "POST", r.Method)
+
+ b, err := io.ReadAll(r.Body)
+ require.NoError(t, err)
+ require.Equal(t, body, string(b))
+
+ require.Equal(t, []string{"Bearer token"}, r.Header["Authorization"])
+
+ w.Write([]byte(testData))
+ }
+
+ mux := http.NewServeMux()
+ mux.HandleFunc("/post/request/external/url", externalPostUrlHandler)
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ httpRequest, err := http.NewRequest("POST", server.URL+"/post/request", nil)
+ require.NoError(t, err)
+
+ response := httptest.NewRecorder()
+ postRequestHandler(response, httpRequest)
+
+ require.Equal(t, http.StatusOK, response.Code)
+
+ result, err := io.ReadAll(response.Body)
+ require.NoError(t, err)
+ require.Equal(t, testData, string(result))
+}