summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-05 09:08:00 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-05 09:08:00 +0000
commit17ef30f3df6d3939e41e69efc7cfa3deaa08605d (patch)
tree6852730e03de7e85e7a42952ec85960ab9832fa5
parentcd9bbd8a3e8af73864ca3c7704211309fae8ce0e (diff)
downloadgitlab-ce-17ef30f3df6d3939e41e69efc7cfa3deaa08605d.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo.yml4
-rw-r--r--.rubocop_todo/database/multiple_databases.yml25
-rw-r--r--.rubocop_todo/migration/background_migration_record.yml3
-rw-r--r--.rubocop_todo/style/missing_respond_to_missing.yml27
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue9
-rw-r--r--app/controllers/clusters/clusters_controller.rb8
-rw-r--r--app/graphql/mutations/environments/canary_ingress/update.rb7
-rw-r--r--app/graphql/resolvers/design_management/designs_resolver.rb4
-rw-r--r--app/graphql/resolvers/design_management/versions_resolver.rb26
-rw-r--r--app/graphql/types/design_management/design_type.rb5
-rw-r--r--app/helpers/application_settings_helper.rb2
-rw-r--r--app/helpers/clusters_helper.rb2
-rw-r--r--app/models/ci/job_artifact.rb4
-rw-r--r--app/models/clusters/instance.rb6
-rw-r--r--app/models/concerns/deployment_platform.rb2
-rw-r--r--app/models/deployment.rb9
-rw-r--r--app/models/integration.rb2
-rw-r--r--app/models/integrations/bamboo.rb54
-rw-r--r--app/models/integrations/buildkite.rb31
-rw-r--r--app/models/integrations/drone_ci.rb32
-rw-r--r--app/models/integrations/jenkins.rb52
-rw-r--r--app/models/integrations/mock_ci.rb18
-rw-r--r--app/models/integrations/teamcity.rb46
-rw-r--r--app/models/namespace.rb6
-rw-r--r--app/models/project.rb2
-rw-r--r--app/presenters/clusterable_presenter.rb5
-rw-r--r--config/feature_flags/development/route_hll_to_snowplow.yml2
-rw-r--r--db/docs/alert_management_alert_assignees.yml2
-rw-r--r--db/docs/alert_management_alert_metric_images.yml2
-rw-r--r--db/docs/alert_management_alert_user_mentions.yml2
-rw-r--r--db/docs/alert_management_alerts.yml2
-rw-r--r--db/docs/alert_management_http_integrations.yml4
-rw-r--r--db/docs/clusters.yml6
-rw-r--r--db/docs/clusters_integration_prometheus.yml2
-rw-r--r--db/docs/error_tracking_client_keys.yml2
-rw-r--r--db/docs/error_tracking_error_events.yml4
-rw-r--r--db/docs/error_tracking_errors.yml4
-rw-r--r--db/docs/incident_management_escalation_policies.yml2
-rw-r--r--db/docs/incident_management_escalation_rules.yml2
-rw-r--r--db/docs/incident_management_issuable_escalation_statuses.yml2
-rw-r--r--db/docs/incident_management_oncall_participants.yml3
-rw-r--r--db/docs/incident_management_oncall_rotations.yml3
-rw-r--r--db/docs/incident_management_oncall_schedules.yml3
-rw-r--r--db/docs/incident_management_oncall_shifts.yml3
-rw-r--r--db/docs/incident_management_pending_alert_escalations.yml4
-rw-r--r--db/docs/incident_management_pending_issue_escalations.yml2
-rw-r--r--db/docs/incident_management_timeline_events.yml2
-rw-r--r--db/docs/issuable_slas.yml4
-rw-r--r--db/docs/issues_prometheus_alert_events.yml2
-rw-r--r--db/docs/issues_self_managed_prometheus_alert_events.yml2
-rw-r--r--db/docs/project_alerting_settings.yml4
-rw-r--r--db/docs/project_error_tracking_settings.yml4
-rw-r--r--db/docs/project_incident_management_settings.yml2
-rw-r--r--db/docs/prometheus_alerts.yml2
-rw-r--r--db/docs/sentry_issues.yml2
-rw-r--r--db/docs/status_page_published_incidents.yml2
-rw-r--r--db/docs/status_page_settings.yml2
-rw-r--r--db/docs/zoom_meetings.yml2
-rw-r--r--doc/development/changelog.md2
-rw-r--r--doc/development/experiment_guide/experiment_code_reviews.md25
-rw-r--r--doc/development/experiment_guide/experiment_rollout.md77
-rw-r--r--doc/development/experiment_guide/gitlab_experiment.md589
-rw-r--r--doc/development/experiment_guide/implementing_experiments.md369
-rw-r--r--doc/development/experiment_guide/index.md83
-rw-r--r--doc/development/experiment_guide/testing_experiments.md150
-rw-r--r--doc/update/index.md13
-rw-r--r--doc/user/application_security/container_scanning/index.md25
-rw-r--r--doc/user/group/iterations/index.md64
-rw-r--r--lib/api/admin/instance_clusters.rb2
-rw-r--r--lib/api/group_clusters.rb2
-rw-r--r--lib/api/project_clusters.rb4
-rw-r--r--lib/gitlab/background_migration/backfill_integrations_type_new.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_issue_search_data.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb8
-rw-r--r--lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_user_namespace.rb2
-rw-r--r--lib/gitlab/background_migration/delete_orphaned_deployments.rb2
-rw-r--r--lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb2
-rw-r--r--lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb2
-rw-r--r--lib/gitlab/background_migration/fix_projects_without_project_feature.rb2
-rw-r--r--lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb4
-rw-r--r--lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb2
-rw-r--r--lib/gitlab/background_migration/migrate_stage_status.rb81
-rw-r--r--lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb2
-rw-r--r--lib/gitlab/background_migration/populate_container_repository_migration_plan.rb2
-rw-r--r--lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb2
-rw-r--r--lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb2
-rw-r--r--lib/gitlab/background_migration/populate_vulnerability_reads.rb2
-rw-r--r--lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb10
-rw-r--r--lib/gitlab/background_migration/remove_vulnerability_finding_links.rb2
-rw-r--r--lib/gitlab/background_migration/update_timelogs_null_spent_at.rb2
-rw-r--r--lib/gitlab/background_migration/update_timelogs_project_id.rb2
-rw-r--r--lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb2
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb3
-rw-r--r--lib/gitlab/graphql/find_argument_in_parent.rb32
-rw-r--r--lib/sidebars/groups/menus/kubernetes_menu.rb3
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/qa/page/component/new_snippet.rb3
-rw-r--r--qa/qa/page/dashboard/snippet/edit.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb2
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js45
-rw-r--r--spec/graphql/resolvers/design_management/versions_resolver_spec.rb11
-rw-r--r--spec/helpers/application_settings_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb45
-rw-r--r--spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb38
-rw-r--r--spec/models/deployment_spec.rb38
-rw-r--r--spec/models/integration_spec.rb18
-rw-r--r--spec/models/namespace_spec.rb36
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb4
-rw-r--r--spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb2
119 files changed, 1169 insertions, 1159 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 14bc169854a..37c5c2ed238 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -241,10 +241,6 @@ Style/KeywordParametersOrder:
Style/Lambda:
Enabled: false
-# Offense count: 21
-Style/MissingRespondToMissing:
- Enabled: false
-
# Offense count: 35
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength.
diff --git a/.rubocop_todo/database/multiple_databases.yml b/.rubocop_todo/database/multiple_databases.yml
index 019fb41094d..40de71b1203 100644
--- a/.rubocop_todo/database/multiple_databases.yml
+++ b/.rubocop_todo/database/multiple_databases.yml
@@ -14,34 +14,9 @@ Database/MultipleDatabases:
- 'ee/spec/services/ee/merge_requests/update_service_spec.rb'
- 'lib/backup/database.rb'
- 'lib/backup/manager.rb'
- - lib/gitlab/background_migration/backfill_integrations_type_new.rb
- - lib/gitlab/background_migration/backfill_issue_search_data.rb
- - lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb
- - lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb
- - lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
- - lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb
- lib/gitlab/background_migration/backfill_projects_with_coverage.rb
- - lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb
- - lib/gitlab/background_migration/backfill_user_namespace.rb
- lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans.rb
- - lib/gitlab/background_migration/delete_orphaned_deployments.rb
- - lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb
- - lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb
- - lib/gitlab/background_migration/fix_projects_without_project_feature.rb
- - lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb
- - lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb
- - lib/gitlab/background_migration/migrate_stage_status.rb
- - lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb
- lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds.rb
- - lib/gitlab/background_migration/populate_container_repository_migration_plan.rb
- - lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb
- - lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb
- - lib/gitlab/background_migration/populate_vulnerability_reads.rb
- - lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
- - lib/gitlab/background_migration/remove_vulnerability_finding_links.rb
- - lib/gitlab/background_migration/update_timelogs_null_spent_at.rb
- - lib/gitlab/background_migration/update_timelogs_project_id.rb
- - lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
- 'lib/gitlab/database.rb'
- 'lib/gitlab/database/load_balancing/load_balancer.rb'
- 'lib/gitlab/database/migrations/observers/query_log.rb'
diff --git a/.rubocop_todo/migration/background_migration_record.yml b/.rubocop_todo/migration/background_migration_record.yml
index 7699d508da5..6ae518a86fb 100644
--- a/.rubocop_todo/migration/background_migration_record.yml
+++ b/.rubocop_todo/migration/background_migration_record.yml
@@ -28,7 +28,6 @@ Migration/BackgroundMigrationRecord:
- lib/gitlab/background_migration/migrate_merge_request_diff_commit_users.rb
- lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb
- lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb
- - lib/gitlab/background_migration/migrate_stage_status.rb
- lib/gitlab/background_migration/migrate_u2f_webauthn.rb
- lib/gitlab/background_migration/populate_latest_pipeline_ids.rb
- lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb
@@ -54,4 +53,4 @@ Migration/BackgroundMigrationRecord:
- ee/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column.rb
- ee/lib/ee/gitlab/background_migration/populate_status_column_of_security_scans.rb
- ee/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings.rb
- - ee/lib/ee/gitlab/background_migration/update_vulnerability_occurrences_location.rb \ No newline at end of file
+ - ee/lib/ee/gitlab/background_migration/update_vulnerability_occurrences_location.rb
diff --git a/.rubocop_todo/style/missing_respond_to_missing.yml b/.rubocop_todo/style/missing_respond_to_missing.yml
new file mode 100644
index 00000000000..4d602586342
--- /dev/null
+++ b/.rubocop_todo/style/missing_respond_to_missing.yml
@@ -0,0 +1,27 @@
+---
+Style/MissingRespondToMissing:
+ # Offense count: 21
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'app/controllers/projects/application_controller.rb'
+ - 'app/models/network/commit.rb'
+ - 'app/services/notification_service.rb'
+ - 'ee/app/controllers/ee/groups/application_controller.rb'
+ - 'ee/app/models/elastic/migration_record.rb'
+ - 'ee/app/services/ee/audit_event_service.rb'
+ - 'lib/declarative_enum.rb'
+ - 'lib/gitlab/auth/ldap/dn.rb'
+ - 'lib/gitlab/fake_application_settings.rb'
+ - 'lib/gitlab/gitaly_client/storage_settings.rb'
+ - 'lib/gitlab/graphql/batch_key.rb'
+ - 'lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb'
+ - 'lib/gitlab/legacy_github_import/client.rb'
+ - 'lib/gitlab/metrics/null_metric.rb'
+ - 'lib/gitlab/tracking/event_definition.rb'
+ - 'lib/kramdown/parser/atlassian_document_format.rb'
+ - 'lib/mattermost/session.rb'
+ - 'lib/uploaded_file.rb'
+ - 'qa/qa/runtime/release.rb'
+ - 'qa/qa/runtime/scenario.rb'
+ - 'spec/support/helpers/next_found_instance_of.rb'
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index 866148596e3..d0c4ad3646c 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -57,6 +57,9 @@ export default {
update({ currentLicense }) {
return currentLicense?.plan;
},
+ error() {
+ this.hasCurrentLicenseFetchError = true;
+ },
},
},
props: {
@@ -99,6 +102,7 @@ export default {
autoDevopsEnabledAlertDismissedProjects: [],
errorMessage: '',
currentLicensePlan: '',
+ hasCurrentLicenseFetchError: false,
};
},
computed: {
@@ -120,7 +124,10 @@ export default {
);
},
shouldShowVulnerabilityManagementTab() {
- return this.currentLicensePlan === LICENSE_ULTIMATE;
+ // if the query fails (if the plan is `null` also means an error has occurred) we still want to show the feature
+ const hasQueryError = this.hasCurrentLicenseFetchError || this.currentLicensePlan === null;
+
+ return hasQueryError || this.currentLicensePlan === LICENSE_ULTIMATE;
},
},
methods: {
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 54df021e4f8..97b3c4c6a2f 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -173,16 +173,12 @@ class Clusters::ClustersController < Clusters::BaseController
private
- def certificate_based_clusters_enabled?
- Feature.enabled?(:certificate_based_clusters, clusterable.clusterable_namespace, default_enabled: :yaml, type: :ops)
- end
-
def ensure_feature_enabled!
- render_404 unless certificate_based_clusters_enabled?
+ render_404 unless clusterable.certificate_based_clusters_enabled?
end
def cluster_list
- return [] unless certificate_based_clusters_enabled?
+ return [] unless clusterable.certificate_based_clusters_enabled?
finder = ClusterAncestorsFinder.new(clusterable.__subject__, current_user)
clusters = finder.execute
diff --git a/app/graphql/mutations/environments/canary_ingress/update.rb b/app/graphql/mutations/environments/canary_ingress/update.rb
index ce24b8842c6..b5633f49809 100644
--- a/app/graphql/mutations/environments/canary_ingress/update.rb
+++ b/app/graphql/mutations/environments/canary_ingress/update.rb
@@ -24,7 +24,7 @@ module Mutations
'https://gitlab.com/groups/gitlab-org/configure/-/epics/8'
def resolve(id:, **kwargs)
- return { errors: [REMOVAL_ERR_MSG] } if cert_based_clusters_ff_disabled?
+ return { errors: [REMOVAL_ERR_MSG] } unless certificate_based_clusters_enabled?
environment = authorized_find!(id: id)
@@ -43,8 +43,9 @@ module Mutations
private
- def cert_based_clusters_ff_disabled?
- Feature.disabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops)
+ def certificate_based_clusters_enabled?
+ instance_cluster = ::Clusters::Instance.new
+ instance_cluster.certificate_based_clusters_enabled?
end
end
end
diff --git a/app/graphql/resolvers/design_management/designs_resolver.rb b/app/graphql/resolvers/design_management/designs_resolver.rb
index a62ef6d76e5..50e3b64448c 100644
--- a/app/graphql/resolvers/design_management/designs_resolver.rb
+++ b/app/graphql/resolvers/design_management/designs_resolver.rb
@@ -24,6 +24,10 @@ module Resolvers
end
def resolve(ids: nil, filenames: nil, at_version: nil)
+ # TODO: remove the coercion when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ context.scoped_set!(:at_version_argument, VersionID.coerce_isolated_input(at_version)) if at_version
+
::DesignManagement::DesignsFinder.new(
issue,
current_user,
diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb
index 23ba3c86d98..df283104821 100644
--- a/app/graphql/resolvers/design_management/versions_resolver.rb
+++ b/app/graphql/resolvers/design_management/versions_resolver.rb
@@ -9,8 +9,6 @@ module Resolvers
VersionID = ::Types::GlobalIDType[::DesignManagement::Version]
- extras [:parent]
-
argument :earlier_or_equal_to_sha, GraphQL::Types::String,
as: :sha,
required: false,
@@ -26,11 +24,11 @@ module Resolvers
::Resolvers::DesignManagement::VersionInCollectionResolver
end
- def resolve(parent: nil, id: nil, sha: nil)
+ def resolve(id: nil, sha: nil)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id &&= VersionID.coerce_isolated_input(id)
- version = cutoff(parent, id, sha)
+ version = cutoff(id, sha)
raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'cutoff not found' unless version.present?
@@ -44,11 +42,11 @@ module Resolvers
private
# Find the most recent version that the client will accept
- def cutoff(parent, id, sha)
+ def cutoff(id, sha)
if sha.present? || id.present?
specific_version(id, sha)
- elsif at_version = at_version_arg(parent)
- by_id(at_version)
+ elsif at_version = context[:at_version_argument]
+ by_id(at_version) # See: DesignsResolver
else
:unconstrained
end
@@ -68,20 +66,6 @@ module Resolvers
def by_id(gid)
::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(gid))
end
-
- # Find an `at_version` argument passed to a parent node.
- #
- # If one is found, then a design collection further up the AST
- # has been filtered to reflect designs at that version, and so
- # for consistency we should only present versions up to the given
- # version here.
- def at_version_arg(parent)
- # TODO: remove coercion when the compatibility layer is removed
- # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
- version_id = ::Gitlab::Graphql::FindArgumentInParent.find(parent, :at_version, limit_depth: 4)
- version_id &&= VersionID.coerce_isolated_input(version_id)
- version_id
- end
end
end
end
diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb
index 4c0b1162306..cc4c0e19ec7 100644
--- a/app/graphql/types/design_management/design_type.rb
+++ b/app/graphql/types/design_management/design_type.rb
@@ -23,14 +23,13 @@ module Types
field :versions,
Types::DesignManagement::VersionType.connection_type,
resolver: Resolvers::DesignManagement::VersionsResolver,
- description: "All versions related to this design ordered newest first.",
- extras: [:parent]
+ description: "All versions related to this design ordered newest first."
# Returns a `DesignManagement::Version` for this query based on the
# `atVersion` argument passed to a parent node if present, or otherwise
# the most recent `Version` for the issue.
def cached_stateful_version(parent_node)
- version_gid = Gitlab::Graphql::FindArgumentInParent.find(parent_node, :at_version)
+ version_gid = context[:at_version_argument] # See: DesignsResolver
# Caching is scoped to an `issue_id` to allow us to cache the
# most recent `Version` for an issue
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index dbdfa0c1eab..aa0e8c55470 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -462,7 +462,7 @@ module ApplicationSettingsHelper
def instance_clusters_enabled?
clusterable = Clusters::Instance.new
- Feature.enabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops) &&
+ clusterable.certificate_based_clusters_enabled? &&
can?(current_user, :read_cluster, clusterable)
end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 116ff284c16..6a721f600f5 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -23,7 +23,7 @@ module ClustersHelper
can_add_cluster: clusterable.can_add_cluster?.to_s,
can_admin_cluster: clusterable.can_admin_cluster?.to_s,
display_cluster_agents: display_cluster_agents?(clusterable).to_s,
- certificate_based_clusters_enabled: Feature.enabled?(:certificate_based_clusters, clusterable.clusterable_namespace, default_enabled: :yaml, type: :ops).to_s,
+ certificate_based_clusters_enabled: clusterable.certificate_based_clusters_enabled?.to_s,
default_branch_name: default_branch_name(clusterable),
project_path: clusterable_project_path(clusterable),
kas_address: Gitlab::Kas.external_url,
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index dff8bb89021..0f4cac7f5b4 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -45,7 +45,7 @@ module Ci
dotenv: '.env',
cobertura: 'cobertura-coverage.xml',
terraform: 'tfplan.json',
- cluster_applications: 'gl-cluster-applications.json', # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/333441
+ cluster_applications: 'gl-cluster-applications.json', # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/361094
requirements: 'requirements.json',
coverage_fuzzing: 'gl-coverage-fuzzing.json',
api_fuzzing: 'gl-api-fuzzing-report.json'
@@ -64,7 +64,7 @@ module Ci
network_referee: :gzip,
dotenv: :gzip,
cobertura: :gzip,
- cluster_applications: :gzip,
+ cluster_applications: :gzip, # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/361094
lsif: :zip,
# Security reports and license scanning reports are raw artifacts
diff --git a/app/models/clusters/instance.rb b/app/models/clusters/instance.rb
index 2a09ba11564..ef120384adf 100644
--- a/app/models/clusters/instance.rb
+++ b/app/models/clusters/instance.rb
@@ -9,5 +9,11 @@ module Clusters
def flipper_id
self.class.to_s
end
+
+ def certificate_based_clusters_enabled?
+ ::Gitlab::SafeRequestStore.fetch("certificate_based_clusters:") do
+ Feature.enabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops)
+ end
+ end
end
end
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 9ee28916553..2b5e1a204cb 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -3,7 +3,7 @@
module DeploymentPlatform
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def deployment_platform(environment: nil)
- return if Feature.disabled?(:certificate_based_clusters, self.namespace, default_enabled: :yaml, type: :ops)
+ return unless self.namespace.certificate_based_clusters_enabled?
@deployment_platform ||= {}
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 63d531d82c3..4204ad707b2 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -70,6 +70,11 @@ class Deployment < ApplicationRecord
transition created: :blocked
end
+ # This transition is possible when we have manual jobs.
+ event :create do
+ transition skipped: :created
+ end
+
event :unblock do
transition blocked: :created
end
@@ -348,7 +353,7 @@ class Deployment < ApplicationRecord
def sync_status_with(build)
return false unless ::Deployment.statuses.include?(build.status)
- return false if build.created? || build.status == self.status
+ return false if build.status == self.status
update_status!(build.status)
rescue StandardError => e
@@ -403,6 +408,8 @@ class Deployment < ApplicationRecord
skip!
when 'blocked'
block!
+ when 'created'
+ create!
else
raise ArgumentError, "The status #{status.inspect} is invalid"
end
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 13ef37e0157..c905788ac8b 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -161,7 +161,7 @@ class Integration < ApplicationRecord
end
def fields
- self.class.fields
+ self.class.fields.dup
end
# Provide convenient accessor methods for each serialized property.
diff --git a/app/models/integrations/bamboo.rb b/app/models/integrations/bamboo.rb
index b384a94d713..4e144a688f6 100644
--- a/app/models/integrations/bamboo.rb
+++ b/app/models/integrations/bamboo.rb
@@ -5,7 +5,26 @@ module Integrations
include ReactivelyCached
prepend EnableSslVerification
- prop_accessor :bamboo_url, :build_key, :username, :password
+ field :bamboo_url,
+ title: s_('BambooService|Bamboo URL'),
+ placeholder: s_('https://bamboo.example.com'),
+ help: s_('BambooService|Bamboo service root URL.'),
+ required: true
+
+ field :build_key,
+ help: s_('BambooService|Bamboo build plan key.'),
+ non_empty_password_title: s_('BambooService|Enter new build key'),
+ non_empty_password_help: s_('BambooService|Leave blank to use your current build key.'),
+ placeholder: s_('KEY'),
+ required: true
+
+ field :username,
+ help: s_('BambooService|The user with API access to the Bamboo server.')
+
+ field :password,
+ type: 'password',
+ non_empty_password_title: s_('ProjectService|Enter new password'),
+ non_empty_password_help: s_('ProjectService|Leave blank to use your current password')
validates :bamboo_url, presence: true, public_url: true, if: :activated?
validates :build_key, presence: true, if: :activated?
@@ -43,39 +62,6 @@ module Integrations
'bamboo'
end
- def fields
- [
- {
- type: 'text',
- name: 'bamboo_url',
- title: s_('BambooService|Bamboo URL'),
- placeholder: s_('https://bamboo.example.com'),
- help: s_('BambooService|Bamboo service root URL.'),
- required: true
- },
- {
- type: 'password',
- name: 'build_key',
- help: s_('BambooService|Bamboo build plan key.'),
- non_empty_password_title: s_('BambooService|Enter new build key'),
- non_empty_password_help: s_('BambooService|Leave blank to use your current build key.'),
- placeholder: s_('KEY'),
- required: true
- },
- {
- type: 'text',
- name: 'username',
- help: s_('BambooService|The user with API access to the Bamboo server.')
- },
- {
- type: 'password',
- name: 'password',
- non_empty_password_title: s_('ProjectService|Enter new password'),
- non_empty_password_help: s_('ProjectService|Leave blank to use your current password')
- }
- ]
- end
-
def build_page(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end
diff --git a/app/models/integrations/buildkite.rb b/app/models/integrations/buildkite.rb
index 3b802271a36..d1e54ce86ee 100644
--- a/app/models/integrations/buildkite.rb
+++ b/app/models/integrations/buildkite.rb
@@ -10,7 +10,18 @@ module Integrations
ENDPOINT = "https://buildkite.com"
- prop_accessor :project_url, :token
+ field :project_url,
+ title: _('Pipeline URL'),
+ placeholder: "#{ENDPOINT}/example-org/test-pipeline",
+ required: true
+
+ field :token,
+ type: 'password',
+ title: _('Token'),
+ help: s_('ProjectService|The token you get after you create a Buildkite pipeline with a GitLab repository.'),
+ non_empty_password_title: s_('ProjectService|Enter new token'),
+ non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
+ required: true
validates :project_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated?
@@ -74,24 +85,6 @@ module Integrations
s_('ProjectService|Run CI/CD pipelines with Buildkite.')
end
- def fields
- [
- { type: 'password',
- name: 'token',
- title: _('Token'),
- help: s_('ProjectService|The token you get after you create a Buildkite pipeline with a GitLab repository.'),
- non_empty_password_title: s_('ProjectService|Enter new token'),
- non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
- required: true },
-
- { type: 'text',
- name: 'project_url',
- title: _('Pipeline URL'),
- placeholder: "#{ENDPOINT}/example-org/test-pipeline",
- required: true }
- ]
- end
-
def calculate_reactive_cache(sha, ref)
response = Gitlab::HTTP.try_get(commit_status_path(sha), request_options)
diff --git a/app/models/integrations/drone_ci.rb b/app/models/integrations/drone_ci.rb
index 73f78bec381..0c65ed8cd5f 100644
--- a/app/models/integrations/drone_ci.rb
+++ b/app/models/integrations/drone_ci.rb
@@ -10,7 +10,17 @@ module Integrations
DRONE_SAAS_HOSTNAME = 'cloud.drone.io'
- prop_accessor :drone_url, :token
+ field :drone_url,
+ title: s_('ProjectService|Drone server URL'),
+ placeholder: 'http://drone.example.com',
+ required: true
+
+ field :token,
+ type: 'password',
+ help: s_('ProjectService|Token for the Drone project.'),
+ non_empty_password_title: s_('ProjectService|Enter new token'),
+ non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
+ required: true
validates :drone_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated?
@@ -94,26 +104,6 @@ module Integrations
s_('ProjectService|Run CI/CD pipelines with Drone.')
end
- def fields
- [
- {
- type: 'password',
- name: 'token',
- help: s_('ProjectService|Token for the Drone project.'),
- non_empty_password_title: s_('ProjectService|Enter new token'),
- non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
- required: true
- },
- {
- type: 'text',
- name: 'drone_url',
- title: s_('ProjectService|Drone server URL'),
- placeholder: 'http://drone.example.com',
- required: true
- }
- ]
- end
-
override :hook_url
def hook_url
[drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join
diff --git a/app/models/integrations/jenkins.rb b/app/models/integrations/jenkins.rb
index 83838ac1b53..a1abbce72bc 100644
--- a/app/models/integrations/jenkins.rb
+++ b/app/models/integrations/jenkins.rb
@@ -7,7 +7,25 @@ module Integrations
prepend EnableSslVerification
extend Gitlab::Utils::Override
- prop_accessor :jenkins_url, :project_name, :username, :password
+ field :jenkins_url,
+ title: s_('ProjectService|Jenkins server URL'),
+ required: true,
+ placeholder: 'http://jenkins.example.com',
+ help: s_('The URL of the Jenkins server.')
+
+ field :project_name,
+ required: true,
+ placeholder: 'my_project_name',
+ help: s_('The name of the Jenkins project. Copy the name from the end of the URL to the project.')
+
+ field :username,
+ help: s_('The username for the Jenkins server.')
+
+ field :password,
+ type: 'password',
+ help: s_('The password for the Jenkins server.'),
+ non_empty_password_title: s_('ProjectService|Enter new password.'),
+ non_empty_password_help: s_('ProjectService|Leave blank to use your current password.')
before_validation :reset_password
@@ -71,37 +89,5 @@ module Integrations
def self.to_param
'jenkins'
end
-
- def fields
- [
- {
- type: 'text',
- name: 'jenkins_url',
- title: s_('ProjectService|Jenkins server URL'),
- required: true,
- placeholder: 'http://jenkins.example.com',
- help: s_('The URL of the Jenkins server.')
- },
- {
- type: 'text',
- name: 'project_name',
- required: true,
- placeholder: 'my_project_name',
- help: s_('The name of the Jenkins project. Copy the name from the end of the URL to the project.')
- },
- {
- type: 'text',
- name: 'username',
- help: s_('The username for the Jenkins server.')
- },
- {
- type: 'password',
- name: 'password',
- help: s_('The password for the Jenkins server.'),
- non_empty_password_title: s_('ProjectService|Enter new password.'),
- non_empty_password_help: s_('ProjectService|Leave blank to use your current password.')
- }
- ]
- end
end
end
diff --git a/app/models/integrations/mock_ci.rb b/app/models/integrations/mock_ci.rb
index 568fb609a44..cd2928136ef 100644
--- a/app/models/integrations/mock_ci.rb
+++ b/app/models/integrations/mock_ci.rb
@@ -7,7 +7,11 @@ module Integrations
ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze
- prop_accessor :mock_service_url
+ field :mock_service_url,
+ title: s_('ProjectService|Mock service URL'),
+ placeholder: 'http://localhost:4004',
+ required: true
+
validates :mock_service_url, presence: true, public_url: true, if: :activated?
def title
@@ -22,18 +26,6 @@ module Integrations
'mock_ci'
end
- def fields
- [
- {
- type: 'text',
- name: 'mock_service_url',
- title: s_('ProjectService|Mock service URL'),
- placeholder: 'http://localhost:4004',
- required: true
- }
- ]
- end
-
# Return complete url to build page
#
# Ex.
diff --git a/app/models/integrations/teamcity.rb b/app/models/integrations/teamcity.rb
index f0f83f118d7..1205173e40b 100644
--- a/app/models/integrations/teamcity.rb
+++ b/app/models/integrations/teamcity.rb
@@ -8,7 +8,22 @@ module Integrations
TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i.freeze
- prop_accessor :teamcity_url, :build_type, :username, :password
+ field :teamcity_url,
+ title: s_('ProjectService|TeamCity server URL'),
+ placeholder: 'https://teamcity.example.com',
+ required: true
+
+ field :build_type,
+ help: s_('ProjectService|The build configuration ID of the TeamCity project.'),
+ required: true
+
+ field :username,
+ help: s_('ProjectService|Must have permission to trigger a manual build in TeamCity.')
+
+ field :password,
+ type: 'password',
+ non_empty_password_title: s_('ProjectService|Enter new password'),
+ non_empty_password_help: s_('ProjectService|Leave blank to use your current password')
validates :teamcity_url, presence: true, public_url: true, if: :activated?
validates :build_type, presence: true, if: :activated?
@@ -51,35 +66,6 @@ module Integrations
s_('To run CI/CD pipelines with JetBrains TeamCity, input the GitLab project details in the TeamCity project Version Control Settings.')
end
- def fields
- [
- {
- type: 'text',
- name: 'teamcity_url',
- title: s_('ProjectService|TeamCity server URL'),
- placeholder: 'https://teamcity.example.com',
- required: true
- },
- {
- type: 'text',
- name: 'build_type',
- help: s_('ProjectService|The build configuration ID of the TeamCity project.'),
- required: true
- },
- {
- type: 'text',
- name: 'username',
- help: s_('ProjectService|Must have permission to trigger a manual build in TeamCity.')
- },
- {
- type: 'password',
- name: 'password',
- non_empty_password_title: s_('ProjectService|Enter new password'),
- non_empty_password_help: s_('ProjectService|Leave blank to use your current password')
- }
- ]
- end
-
def build_page(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index fa9e8152fa9..32f44c6cfcc 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -525,6 +525,12 @@ class Namespace < ApplicationRecord
nil
end
+ def certificate_based_clusters_enabled?
+ ::Gitlab::SafeRequestStore.fetch("certificate_based_clusters:ns:#{self.id}") do
+ Feature.enabled?(:certificate_based_clusters, self, default_enabled: :yaml, type: :ops)
+ end
+ end
+
private
def expire_child_caches
diff --git a/app/models/project.rb b/app/models/project.rb
index eeafb188fc4..f1e2967fb0e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -465,7 +465,7 @@ class Project < ApplicationRecord
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
- delegate :root_ancestor, to: :namespace, allow_nil: true
+ delegate :root_ancestor, :certificate_based_clusters_enabled?, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index f8ad3e18505..c2ed40d8b0c 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -12,11 +12,6 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
.fabricate!
end
- def clusterable_namespace
- return clusterable.namespace if clusterable.is_a?(Project)
- return clusterable if clusterable.is_a?(Group)
- end
-
def can_add_cluster?
can?(current_user, :add_cluster, clusterable)
end
diff --git a/config/feature_flags/development/route_hll_to_snowplow.yml b/config/feature_flags/development/route_hll_to_snowplow.yml
index 3c8f7826a0a..0caaf1446ca 100644
--- a/config/feature_flags/development/route_hll_to_snowplow.yml
+++ b/config/feature_flags/development/route_hll_to_snowplow.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354442
milestone: '14.9'
type: development
group: group::product intelligence
-default_enabled: false
+default_enabled: true
diff --git a/db/docs/alert_management_alert_assignees.yml b/db/docs/alert_management_alert_assignees.yml
index 6b91d6db72c..4d5a1acb0ca 100644
--- a/db/docs/alert_management_alert_assignees.yml
+++ b/db/docs/alert_management_alert_assignees.yml
@@ -4,6 +4,6 @@ classes:
- AlertManagement::AlertAssignee
feature_categories:
- incident_management
-description: TODO
+description: Persists metadata between users and alerts to support alert assignments
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32609
milestone: '13.1'
diff --git a/db/docs/alert_management_alert_metric_images.yml b/db/docs/alert_management_alert_metric_images.yml
index e43be4f854c..bcfa2b54b1c 100644
--- a/db/docs/alert_management_alert_metric_images.yml
+++ b/db/docs/alert_management_alert_metric_images.yml
@@ -4,6 +4,6 @@ classes:
- AlertManagement::MetricImage
feature_categories:
- incident_management
-description: TODO
+description: Persists metadata for uploads related to alerts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80339
milestone: '14.8'
diff --git a/db/docs/alert_management_alert_user_mentions.yml b/db/docs/alert_management_alert_user_mentions.yml
index eda29a17cda..6a3aaf2ce83 100644
--- a/db/docs/alert_management_alert_user_mentions.yml
+++ b/db/docs/alert_management_alert_user_mentions.yml
@@ -4,6 +4,6 @@ classes:
- AlertManagement::AlertUserMention
feature_categories:
- incident_management
-description: TODO
+description: Persists metadata for system notes related to alerts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33217
milestone: '13.1'
diff --git a/db/docs/alert_management_alerts.yml b/db/docs/alert_management_alerts.yml
index 347653fbb0b..ca8b02ec346 100644
--- a/db/docs/alert_management_alerts.yml
+++ b/db/docs/alert_management_alerts.yml
@@ -4,6 +4,6 @@ classes:
- AlertManagement::Alert
feature_categories:
- incident_management
-description: TODO
+description: Persists incoming alert data including its payload
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29864
milestone: '13.0'
diff --git a/db/docs/alert_management_http_integrations.yml b/db/docs/alert_management_http_integrations.yml
index 7dba70bcb67..8fa330f0775 100644
--- a/db/docs/alert_management_http_integrations.yml
+++ b/db/docs/alert_management_http_integrations.yml
@@ -4,6 +4,6 @@ classes:
- AlertManagement::HttpIntegration
feature_categories:
- incident_management
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44098
+description: Persists settings for alert HTTP integrations
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43634
milestone: '13.5'
diff --git a/db/docs/clusters.yml b/db/docs/clusters.yml
index 142186e4410..cad0fd4c3b3 100644
--- a/db/docs/clusters.yml
+++ b/db/docs/clusters.yml
@@ -3,7 +3,7 @@ table_name: clusters
classes:
- Clusters::Cluster
feature_categories:
-- incident_management
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/d0cff7f5855f91b5479f9fdaa39d8d95ec691a9e
+- kubernetes_management
+description: Persists information about GitLab managed clusters
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14879
milestone: '10.1'
diff --git a/db/docs/clusters_integration_prometheus.yml b/db/docs/clusters_integration_prometheus.yml
index 78a1933a931..f8702226daa 100644
--- a/db/docs/clusters_integration_prometheus.yml
+++ b/db/docs/clusters_integration_prometheus.yml
@@ -4,6 +4,6 @@ classes:
- Clusters::Integrations::Prometheus
feature_categories:
- incident_management
-description: TODO
+description: Persists information about prometheus cluster integration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59091
milestone: '13.11'
diff --git a/db/docs/error_tracking_client_keys.yml b/db/docs/error_tracking_client_keys.yml
index b9c7b74f0a3..c07fc282839 100644
--- a/db/docs/error_tracking_client_keys.yml
+++ b/db/docs/error_tracking_client_keys.yml
@@ -4,6 +4,6 @@ classes:
- ErrorTracking::ClientKey
feature_categories:
- error_tracking
-description: TODO
+description: Model to store public keys used by Sentry SDK for Error Tracking
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66466
milestone: '14.2'
diff --git a/db/docs/error_tracking_error_events.yml b/db/docs/error_tracking_error_events.yml
index 19a7f1cbfe8..9d938e47e3c 100644
--- a/db/docs/error_tracking_error_events.yml
+++ b/db/docs/error_tracking_error_events.yml
@@ -4,6 +4,6 @@ classes:
- ErrorTracking::ErrorEvent
feature_categories:
- error_tracking
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/2d1c197ab0bf10071cb52e579edd3808cb0adc21
+description: Persists error event data for the Error Tracking's GitLab backend
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64712
milestone: '14.1'
diff --git a/db/docs/error_tracking_errors.yml b/db/docs/error_tracking_errors.yml
index b348d47c77f..a961d759da3 100644
--- a/db/docs/error_tracking_errors.yml
+++ b/db/docs/error_tracking_errors.yml
@@ -4,6 +4,6 @@ classes:
- ErrorTracking::Error
feature_categories:
- error_tracking
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/2d1c197ab0bf10071cb52e579edd3808cb0adc21
+description: Persists error data for the Error Tracking's GitLab backend
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64712
milestone: '14.1'
diff --git a/db/docs/incident_management_escalation_policies.yml b/db/docs/incident_management_escalation_policies.yml
index f8bb51957fd..9584f65b14d 100644
--- a/db/docs/incident_management_escalation_policies.yml
+++ b/db/docs/incident_management_escalation_policies.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::EscalationPolicy
feature_categories:
- incident_management
-description: TODO
+description: Persists information about escalation policies in a project
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60685
milestone: '13.12'
diff --git a/db/docs/incident_management_escalation_rules.yml b/db/docs/incident_management_escalation_rules.yml
index 6f97d8b6524..40c1f9bdcc0 100644
--- a/db/docs/incident_management_escalation_rules.yml
+++ b/db/docs/incident_management_escalation_rules.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::EscalationRule
feature_categories:
- incident_management
-description: TODO
+description: Persists information about escalation rules for incident management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60685
milestone: '13.12'
diff --git a/db/docs/incident_management_issuable_escalation_statuses.yml b/db/docs/incident_management_issuable_escalation_statuses.yml
index 4c0010f212a..466bc0314b6 100644
--- a/db/docs/incident_management_issuable_escalation_statuses.yml
+++ b/db/docs/incident_management_issuable_escalation_statuses.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::IssuableEscalationStatus
feature_categories:
- incident_management
-description: TODO
+description: Persists escalation status information for incidents
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65206
milestone: '14.2'
diff --git a/db/docs/incident_management_oncall_participants.yml b/db/docs/incident_management_oncall_participants.yml
index a394afae79b..9186be4824c 100644
--- a/db/docs/incident_management_oncall_participants.yml
+++ b/db/docs/incident_management_oncall_participants.yml
@@ -4,6 +4,7 @@ classes:
- IncidentManagement::OncallParticipant
feature_categories:
- incident_management
-description: TODO
+- on_call_schedule_management
+description: Persists information about on-call rotation participants
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49058
milestone: '13.7'
diff --git a/db/docs/incident_management_oncall_rotations.yml b/db/docs/incident_management_oncall_rotations.yml
index 114973d12be..bf9df2e2d2a 100644
--- a/db/docs/incident_management_oncall_rotations.yml
+++ b/db/docs/incident_management_oncall_rotations.yml
@@ -4,6 +4,7 @@ classes:
- IncidentManagement::OncallRotation
feature_categories:
- incident_management
-description: TODO
+- on_call_schedule_management
+description: Persists information about on-call rotation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49058
milestone: '13.7'
diff --git a/db/docs/incident_management_oncall_schedules.yml b/db/docs/incident_management_oncall_schedules.yml
index deb05826e08..9fa0ed1bec5 100644
--- a/db/docs/incident_management_oncall_schedules.yml
+++ b/db/docs/incident_management_oncall_schedules.yml
@@ -4,6 +4,7 @@ classes:
- IncidentManagement::OncallSchedule
feature_categories:
- incident_management
-description: TODO
+- on_call_schedule_management
+description: Persists on-call schedules for incident management in a project
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47407
milestone: '13.7'
diff --git a/db/docs/incident_management_oncall_shifts.yml b/db/docs/incident_management_oncall_shifts.yml
index 2e1a12032d4..6ef7de5da50 100644
--- a/db/docs/incident_management_oncall_shifts.yml
+++ b/db/docs/incident_management_oncall_shifts.yml
@@ -4,6 +4,7 @@ classes:
- IncidentManagement::OncallShift
feature_categories:
- incident_management
-description: TODO
+- on_call_schedule_management
+description: Tracks past and present on-call shifts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49423
milestone: '13.8'
diff --git a/db/docs/incident_management_pending_alert_escalations.yml b/db/docs/incident_management_pending_alert_escalations.yml
index 5c8f456f084..c39b8d74ce2 100644
--- a/db/docs/incident_management_pending_alert_escalations.yml
+++ b/db/docs/incident_management_pending_alert_escalations.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::PendingEscalations::Alert
feature_categories:
- incident_management
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/e5cc867503dfbd54f99df90cce6be39bc4fde712
+description: Persists information about pending alert escalations for incidents
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64274
milestone: '14.1'
diff --git a/db/docs/incident_management_pending_issue_escalations.yml b/db/docs/incident_management_pending_issue_escalations.yml
index 0956b1f9453..eb8f11fc72f 100644
--- a/db/docs/incident_management_pending_issue_escalations.yml
+++ b/db/docs/incident_management_pending_issue_escalations.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::PendingEscalations::Issue
feature_categories:
- incident_management
-description: TODO
+description: Represents when issues should be escalated according to a project's escalation policy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65209
milestone: '14.3'
diff --git a/db/docs/incident_management_timeline_events.yml b/db/docs/incident_management_timeline_events.yml
index d13a6ec29ec..6031f0d32e2 100644
--- a/db/docs/incident_management_timeline_events.yml
+++ b/db/docs/incident_management_timeline_events.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::TimelineEvent
feature_categories:
- incident_management
-description: TODO
+description: Persists timeline events for an incident
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74530
milestone: '14.6'
diff --git a/db/docs/issuable_slas.yml b/db/docs/issuable_slas.yml
index 631086f68a9..14e94a3aed3 100644
--- a/db/docs/issuable_slas.yml
+++ b/db/docs/issuable_slas.yml
@@ -4,6 +4,6 @@ classes:
- IssuableSla
feature_categories:
- incident_management
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44928
+description: Persists information about incident SLAs for incidents
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44253
milestone: '13.5'
diff --git a/db/docs/issues_prometheus_alert_events.yml b/db/docs/issues_prometheus_alert_events.yml
index 4a222ab3924..01ff7f9b6e4 100644
--- a/db/docs/issues_prometheus_alert_events.yml
+++ b/db/docs/issues_prometheus_alert_events.yml
@@ -3,6 +3,6 @@ table_name: issues_prometheus_alert_events
classes: []
feature_categories:
- incident_management
-description: TODO
+description: Adds relationship between PrometheusAlertEvent and issues created due to them
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17477
milestone: '12.4'
diff --git a/db/docs/issues_self_managed_prometheus_alert_events.yml b/db/docs/issues_self_managed_prometheus_alert_events.yml
index 016e1f17a6e..feb208d7c3a 100644
--- a/db/docs/issues_self_managed_prometheus_alert_events.yml
+++ b/db/docs/issues_self_managed_prometheus_alert_events.yml
@@ -3,6 +3,6 @@ table_name: issues_self_managed_prometheus_alert_events
classes: []
feature_categories:
- incident_management
-description: TODO
+description: Adds associations between Issues table and Prometheus alerts from self-managed Prometheus instances
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18046
milestone: '12.4'
diff --git a/db/docs/project_alerting_settings.yml b/db/docs/project_alerting_settings.yml
index 60a23e35f71..0737c65faaf 100644
--- a/db/docs/project_alerting_settings.yml
+++ b/db/docs/project_alerting_settings.yml
@@ -4,6 +4,6 @@ classes:
- Alerting::ProjectAlertingSetting
feature_categories:
- incident_management
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/39aa9458c282c1dabd3623698da5af3b9a6122a9
+description: Persists project-level tokens for manual Prometheus installations
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9334
milestone: '11.8'
diff --git a/db/docs/project_error_tracking_settings.yml b/db/docs/project_error_tracking_settings.yml
index 0a1e2571743..d10982fe712 100644
--- a/db/docs/project_error_tracking_settings.yml
+++ b/db/docs/project_error_tracking_settings.yml
@@ -4,6 +4,6 @@ classes:
- ErrorTracking::ProjectErrorTrackingSetting
feature_categories:
- error_tracking
-description: TODO
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/f40b5860d76a8ea5d964260834a6e83516b0f1fd
+description: Project settings related to Error Tracking
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24047
milestone: '11.7'
diff --git a/db/docs/project_incident_management_settings.yml b/db/docs/project_incident_management_settings.yml
index 55413824f40..b1ef6824fe2 100644
--- a/db/docs/project_incident_management_settings.yml
+++ b/db/docs/project_incident_management_settings.yml
@@ -4,6 +4,6 @@ classes:
- IncidentManagement::ProjectIncidentManagementSetting
feature_categories:
- incident_management
-description: TODO
+description: Persists project settings for incident management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9744
milestone: '11.9'
diff --git a/db/docs/prometheus_alerts.yml b/db/docs/prometheus_alerts.yml
index 0e017292f81..3d3a2e45650 100644
--- a/db/docs/prometheus_alerts.yml
+++ b/db/docs/prometheus_alerts.yml
@@ -4,6 +4,6 @@ classes:
- PrometheusAlert
feature_categories:
- incident_management
-description: TODO
+description: Persists information about prometheus alerts from an environment
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6590
milestone: '11.2'
diff --git a/db/docs/sentry_issues.yml b/db/docs/sentry_issues.yml
index 898d4ddbae1..af96751fc7d 100644
--- a/db/docs/sentry_issues.yml
+++ b/db/docs/sentry_issues.yml
@@ -4,6 +4,6 @@ classes:
- SentryIssue
feature_categories:
- error_tracking
-description: TODO
+description: Persists issue data for the Error Tracking's Sentry backend
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20629
milestone: '12.6'
diff --git a/db/docs/status_page_published_incidents.yml b/db/docs/status_page_published_incidents.yml
index 562e8eb2f80..4a21ed156f2 100644
--- a/db/docs/status_page_published_incidents.yml
+++ b/db/docs/status_page_published_incidents.yml
@@ -4,6 +4,6 @@ classes:
- StatusPage::PublishedIncident
feature_categories:
- incident_management
-description: TODO
+description: Corresponds to an issue which has been published to the Status Page
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29994
milestone: '13.0'
diff --git a/db/docs/status_page_settings.yml b/db/docs/status_page_settings.yml
index b4022bdb678..a5cefe70300 100644
--- a/db/docs/status_page_settings.yml
+++ b/db/docs/status_page_settings.yml
@@ -4,6 +4,6 @@ classes:
- StatusPage::ProjectSetting
feature_categories:
- incident_management
-description: TODO
+description: Project settings related to Status Page
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25863
milestone: '12.9'
diff --git a/db/docs/zoom_meetings.yml b/db/docs/zoom_meetings.yml
index 0c1a47eb6d0..620df953ad5 100644
--- a/db/docs/zoom_meetings.yml
+++ b/db/docs/zoom_meetings.yml
@@ -4,6 +4,6 @@ classes:
- ZoomMeeting
feature_categories:
- incident_management
-description: TODO
+description: Persists Zoom meetings, its associations and its metadata
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17890
milestone: '12.5'
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index b98ed6cb109..c19c5b40382 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -100,7 +100,7 @@ EE: true
database records created during Cycle Analytics model spec."
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
-- Any [GLEX experiment](experiment_guide/gitlab_experiment.md) changes **should not** have a changelog entry.
+- Any [experiment](experiment_guide/index.md) changes **should not** have a changelog entry.
- An MR that includes only documentation changes **should not** have a changelog entry.
For more information, see
diff --git a/doc/development/experiment_guide/experiment_code_reviews.md b/doc/development/experiment_guide/experiment_code_reviews.md
new file mode 100644
index 00000000000..fdde89caa34
--- /dev/null
+++ b/doc/development/experiment_guide/experiment_code_reviews.md
@@ -0,0 +1,25 @@
+---
+stage: Growth
+group: Adoption
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Experiment code reviews
+
+Experiments' code quality can fail our standards for several reasons. These
+reasons can include not being added to the codebase for a long time, or because
+of fast iteration to retrieve data. However, having the experiment run (or not
+run) shouldn't impact GitLab availability. To avoid or identify issues,
+experiments are initially deployed to a small number of users. Regardless,
+experiments still need tests.
+
+Experiments must have corresponding [frontend or feature tests](../testing_guide/index.md) to ensure they
+exist in the application. These tests should help prevent the experiment code from
+being removed before the [experiment cleanup process](https://about.gitlab.com/handbook/engineering/development/growth/experimentation/#experiment-cleanup-issue) starts.
+
+If, as a reviewer or maintainer, you find code that would usually fail review
+but is acceptable for now, mention your concerns with a note that there's no
+need to change the code. The author can then add a comment to this piece of code
+and link to the issue that resolves the experiment. The author or reviewer can add a link to this concern in the
+experiment rollout issue under the `Experiment Successful Cleanup Concerns` section of the description.
+If the experiment is successful and becomes part of the product, any items that appear under this section will be addressed.
diff --git a/doc/development/experiment_guide/experiment_rollout.md b/doc/development/experiment_guide/experiment_rollout.md
new file mode 100644
index 00000000000..bc700b13600
--- /dev/null
+++ b/doc/development/experiment_guide/experiment_rollout.md
@@ -0,0 +1,77 @@
+---
+stage: Growth
+group: Adoption
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Experiment rollouts and feature flags
+
+## Experiment rollout issue
+
+Each experiment should have an [experiment rollout](https://gitlab.com/groups/gitlab-org/-/boards/1352542) issue to track the experiment from rollout through to cleanup and removal.
+The rollout issue is similar to a feature flag rollout issue, and is also used to track the status of an experiment.
+
+When an experiment is deployed, the due date of the issue should be set (this depends on the experiment but can be up to a few weeks in the future).
+After the deadline, the issue needs to be resolved and either:
+
+- It was successful and the experiment becomes the new default.
+- It was not successful and all code related to the experiment is removed.
+
+In either case, an outcome of the experiment should be posted to the issue with the reasoning for the decision.
+
+## Turn off all experiments
+
+When there is a case on GitLab.com (SaaS) that necessitates turning off all experiments, we have this control.
+
+You can toggle experiments on SaaS on and off using the `gitlab_experiment` [feature flag](../feature_flags).
+
+This can be done via chatops:
+
+- [disable](../feature_flags/controls.md#disabling-feature-flags): `/chatops run feature set gitlab_experiment false`
+- [enable](../feature_flags/controls.md#process): `/chatops run feature delete gitlab_experiment`
+- This allows the `default_enabled` [value of true in the yml](https://gitlab.com/gitlab-org/gitlab/-/blob/016430f6751b0c34abb24f74608c80a1a8268f20/config/feature_flags/ops/gitlab_experiment.yml#L8) to be honored.
+
+## Notes on feature flags
+
+NOTE:
+We use the terms "enabled" and "disabled" here, even though it's against our
+[documentation style guide recommendations](../documentation/styleguide/word_list.md#enable)
+because these are the terms that the feature flag documentation uses.
+
+You may already be familiar with the concept of feature flags in GitLab, but using
+feature flags in experiments is a bit different. While in general terms, a feature flag
+is viewed as being either `on` or `off`, this isn't accurate for experiments.
+
+Generally, `off` means that when we ask if a feature flag is enabled, it will always
+return `false`, and `on` means that it will always return `true`. An interim state,
+considered `conditional`, also exists. We take advantage of this trinary state of
+feature flags. To understand this `conditional` aspect: consider that either of these
+settings puts a feature flag into this state:
+
+- Setting a `percentage_of_actors` of any percent greater than 0%.
+- Enabling it for a single user or group.
+
+Conditional means that it returns `true` in some situations, but not all situations.
+
+When a feature flag is disabled (meaning the state is `off`), the experiment is
+considered _inactive_. You can visualize this in the [decision tree diagram](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment#how-it-works)
+as reaching the first `Running?` node, and traversing the negative path.
+
+When a feature flag is rolled out to a `percentage_of_actors` or similar (meaning the
+state is `conditional`) the experiment is considered to be _running_
+where sometimes the control is assigned, and sometimes the candidate is assigned.
+We don't refer to this as being enabled, because that's a confusing and overloaded
+term here. In the experiment terms, our experiment is _running_, and the feature flag is
+`conditional`.
+
+When a feature flag is enabled (meaning the state is `on`), the candidate will always be
+assigned.
+
+We should try to be consistent with our terms, and so for experiments, we have an
+_inactive_ experiment until we set the feature flag to `conditional`. After which,
+our experiment is then considered _running_. If you choose to "enable" your feature flag,
+you should consider the experiment to be _resolved_, because everyone is assigned
+the candidate unless they've opted out of experimentation.
+
+As of GitLab 13.10, work is being done to improve this process and how we communicate
+about it.
diff --git a/doc/development/experiment_guide/gitlab_experiment.md b/doc/development/experiment_guide/gitlab_experiment.md
index 78e1f84d701..5ddbe9b3de9 100644
--- a/doc/development/experiment_guide/gitlab_experiment.md
+++ b/doc/development/experiment_guide/gitlab_experiment.md
@@ -1,586 +1,11 @@
---
-stage: Growth
-group: Adoption
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+redirect_to: 'index.md'
+remove_date: '2022-08-05'
---
-# Implementing an A/B/n experiment
+This document was moved to [another location](index.md).
-## Introduction
-
-Experiments in GitLab are tightly coupled with the concepts provided by
-[Feature flags in development of GitLab](../feature_flags/index.md). You're strongly encouraged
-to read and understand the [Feature flags in development of GitLab](../feature_flags/index.md)
-portion of the documentation before considering running experiments. Experiments add additional
-concepts which may seem confusing or advanced without understanding the underpinnings of how GitLab
-uses feature flags in development. One concept: experiments can be run with multiple variants,
-which are sometimes referred to as A/B/n tests.
-
-We use the [`gitlab-experiment` gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment),
-sometimes referred to as GLEX, to run our experiments. The gem exists in a separate repository
-so it can be shared across any GitLab property that uses Ruby. You should feel comfortable reading
-the documentation on that project if you want to dig into more advanced topics or open issues. Be
-aware that the documentation there reflects what's in the main branch and may not be the same as
-the version being used within GitLab.
-
-## Glossary of terms
-
-To ensure a shared language, you should understand these fundamental terms we use
-when communicating about experiments:
-
-- `experiment`: Any deviation of code paths we want to run at some times, but not others.
-- `context`: A consistent experience we provide in an experiment.
-- `control`: The default, or "original" code path.
-- `candidate`: Defines an experiment with only one code path.
-- `variant(s)`: Defines an experiment with multiple code paths.
-- `behaviors`: Used to reference all possible code paths of an experiment, including the control.
-
-## Implementing an experiment
-
-[Examples](https://gitlab.com/gitlab-org/growth/growth/-/wikis/GLEX-Framework-code-examples)
-
-Start by generating a feature flag using the `bin/feature-flag` command as you
-normally would for a development feature flag, making sure to use `experiment` for
-the type. For the sake of documentation let's name our feature flag (and experiment)
-"pill_color".
-
-```shell
-bin/feature-flag pill_color -t experiment
-```
-
-After you generate the desired feature flag, you can immediately implement an
-experiment in code. An experiment implementation can be as simple as:
-
-```ruby
-experiment(:pill_color, actor: current_user) do |e|
- e.control { 'control' }
- e.variant(:red) { 'red' }
- e.variant(:blue) { 'blue' }
-end
-```
-
-When this code executes, the experiment is run, a variant is assigned, and (if within a
-controller or view) a `window.gl.experiments.pill_color` object will be available in the
-client layer, with details like:
-
-- The assigned variant.
-- The context key for client tracking events.
-
-In addition, when an experiment runs, an event is tracked for
-the experiment `:assignment`. We cover more about events, tracking, and
-the client layer later.
-
-In local development, you can make the experiment active by using the feature flag
-interface. You can also target specific cases by providing the relevant experiment
-to the call to enable the feature flag:
-
-```ruby
-# Enable for everyone
-Feature.enable(:pill_color)
-
-# Get the `experiment` method -- already available in controllers, views, and mailers.
-include Gitlab::Experiment::Dsl
-# Enable for only the first user
-Feature.enable(:pill_color, experiment(:pill_color, actor: User.first))
-```
-
-To roll out your experiment feature flag on an environment, run
-the following command using ChatOps (which is covered in more depth in the
-[Feature flags in development of GitLab](../feature_flags/index.md) documentation).
-This command creates a scenario where half of everyone who encounters
-the experiment would be assigned the _control_, 25% would be assigned the _red_
-variant, and 25% would be assigned the _blue_ variant:
-
-```slack
-/chatops run feature set pill_color 50 --actors
-```
-
-For an even distribution in this example, change the command to set it to 66% instead
-of 50.
-
-NOTE:
-To immediately stop running an experiment, use the
-`/chatops run feature set pill_color false` command.
-
-WARNING:
-We strongly recommend using the `--actors` flag when using the ChatOps commands,
-as anything else may give odd behaviors due to how the caching of variant assignment is
-handled.
-
-We can also implement this experiment in a HAML file with HTML wrappings:
-
-```haml
-#cta-interface
- - experiment(:pill_color, actor: current_user) do |e|
- - e.control do
- .pill-button control
- - e.variant(:red) do
- .pill-button.red red
- - e.variant(:blue) do
- .pill-button.blue blue
-```
-
-### The importance of context
-
-In our previous example experiment, our context (this is an important term) is a hash
-that's set to `{ actor: current_user }`. Context must be unique based on how you
-want to run your experiment, and should be understood at a lower level.
-
-It's expected, and recommended, that you use some of these
-contexts to simplify reporting:
-
-- `{ actor: current_user }`: Assigns a variant and is "sticky" to each user
- (or "client" if `current_user` is nil) who enters the experiment.
-- `{ project: project }`: Assigns a variant and is "sticky" to the project currently
- being viewed. If running your experiment is more useful when viewing a project,
- rather than when a specific user is viewing any project, consider this approach.
-- `{ group: group }`: Similar to the project example, but applies to a wider
- scope of projects and users.
-- `{ actor: current_user, project: project }`: Assigns a variant and is "sticky"
- to the user who is viewing the given project. This creates a different variant
- assignment possibility for every project that `current_user` views. Understand this
- can create a large cache size if an experiment like this in a highly trafficked part
- of the application.
-- `{ wday: Time.current.wday }`: Assigns a variant based on the current day of the
- week. In this example, it would consistently assign one variant on Friday, and a
- potentially different variant on Saturday.
-
-Context is critical to how you define and report on your experiment. It's usually
-the most important aspect of how you choose to implement your experiment, so consider
-it carefully, and discuss it with the wider team if needed. Also, take into account
-that the context you choose affects our cache size.
-
-After the above examples, we can state the general case: *given a specific
-and consistent context, we can provide a consistent experience and track events for
-that experience.* To dive a bit deeper into the implementation details: a context key
-is generated from the context that's provided. Use this context key to:
-
-- Determine the assigned variant.
-- Identify events tracked against that context key.
-
-We can think about this as the experience that we've rendered, which is both dictated
-and tracked by the context key. The context key is used to track the interaction and
-results of the experience we've rendered to that context key. These concepts are
-somewhat abstract and hard to understand initially, but this approach enables us to
-communicate about experiments as something that's wider than just user behavior.
-
-NOTE:
-Using `actor:` utilizes cookies if the `current_user` is nil. If you don't need
-cookies though - meaning that the exposed functionality would only be visible to
-signed in users - `{ user: current_user }` would be just as effective.
-
-WARNING:
-The caching of variant assignment is done by using this context, and so consider
-your impact on the cache size when defining your experiment. If you use
-`{ time: Time.current }` you would be inflating the cache size every time the
-experiment is run. Not only that, your experiment would not be "sticky" and events
-wouldn't be resolvable.
-
-### Advanced experimentation
-
-There are two ways to implement an experiment:
-
-1. The simple experiment style described previously.
-1. A more advanced style where an experiment class is provided.
-
-The advanced style is handled by naming convention, and works similar to what you
-would expect in Rails.
-
-To generate a custom experiment class that can override the defaults in
-`ApplicationExperiment` use the Rails generator:
-
-```shell
-rails generate gitlab:experiment pill_color control red blue
-```
-
-This generates an experiment class in `app/experiments/pill_color_experiment.rb`
-with the _behaviors_ we've provided to the generator. Here's an example
-of how that class would look after migrating our previous example into it:
-
-```ruby
-class PillColorExperiment < ApplicationExperiment
- control { 'control' }
- variant(:red) { 'red' }
- variant(:blue) { 'blue' }
-end
-```
-
-We can now simplify where we run our experiment to the following call, instead of
-providing the block we were initially providing, by explicitly calling `run`:
-
-```ruby
-experiment(:pill_color, actor: current_user).run
-```
-
-The _behaviors_ we defined in our experiment class represent the default
-implementation. You can still use the block syntax to override these _behaviors_
-however, so the following would also be valid:
-
-```ruby
-experiment(:pill_color, actor: current_user) do |e|
- e.control { '<strong>control</strong>' }
-end
-```
-
-NOTE:
-When passing a block to the `experiment` method, it is implicitly invoked as
-if `run` has been called.
-
-#### Segmentation rules
-
-You can use runtime segmentation rules to, for instance, segment contexts into a specific
-variant. The `segment` method is a callback (like `before_action`) and so allows providing
-a block or method name.
-
-In this example, any user named `'Richard'` would always be assigned the _red_
-variant, and any account older than 2 weeks old would be assigned the _blue_ variant:
-
-```ruby
-class PillColorExperiment < ApplicationExperiment
- # ...registered behaviors
-
- segment(variant: :red) { context.actor.first_name == 'Richard' }
- segment :old_account?, variant: :blue
-
- private
-
- def old_account?
- context.actor.created_at < 2.weeks.ago
- end
-end
-```
-
-When an experiment runs, the segmentation rules are executed in the order they're
-defined. The first segmentation rule to produce a truthy result assigns the variant.
-
-In our example, any user named `'Richard'`, regardless of account age, will always
-be assigned the _red_ variant. If you want the opposite logic, flip the order.
-
-NOTE:
-Keep in mind when defining segmentation rules: after a truthy result, the remaining
-segmentation rules are skipped to achieve optimal performance.
-
-#### Exclusion rules
-
-Exclusion rules are similar to segmentation rules, but are intended to determine
-if a context should even be considered as something we should include in the experiment
-and track events toward. Exclusion means we don't care about the events in relation
-to the given context.
-
-These examples exclude all users named `'Richard'`, *and* any account
-older than 2 weeks old. Not only are they given the control behavior - which could
-be nothing - but no events are tracked in these cases as well.
-
-```ruby
-class PillColorExperiment < ApplicationExperiment
- # ...registered behaviors
-
- exclude :old_account?, ->{ context.actor.first_name == 'Richard' }
-
- private
-
- def old_account?
- context.actor.created_at < 2.weeks.ago
- end
-end
-```
-
-You may also need to check exclusion in custom tracking logic by calling `should_track?`:
-
-```ruby
-class PillColorExperiment < ApplicationExperiment
- # ...registered behaviors
-
- def expensive_tracking_logic
- return unless should_track?
-
- track(:my_event, value: expensive_method_call)
- end
-end
-```
-
-### Tracking events
-
-One of the most important aspects of experiments is gathering data and reporting on
-it. You can use the `track` method to track events across an experimental implementation.
-You can track events consistently to an experiment if you provide the same context between
-calls to your experiment. If you do not yet understand context, you should read
-about contexts now.
-
-We can assume we run the experiment in one or a few places, but
-track events potentially in many places. The tracking call remains the same, with
-the arguments you would normally use when
-[tracking events using snowplow](../snowplow/index.md). The easiest example
-of tracking an event in Ruby would be:
-
-```ruby
-experiment(:pill_color, actor: current_user).track(:clicked)
-```
-
-When you run an experiment with any of the examples so far, an `:assignment` event
-is tracked automatically by default. All events that are tracked from an
-experiment have a special
-[experiment context](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-3)
-added to the event. This can be used - typically by the data team - to create a connection
-between the events on a given experiment.
-
-If our current user hasn't encountered the experiment yet (meaning where the experiment
-is run), and we track an event for them, they are assigned a variant and see
-that variant if they ever encountered the experiment later, when an `:assignment`
-event would be tracked at that time for them.
-
-NOTE:
-GitLab tries to be sensitive and respectful of our customers regarding tracking,
-so our experimentation library allows us to implement an experiment without ever tracking identifying
-IDs. It's not always possible, though, based on experiment reporting requirements.
-You may be asked from time to time to track a specific record ID in experiments.
-The approach is largely up to the PM and engineer creating the implementation.
-No recommendations are provided here at this time.
-
-## Testing with RSpec
-
-In the course of working with experiments, you'll probably want to utilize the RSpec
-tooling that's built in. This happens automatically for files in `spec/experiments`, but
-for other files and specs you want to include it in, you can specify the `:experiment` type:
-
-```ruby
-it "tests experiments nicely", :experiment do
-end
-```
-
-### Stub helpers
-
-You can stub experiments using `stub_experiments`. Pass it a hash using experiment
-names as the keys, and the variants you want each to resolve to, as the values:
-
-```ruby
-# Ensures the experiments named `:example` & `:example2` are both "enabled" and
-# that each will resolve to the given variant (`:my_variant` and `:control`
-# respectively).
-stub_experiments(example: :my_variant, example2: :control)
-
-experiment(:example) do |e|
- e.enabled? # => true
- e.assigned.name # => 'my_variant'
-end
-
-experiment(:example2) do |e|
- e.enabled? # => true
- e.assigned.name # => 'control'
-end
-```
-
-### Exclusion, segmentation, and behavior matchers
-
-You can also test things like the registered behaviors, the exclusions, and
-segmentations using the matchers.
-
-```ruby
-class ExampleExperiment < ApplicationExperiment
- control { }
- candidate { '_candidate_' }
-
- exclude { context.actor.first_name == 'Richard' }
- segment(variant: :candidate) { context.actor.username == 'jejacks0n' }
-end
-
-excluded = double(username: 'rdiggitty', first_name: 'Richard')
-segmented = double(username: 'jejacks0n', first_name: 'Jeremy')
-
-# register_behavior matcher
-expect(experiment(:example)).to register_behavior(:control)
-expect(experiment(:example)).to register_behavior(:candidate).with('_candidate_')
-
-# exclude matcher
-expect(experiment(:example)).to exclude(actor: excluded)
-expect(experiment(:example)).not_to exclude(actor: segmented)
-
-# segment matcher
-expect(experiment(:example)).to segment(actor: segmented).into(:candidate)
-expect(experiment(:example)).not_to segment(actor: excluded)
-```
-
-### Tracking matcher
-
-Tracking events is a major aspect of experimentation. We try
-to provide a flexible way to ensure your tracking calls are covered.
-
-You can do this on the instance level or at an "any instance" level:
-
-```ruby
-subject = experiment(:example)
-
-expect(subject).to track(:my_event)
-
-subject.track(:my_event)
-```
-
-You can use the `on_next_instance` chain method to specify that it will happen
-on the next instance of the experiment. This helps you if you're calling
-`experiment(:example).track` downstream:
-
-```ruby
-expect(experiment(:example)).to track(:my_event).on_next_instance
-
-experiment(:example).track(:my_event)
-```
-
-A full example of the methods you can chain onto the `track` matcher:
-
-```ruby
-expect(experiment(:example)).to track(:my_event, value: 1, property: '_property_')
- .on_next_instance
- .with_context(foo: :bar)
- .for(:variant_name)
-
-experiment(:example, :variant_name, foo: :bar).track(:my_event, value: 1, property: '_property_')
-```
-
-## Experiments in the client layer
-
-Any experiment that's been run in the request lifecycle surfaces in `window.gl.experiments`,
-and matches [this schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-3)
-so it can be used when resolving experimentation in the client layer.
-
-Given that we've defined a class for our experiment, and have defined the variants for it, we can publish that experiment in a couple ways.
-
-The first way is simply by running the experiment. Assuming the experiment has been run, it will surface in the client layer without having to do anything special.
-
-The second way doesn't run the experiment and is intended to be used if the experiment only needs to surface in the client layer. To accomplish this we can simply `.publish` the experiment. This won't run any logic, but does surface the experiment details in the client layer so they can be utilized there.
-
-An example might be to publish an experiment in a `before_action` in a controller. Assuming we've defined the `PillColorExperiment` class, like we have above, we can surface it to the client by publishing it instead of running it:
-
-```ruby
-before_action -> { experiment(:pill_color).publish }, only: [:show]
-```
-
-You can then see this surface in the JavaScript console:
-
-```javascript
-window.gl.experiments // => { pill_color: { excluded: false, experiment: "pill_color", key: "ca63ac02", variant: "candidate" } }
-```
-
-### Using experiments in Vue
-
-With the `gitlab-experiment` component, you can define slots that match the name of the
-variants pushed to `window.gl.experiments`.
-
-We can make use of the named slots in the Vue component, that match the behaviors defined in :
-
-```vue
-<script>
-import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
-
-export default {
- components: { GitlabExperiment }
-}
-</script>
-
-<template>
- <gitlab-experiment name="pill_color">
- <template #control>
- <button class="bg-default">Click default button</button>
- </template>
-
- <template #red>
- <button class="bg-red">Click red button</button>
- </template>
-
- <template #blue>
- <button class="bg-blue">Click blue button</button>
- </template>
- </gitlab-experiment>
-</template>
-```
-
-NOTE:
-When there is no experiment data in the `window.gl.experiments` object for the given experiment name, the `control` slot will be used, if it exists.
-
-## Test with Jest
-
-### Stub Helpers
-
-You can stub experiments using the `stubExperiments` helper defined in `spec/frontend/__helpers__/experimentation_helper.js`.
-
-```javascript
-import { stubExperiments } from 'helpers/experimentation_helper';
-import { getExperimentData } from '~/experimentation/utils';
-
-describe('when my_experiment is enabled', () => {
- beforeEach(() => {
- stubExperiments({ my_experiment: 'candidate' });
- });
-
- it('sets the correct data', () => {
- expect(getExperimentData('my_experiment')).toEqual({ experiment: 'my_experiment', variant: 'candidate' });
- });
-});
-```
-
-NOTE:
-This method of stubbing in Jest specs will not automatically un-stub itself at the end of the test. We merge our stubbed experiment in with all the other global data in `window.gl`. If you need to remove the stubbed experiment(s) after your test or ensure a clean global object before your test, you'll need to manage the global object directly yourself:
-
-```javascript
-describe('tests that care about global state', () => {
- const originalObjects = [];
-
- beforeEach(() => {
- // For backwards compatibility for now, we're using both window.gon & window.gl
- originalObjects.push(window.gon, window.gl);
- });
-
- afterEach(() => {
- [window.gon, window.gl] = originalObjects;
- });
-
- it('stubs experiment in fresh global state', () => {
- stubExperiment({ my_experiment: 'candidate' });
- // ...
- });
-})
-```
-
-## Notes on feature flags
-
-NOTE:
-We use the terms "enabled" and "disabled" here, even though it's against our
-[documentation style guide recommendations](../documentation/styleguide/word_list.md#enable)
-because these are the terms that the feature flag documentation uses.
-
-You may already be familiar with the concept of feature flags in GitLab, but using
-feature flags in experiments is a bit different. While in general terms, a feature flag
-is viewed as being either `on` or `off`, this isn't accurate for experiments.
-
-Generally, `off` means that when we ask if a feature flag is enabled, it will always
-return `false`, and `on` means that it will always return `true`. An interim state,
-considered `conditional`, also exists. We take advantage of this trinary state of
-feature flags. To understand this `conditional` aspect: consider that either of these
-settings puts a feature flag into this state:
-
-- Setting a `percentage_of_actors` of any percent greater than 0%.
-- Enabling it for a single user or group.
-
-Conditional means that it returns `true` in some situations, but not all situations.
-
-When a feature flag is disabled (meaning the state is `off`), the experiment is
-considered _inactive_. You can visualize this in the [decision tree diagram](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment#how-it-works)
-as reaching the first `Running?` node, and traversing the negative path.
-
-When a feature flag is rolled out to a `percentage_of_actors` or similar (meaning the
-state is `conditional`) the experiment is considered to be _running_
-where sometimes the control is assigned, and sometimes the candidate is assigned.
-We don't refer to this as being enabled, because that's a confusing and overloaded
-term here. In the experiment terms, our experiment is _running_, and the feature flag is
-`conditional`.
-
-When a feature flag is enabled (meaning the state is `on`), the candidate will always be
-assigned.
-
-We should try to be consistent with our terms, and so for experiments, we have an
-_inactive_ experiment until we set the feature flag to `conditional`. After which,
-our experiment is then considered _running_. If you choose to "enable" your feature flag,
-you should consider the experiment to be _resolved_, because everyone is assigned
-the candidate unless they've opted out of experimentation.
-
-As of GitLab 13.10, work is being done to improve this process and how we communicate
-about it.
+<!-- This redirect file can be deleted after 2022-08-05. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/development/experiment_guide/implementing_experiments.md b/doc/development/experiment_guide/implementing_experiments.md
new file mode 100644
index 00000000000..3c33d015108
--- /dev/null
+++ b/doc/development/experiment_guide/implementing_experiments.md
@@ -0,0 +1,369 @@
+---
+stage: Growth
+group: Adoption
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Implementing an A/B/n experiment
+
+## Implementing an experiment
+
+[Examples](https://gitlab.com/gitlab-org/growth/growth/-/wikis/GLEX-Framework-code-examples)
+
+Start by generating a feature flag using the `bin/feature-flag` command as you
+normally would for a development feature flag, making sure to use `experiment` for
+the type. For the sake of documentation let's name our feature flag (and experiment)
+"pill_color".
+
+```shell
+bin/feature-flag pill_color -t experiment
+```
+
+After you generate the desired feature flag, you can immediately implement an
+experiment in code. An experiment implementation can be as simple as:
+
+```ruby
+experiment(:pill_color, actor: current_user) do |e|
+ e.control { 'control' }
+ e.variant(:red) { 'red' }
+ e.variant(:blue) { 'blue' }
+end
+```
+
+When this code executes, the experiment is run, a variant is assigned, and (if within a
+controller or view) a `window.gl.experiments.pill_color` object will be available in the
+client layer, with details like:
+
+- The assigned variant.
+- The context key for client tracking events.
+
+In addition, when an experiment runs, an event is tracked for
+the experiment `:assignment`. We cover more about events, tracking, and
+the client layer later.
+
+In local development, you can make the experiment active by using the feature flag
+interface. You can also target specific cases by providing the relevant experiment
+to the call to enable the feature flag:
+
+```ruby
+# Enable for everyone
+Feature.enable(:pill_color)
+
+# Get the `experiment` method -- already available in controllers, views, and mailers.
+include Gitlab::Experiment::Dsl
+# Enable for only the first user
+Feature.enable(:pill_color, experiment(:pill_color, actor: User.first))
+```
+
+To roll out your experiment feature flag on an environment, run
+the following command using ChatOps (which is covered in more depth in the
+[Feature flags in development of GitLab](../feature_flags/index.md) documentation).
+This command creates a scenario where half of everyone who encounters
+the experiment would be assigned the _control_, 25% would be assigned the _red_
+variant, and 25% would be assigned the _blue_ variant:
+
+```slack
+/chatops run feature set pill_color 50 --actors
+```
+
+For an even distribution in this example, change the command to set it to 66% instead
+of 50.
+
+NOTE:
+To immediately stop running an experiment, use the
+`/chatops run feature set pill_color false` command.
+
+WARNING:
+We strongly recommend using the `--actors` flag when using the ChatOps commands,
+as anything else may give odd behaviors due to how the caching of variant assignment is
+handled.
+
+We can also implement this experiment in a HAML file with HTML wrappings:
+
+```haml
+#cta-interface
+ - experiment(:pill_color, actor: current_user) do |e|
+ - e.control do
+ .pill-button control
+ - e.variant(:red) do
+ .pill-button.red red
+ - e.variant(:blue) do
+ .pill-button.blue blue
+```
+
+### The importance of context
+
+In our previous example experiment, our context (this is an important term) is a hash
+that's set to `{ actor: current_user }`. Context must be unique based on how you
+want to run your experiment, and should be understood at a lower level.
+
+It's expected, and recommended, that you use some of these
+contexts to simplify reporting:
+
+- `{ actor: current_user }`: Assigns a variant and is "sticky" to each user
+ (or "client" if `current_user` is nil) who enters the experiment.
+- `{ project: project }`: Assigns a variant and is "sticky" to the project currently
+ being viewed. If running your experiment is more useful when viewing a project,
+ rather than when a specific user is viewing any project, consider this approach.
+- `{ group: group }`: Similar to the project example, but applies to a wider
+ scope of projects and users.
+- `{ actor: current_user, project: project }`: Assigns a variant and is "sticky"
+ to the user who is viewing the given project. This creates a different variant
+ assignment possibility for every project that `current_user` views. Understand this
+ can create a large cache size if an experiment like this in a highly trafficked part
+ of the application.
+- `{ wday: Time.current.wday }`: Assigns a variant based on the current day of the
+ week. In this example, it would consistently assign one variant on Friday, and a
+ potentially different variant on Saturday.
+
+Context is critical to how you define and report on your experiment. It's usually
+the most important aspect of how you choose to implement your experiment, so consider
+it carefully, and discuss it with the wider team if needed. Also, take into account
+that the context you choose affects our cache size.
+
+After the above examples, we can state the general case: *given a specific
+and consistent context, we can provide a consistent experience and track events for
+that experience.* To dive a bit deeper into the implementation details: a context key
+is generated from the context that's provided. Use this context key to:
+
+- Determine the assigned variant.
+- Identify events tracked against that context key.
+
+We can think about this as the experience that we've rendered, which is both dictated
+and tracked by the context key. The context key is used to track the interaction and
+results of the experience we've rendered to that context key. These concepts are
+somewhat abstract and hard to understand initially, but this approach enables us to
+communicate about experiments as something that's wider than just user behavior.
+
+NOTE:
+Using `actor:` utilizes cookies if the `current_user` is nil. If you don't need
+cookies though - meaning that the exposed functionality would only be visible to
+signed in users - `{ user: current_user }` would be just as effective.
+
+WARNING:
+The caching of variant assignment is done by using this context, and so consider
+your impact on the cache size when defining your experiment. If you use
+`{ time: Time.current }` you would be inflating the cache size every time the
+experiment is run. Not only that, your experiment would not be "sticky" and events
+wouldn't be resolvable.
+
+### Advanced experimentation
+
+There are two ways to implement an experiment:
+
+1. The simple experiment style described previously.
+1. A more advanced style where an experiment class is provided.
+
+The advanced style is handled by naming convention, and works similar to what you
+would expect in Rails.
+
+To generate a custom experiment class that can override the defaults in
+`ApplicationExperiment` use the Rails generator:
+
+```shell
+rails generate gitlab:experiment pill_color control red blue
+```
+
+This generates an experiment class in `app/experiments/pill_color_experiment.rb`
+with the _behaviors_ we've provided to the generator. Here's an example
+of how that class would look after migrating our previous example into it:
+
+```ruby
+class PillColorExperiment < ApplicationExperiment
+ control { 'control' }
+ variant(:red) { 'red' }
+ variant(:blue) { 'blue' }
+end
+```
+
+We can now simplify where we run our experiment to the following call, instead of
+providing the block we were initially providing, by explicitly calling `run`:
+
+```ruby
+experiment(:pill_color, actor: current_user).run
+```
+
+The _behaviors_ we defined in our experiment class represent the default
+implementation. You can still use the block syntax to override these _behaviors_
+however, so the following would also be valid:
+
+```ruby
+experiment(:pill_color, actor: current_user) do |e|
+ e.control { '<strong>control</strong>' }
+end
+```
+
+NOTE:
+When passing a block to the `experiment` method, it is implicitly invoked as
+if `run` has been called.
+
+#### Segmentation rules
+
+You can use runtime segmentation rules to, for instance, segment contexts into a specific
+variant. The `segment` method is a callback (like `before_action`) and so allows providing
+a block or method name.
+
+In this example, any user named `'Richard'` would always be assigned the _red_
+variant, and any account older than 2 weeks old would be assigned the _blue_ variant:
+
+```ruby
+class PillColorExperiment < ApplicationExperiment
+ # ...registered behaviors
+
+ segment(variant: :red) { context.actor.first_name == 'Richard' }
+ segment :old_account?, variant: :blue
+
+ private
+
+ def old_account?
+ context.actor.created_at < 2.weeks.ago
+ end
+end
+```
+
+When an experiment runs, the segmentation rules are executed in the order they're
+defined. The first segmentation rule to produce a truthy result assigns the variant.
+
+In our example, any user named `'Richard'`, regardless of account age, will always
+be assigned the _red_ variant. If you want the opposite logic, flip the order.
+
+NOTE:
+Keep in mind when defining segmentation rules: after a truthy result, the remaining
+segmentation rules are skipped to achieve optimal performance.
+
+#### Exclusion rules
+
+Exclusion rules are similar to segmentation rules, but are intended to determine
+if a context should even be considered as something we should include in the experiment
+and track events toward. Exclusion means we don't care about the events in relation
+to the given context.
+
+These examples exclude all users named `'Richard'`, *and* any account
+older than 2 weeks old. Not only are they given the control behavior - which could
+be nothing - but no events are tracked in these cases as well.
+
+```ruby
+class PillColorExperiment < ApplicationExperiment
+ # ...registered behaviors
+
+ exclude :old_account?, ->{ context.actor.first_name == 'Richard' }
+
+ private
+
+ def old_account?
+ context.actor.created_at < 2.weeks.ago
+ end
+end
+```
+
+You may also need to check exclusion in custom tracking logic by calling `should_track?`:
+
+```ruby
+class PillColorExperiment < ApplicationExperiment
+ # ...registered behaviors
+
+ def expensive_tracking_logic
+ return unless should_track?
+
+ track(:my_event, value: expensive_method_call)
+ end
+end
+```
+
+### Tracking events
+
+One of the most important aspects of experiments is gathering data and reporting on
+it. You can use the `track` method to track events across an experimental implementation.
+You can track events consistently to an experiment if you provide the same context between
+calls to your experiment. If you do not yet understand context, you should read
+about contexts now.
+
+We can assume we run the experiment in one or a few places, but
+track events potentially in many places. The tracking call remains the same, with
+the arguments you would normally use when
+[tracking events using snowplow](../snowplow/index.md). The easiest example
+of tracking an event in Ruby would be:
+
+```ruby
+experiment(:pill_color, actor: current_user).track(:clicked)
+```
+
+When you run an experiment with any of the examples so far, an `:assignment` event
+is tracked automatically by default. All events that are tracked from an
+experiment have a special
+[experiment context](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-3)
+added to the event. This can be used - typically by the data team - to create a connection
+between the events on a given experiment.
+
+If our current user hasn't encountered the experiment yet (meaning where the experiment
+is run), and we track an event for them, they are assigned a variant and see
+that variant if they ever encountered the experiment later, when an `:assignment`
+event would be tracked at that time for them.
+
+NOTE:
+GitLab tries to be sensitive and respectful of our customers regarding tracking,
+so our experimentation library allows us to implement an experiment without ever tracking identifying
+IDs. It's not always possible, though, based on experiment reporting requirements.
+You may be asked from time to time to track a specific record ID in experiments.
+The approach is largely up to the PM and engineer creating the implementation.
+No recommendations are provided here at this time.
+
+## Experiments in the client layer
+
+Any experiment that's been run in the request lifecycle surfaces in `window.gl.experiments`,
+and matches [this schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-3)
+so it can be used when resolving experimentation in the client layer.
+
+Given that we've defined a class for our experiment, and have defined the variants for it, we can publish that experiment in a couple ways.
+
+The first way is simply by running the experiment. Assuming the experiment has been run, it will surface in the client layer without having to do anything special.
+
+The second way doesn't run the experiment and is intended to be used if the experiment only needs to surface in the client layer. To accomplish this we can simply `.publish` the experiment. This won't run any logic, but does surface the experiment details in the client layer so they can be utilized there.
+
+An example might be to publish an experiment in a `before_action` in a controller. Assuming we've defined the `PillColorExperiment` class, like we have above, we can surface it to the client by publishing it instead of running it:
+
+```ruby
+before_action -> { experiment(:pill_color).publish }, only: [:show]
+```
+
+You can then see this surface in the JavaScript console:
+
+```javascript
+window.gl.experiments // => { pill_color: { excluded: false, experiment: "pill_color", key: "ca63ac02", variant: "candidate" } }
+```
+
+### Using experiments in Vue
+
+With the `gitlab-experiment` component, you can define slots that match the name of the
+variants pushed to `window.gl.experiments`.
+
+We can make use of the named slots in the Vue component, that match the behaviors defined in :
+
+```vue
+<script>
+import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
+
+export default {
+ components: { GitlabExperiment }
+}
+</script>
+
+<template>
+ <gitlab-experiment name="pill_color">
+ <template #control>
+ <button class="bg-default">Click default button</button>
+ </template>
+
+ <template #red>
+ <button class="bg-red">Click red button</button>
+ </template>
+
+ <template #blue>
+ <button class="bg-blue">Click blue button</button>
+ </template>
+ </gitlab-experiment>
+</template>
+```
+
+NOTE:
+When there is no experiment data in the `window.gl.experiments` object for the given experiment name, the `control` slot will be used, if it exists.
diff --git a/doc/development/experiment_guide/index.md b/doc/development/experiment_guide/index.md
index f7af1113b6e..b140cce34fc 100644
--- a/doc/development/experiment_guide/index.md
+++ b/doc/development/experiment_guide/index.md
@@ -6,47 +6,46 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Experiment Guide
-Experiments can be conducted by any GitLab team, most often the teams from the [Growth Sub-department](https://about.gitlab.com/handbook/engineering/development/growth/). Experiments are not tied to releases because they primarily target GitLab.com.
-
-Experiments are run as an A/B/n test, and are behind an [experiment feature flag](../feature_flags/#experiment-type) to turn the test on or off. Based on the data the experiment generates, the team decides if the experiment had a positive impact and should be made the new default, or rolled back.
-
-## Experiment rollout issue
-
-Each experiment should have an [experiment rollout](https://gitlab.com/groups/gitlab-org/-/boards/1352542) issue to track the experiment from rollout through to cleanup and removal.
-The rollout issue is similar to a feature flag rollout issue, and is also used to track the status of an experiment.
-When an experiment is deployed, the due date of the issue should be set (this depends on the experiment but can be up to a few weeks in the future).
-After the deadline, the issue needs to be resolved and either:
-
-- It was successful and the experiment becomes the new default.
-- It was not successful and all code related to the experiment is removed.
-
-In either case, an outcome of the experiment should be posted to the issue with the reasoning for the decision.
-
-## Code reviews
-
-Experiments' code quality can fail our standards for several reasons. These
-reasons can include not being added to the codebase for a long time, or because
-of fast iteration to retrieve data. However, having the experiment run (or not
-run) shouldn't impact GitLab availability. To avoid or identify issues,
-experiments are initially deployed to a small number of users. Regardless,
-experiments still need tests.
-
-Experiments must have corresponding [frontend or feature tests](../testing_guide/index.md) to ensure they
-exist in the application. These tests should help prevent the experiment code from
-being removed before the [experiment cleanup process](https://about.gitlab.com/handbook/engineering/development/growth/experimentation/#experiment-cleanup-issue) starts.
-
-If, as a reviewer or maintainer, you find code that would usually fail review
-but is acceptable for now, mention your concerns with a note that there's no
-need to change the code. The author can then add a comment to this piece of code
-and link to the issue that resolves the experiment. The author or reviewer can add a link to this concern in the
-experiment rollout issue under the `Experiment Successful Cleanup Concerns` section of the description.
-If the experiment is successful and becomes part of the product, any items that appear under this section will be addressed.
+Experiments can be conducted by any GitLab team, most often the teams from the
+[Growth Sub-department](https://about.gitlab.com/handbook/engineering/development/growth/).
+Experiments are not tied to releases because they primarily target GitLab.com.
+
+Experiments are run as an A/B/n test, and are behind an [experiment feature flag](../feature_flags/#experiment-type)
+to turn the test on or off. Based on the data the experiment generates, the team decides
+if the experiment had a positive impact and should be made the new default, or rolled back.
+
+Experiments in GitLab are tightly coupled with the concepts provided by
+[Feature flags in development of GitLab](../feature_flags/index.md). You're strongly encouraged
+to read and understand the [Feature flags in development of GitLab](../feature_flags/index.md)
+portion of the documentation before considering running experiments. Experiments add additional
+concepts which may seem confusing or advanced without understanding the underpinnings of how GitLab
+uses feature flags in development. One concept: experiments can be run with multiple variants,
+which are sometimes referred to as A/B/n tests.
+
+We use the [`gitlab-experiment` gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment),
+sometimes referred to as GLEX, to run our experiments. The gem exists in a separate repository
+so it can be shared across any GitLab property that uses Ruby. You should feel comfortable reading
+the documentation on that project if you want to dig into more advanced topics or open issues. Be
+aware that the documentation there reflects what's in the main branch and may not be the same as
+the version being used within GitLab.
+
+## Glossary of terms
+
+To ensure a shared language, you should understand these fundamental terms we use
+when communicating about experiments:
+
+- `experiment`: Any deviation of code paths we want to run at some times, but not others.
+- `context`: A consistent experience we provide in an experiment.
+- `control`: The default, or "original" code path.
+- `candidate`: Defines an experiment with only one code path.
+- `variant(s)`: Defines an experiment with multiple code paths.
+- `behaviors`: Used to reference all possible code paths of an experiment, including the control.
## Implementing an experiment
[`GLEX`](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment) - or `Gitlab::Experiment`, the `gitlab-experiment` gem - is the preferred option for implementing an experiment in GitLab.
-For more information, see [Implementing an A/B/n experiment using GLEX](gitlab_experiment.md).
+For more information, see [Implementing an A/B/n experiment using GLEX](implementing_experiments.md).
This uses [experiment](../feature_flags/index.md#experiment-type) feature flags.
@@ -64,15 +63,3 @@ We recommend the following workflow:
1. **If the experiment is a success**, designers add the new icon or illustration to the Pajamas UI kit as part of the cleanup process.
Engineers can then add it to the [SVG library](https://gitlab-org.gitlab.io/gitlab-svgs/) and modify the implementation based on the
[Frontend Development Guidelines](../fe_guide/icons.md#usage-in-hamlrails-2).
-
-## Turn off all experiments
-
-When there is a case on GitLab.com (SaaS) that necessitates turning off all experiments, we have this control.
-
-You can toggle experiments on SaaS on and off using the `gitlab_experiment` [feature flag](../feature_flags).
-
-This can be done via chatops:
-
-- [disable](../feature_flags/controls.md#disabling-feature-flags): `/chatops run feature set gitlab_experiment false`
-- [enable](../feature_flags/controls.md#process): `/chatops run feature delete gitlab_experiment`
- - This allows the `default_enabled` [value of true in the yml](https://gitlab.com/gitlab-org/gitlab/-/blob/016430f6751b0c34abb24f74608c80a1a8268f20/config/feature_flags/ops/gitlab_experiment.yml#L8) to be honored.
diff --git a/doc/development/experiment_guide/testing_experiments.md b/doc/development/experiment_guide/testing_experiments.md
new file mode 100644
index 00000000000..c51c3c7cb11
--- /dev/null
+++ b/doc/development/experiment_guide/testing_experiments.md
@@ -0,0 +1,150 @@
+---
+stage: Growth
+group: Activation
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Testing experiments
+
+## Testing experiments with RSpec
+
+In the course of working with experiments, you'll probably want to utilize the RSpec
+tooling that's built in. This happens automatically for files in `spec/experiments`, but
+for other files and specs you want to include it in, you can specify the `:experiment` type:
+
+```ruby
+it "tests experiments nicely", :experiment do
+end
+```
+
+### Stub helpers
+
+You can stub experiments using `stub_experiments`. Pass it a hash using experiment
+names as the keys, and the variants you want each to resolve to, as the values:
+
+```ruby
+# Ensures the experiments named `:example` & `:example2` are both "enabled" and
+# that each will resolve to the given variant (`:my_variant` and `:control`
+# respectively).
+stub_experiments(example: :my_variant, example2: :control)
+
+experiment(:example) do |e|
+ e.enabled? # => true
+ e.assigned.name # => 'my_variant'
+end
+
+experiment(:example2) do |e|
+ e.enabled? # => true
+ e.assigned.name # => 'control'
+end
+```
+
+### Exclusion, segmentation, and behavior matchers
+
+You can also test things like the registered behaviors, the exclusions, and
+segmentations using the matchers.
+
+```ruby
+class ExampleExperiment < ApplicationExperiment
+ control { }
+ candidate { '_candidate_' }
+
+ exclude { context.actor.first_name == 'Richard' }
+ segment(variant: :candidate) { context.actor.username == 'jejacks0n' }
+end
+
+excluded = double(username: 'rdiggitty', first_name: 'Richard')
+segmented = double(username: 'jejacks0n', first_name: 'Jeremy')
+
+# register_behavior matcher
+expect(experiment(:example)).to register_behavior(:control)
+expect(experiment(:example)).to register_behavior(:candidate).with('_candidate_')
+
+# exclude matcher
+expect(experiment(:example)).to exclude(actor: excluded)
+expect(experiment(:example)).not_to exclude(actor: segmented)
+
+# segment matcher
+expect(experiment(:example)).to segment(actor: segmented).into(:candidate)
+expect(experiment(:example)).not_to segment(actor: excluded)
+```
+
+### Tracking matcher
+
+Tracking events is a major aspect of experimentation. We try
+to provide a flexible way to ensure your tracking calls are covered.
+
+You can do this on the instance level or at an "any instance" level:
+
+```ruby
+subject = experiment(:example)
+
+expect(subject).to track(:my_event)
+
+subject.track(:my_event)
+```
+
+You can use the `on_next_instance` chain method to specify that it will happen
+on the next instance of the experiment. This helps you if you're calling
+`experiment(:example).track` downstream:
+
+```ruby
+expect(experiment(:example)).to track(:my_event).on_next_instance
+
+experiment(:example).track(:my_event)
+```
+
+A full example of the methods you can chain onto the `track` matcher:
+
+```ruby
+expect(experiment(:example)).to track(:my_event, value: 1, property: '_property_')
+ .on_next_instance
+ .with_context(foo: :bar)
+ .for(:variant_name)
+
+experiment(:example, :variant_name, foo: :bar).track(:my_event, value: 1, property: '_property_')
+```
+
+## Test with Jest
+
+### Stub Helpers
+
+You can stub experiments using the `stubExperiments` helper defined in `spec/frontend/__helpers__/experimentation_helper.js`.
+
+```javascript
+import { stubExperiments } from 'helpers/experimentation_helper';
+import { getExperimentData } from '~/experimentation/utils';
+
+describe('when my_experiment is enabled', () => {
+ beforeEach(() => {
+ stubExperiments({ my_experiment: 'candidate' });
+ });
+
+ it('sets the correct data', () => {
+ expect(getExperimentData('my_experiment')).toEqual({ experiment: 'my_experiment', variant: 'candidate' });
+ });
+});
+```
+
+NOTE:
+This method of stubbing in Jest specs will not automatically un-stub itself at the end of the test. We merge our stubbed experiment in with all the other global data in `window.gl`. If you need to remove the stubbed experiment(s) after your test or ensure a clean global object before your test, you'll need to manage the global object directly yourself:
+
+```javascript
+describe('tests that care about global state', () => {
+ const originalObjects = [];
+
+ beforeEach(() => {
+ // For backwards compatibility for now, we're using both window.gon & window.gl
+ originalObjects.push(window.gon, window.gl);
+ });
+
+ afterEach(() => {
+ [window.gon, window.gl] = originalObjects;
+ });
+
+ it('stubs experiment in fresh global state', () => {
+ stubExperiment({ my_experiment: 'candidate' });
+ // ...
+ });
+})
+```
diff --git a/doc/update/index.md b/doc/update/index.md
index 6558a289cdc..dcda9150059 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -193,6 +193,8 @@ pending_job_classes.each { |job_class| Gitlab::BackgroundMigration.steal(job_cla
GitLab 13.6 introduced an issue where a background migration named `BackfillJiraTrackerDeploymentType2` can be permanently stuck in a **pending** state across upgrades. To clean up this stuck migration, see the [13.6.0 version-specific instructions](#1360).
+GitLab 14.2 introduced an issue where a background migration named `BackfillDraftStatusOnMergeRequests` can be permanently stuck in a **pending** state across upgrades when the instance lacks records that match the migration's target. To clean up this stuck migration, see the [14.2.0 version-specific instructions](#1420).
+
GitLab 14.4 introduced an issue where a background migration named `PopulateTopicsTotalProjectsCountCache` can be permanently stuck in a **pending** state across upgrades when the instance lacks records that match the migration's target. To clean up this stuck migration, see the [14.4.0 version-specific instructions](#1440).
GitLab 14.8 introduced an issue where a background migration named `PopulateTopicsNonPrivateProjectsCount` can be permanently stuck in a **pending** state across upgrades. To clean up this stuck migration, see the [14.8.0 version-specific instructions](#1480).
@@ -616,6 +618,17 @@ for how to proceed.
```
- See [Maintenance mode issue in GitLab 13.9 to 14.4](#maintenance-mode-issue-in-gitlab-139-to-144).
+- GitLab 14.2.0 includes a
+ [background migration `BackfillDraftStatusOnMergeRequests`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67687)
+ that may remain stuck permanently in a **pending** state when the instance lacks records that match the migration's target.
+
+ To clean up this stuck job, run the following in the [GitLab Rails Console](../administration/operations/rails_console.md):
+
+ ```ruby
+ Gitlab::Database::BackgroundMigrationJob.pending.where(class_name: "BackfillDraftStatusOnMergeRequests").find_each do |job|
+ puts Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded("BackfillDraftStatusOnMergeRequests", job.arguments)
+ end
+ ```
### 14.1.0
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 64566e458ee..006c9902859 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -217,7 +217,7 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u
| `ADDITIONAL_CA_CERT_BUNDLE` | `""` | Bundle of CA certs that you want to trust. See [Using a custom SSL CA certificate authority](#using-a-custom-ssl-ca-certificate-authority) for more details. | All |
| `CI_APPLICATION_REPOSITORY` | `$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG` | Docker repository URL for the image to be scanned. | All |
| `CI_APPLICATION_TAG` | `$CI_COMMIT_SHA` | Docker repository tag for the image to be scanned. | All |
-| `CS_ANALYZER_IMAGE` | `registry.gitlab.com/security-products/container-scanning:4` | Docker image of the analyzer. | All |
+| `CS_ANALYZER_IMAGE` | `registry.gitlab.com/security-products/container-scanning:5` | Docker image of the analyzer. | All |
| `CS_DEFAULT_BRANCH_IMAGE` | `""` | The name of the `DOCKER_IMAGE` on the default branch. See [Setting the default branch image](#setting-the-default-branch-image) for more details. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/338877) in GitLab 14.5. | All |
| `CS_DISABLE_DEPENDENCY_LIST` | `"false"` | Disable Dependency Scanning for packages installed in the scanned image. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345434) in GitLab 14.6. | All |
| `CS_DISABLE_LANGUAGE_VULNERABILITY_SCAN` | `"true"` | Disable scanning for language-specific packages installed in the scanned image. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345434) in GitLab 14.6. | All |
@@ -250,9 +250,9 @@ standard tag plus the `-fips` extension.
| Scanner name | `CS_ANALYZER_IMAGE` |
| --------------- | ------------------- |
-| Default (Trivy) | `registry.gitlab.com/security-products/container-scanning:4-fips` |
-| Grype | `registry.gitlab.com/security-products/container-scanning/grype:4-fips` |
-| Trivy | `registry.gitlab.com/security-products/container-scanning/trivy:4-fips` |
+| Default (Trivy) | `registry.gitlab.com/security-products/container-scanning:5-fips` |
+| Grype | `registry.gitlab.com/security-products/container-scanning/grype:5-fips` |
+| Trivy | `registry.gitlab.com/security-products/container-scanning/trivy:5-fips` |
NOTE:
Prior to GitLab 15.0, the `-ubi` image extension is also available. GitLab 15.0 and later only
@@ -305,9 +305,9 @@ The following options are available:
| Scanner name | `CS_ANALYZER_IMAGE` |
| ------------ | ------------------- |
-| Default ([Trivy](https://github.com/aquasecurity/trivy)) | `registry.gitlab.com/security-products/container-scanning:4` |
-| [Grype](https://github.com/anchore/grype) | `registry.gitlab.com/security-products/container-scanning/grype:4` |
-| Trivy | `registry.gitlab.com/security-products/container-scanning/trivy:4` |
+| Default ([Trivy](https://github.com/aquasecurity/trivy)) | `registry.gitlab.com/security-products/container-scanning:5` |
+| [Grype](https://github.com/anchore/grype) | `registry.gitlab.com/security-products/container-scanning/grype:5` |
+| Trivy | `registry.gitlab.com/security-products/container-scanning/trivy:5` |
If you're migrating from a GitLab 13.x release to a GitLab 14.x release and have customized the
`container_scanning` job or its CI variables, you might need to perform these migration steps in
@@ -320,7 +320,7 @@ your CI file:
- `SECURE_ANALYZERS_PREFIX`
1. Review the `CS_ANALYZER_IMAGE` variable. It no longer depends on the variables above and its new
- default value is `registry.gitlab.com/security-products/container-scanning:4`. If you have an
+ default value is `registry.gitlab.com/security-products/container-scanning:5`. If you have an
offline environment, see
[Running container scanning in an offline environment](#running-container-scanning-in-an-offline-environment).
@@ -532,9 +532,9 @@ For container scanning, import the following images from `registry.gitlab.com` i
[local Docker container registry](../../packages/container_registry/index.md):
```plaintext
-registry.gitlab.com/security-products/container-scanning:4
-registry.gitlab.com/security-products/container-scanning/grype:4
-registry.gitlab.com/security-products/container-scanning/trivy:4
+registry.gitlab.com/security-products/container-scanning:5
+registry.gitlab.com/security-products/container-scanning/grype:5
+registry.gitlab.com/security-products/container-scanning/trivy:5
```
The process for importing Docker images into a local offline Docker registry depends on
@@ -574,7 +574,7 @@ following `.gitlab-ci.yml` example as a template.
```yaml
variables:
- SOURCE_IMAGE: registry.gitlab.com/security-products/container-scanning:4
+ SOURCE_IMAGE: registry.gitlab.com/security-products/container-scanning:5
TARGET_IMAGE: $CI_REGISTRY/namespace/gitlab-container-scanning
image: docker:stable
@@ -827,6 +827,7 @@ For information on this, see the [general Application Security troubleshooting s
as the default for container scanning, and also [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326279)
an integration with [Grype](https://github.com/anchore/grype)
as an alternative scanner.
+- GitLab 15.0 changed the major analyzer version from `4` to `5`.
Other changes to the container scanning analyzer can be found in the project's
[changelog](https://gitlab.com/gitlab-org/security-products/analyzers/container-scanning/-/blob/master/CHANGELOG.md).
diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md
index 78330a38bd5..a5944314a50 100644
--- a/doc/user/group/iterations/index.md
+++ b/doc/user/group/iterations/index.md
@@ -182,7 +182,7 @@ administrator to [enable the feature flag](../../../administration/feature_flags
On GitLab.com, this feature is not available. This feature is not ready for production use.
Iteration cadences automate iteration scheduling. You can use them to
-automate creating iterations every 1, 2, 3, 4, or 6 weeks. You can also
+automate creating iterations every 1, 2, 3, or 4 weeks. You can also
configure iteration cadences to automatically roll over incomplete issues to the next iteration.
### Create an iteration cadence
@@ -198,7 +198,35 @@ To create an iteration cadence:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Issues > Iterations**.
1. Select **New iteration cadence**.
-1. Fill out required fields, and select **Create iteration cadence**. The cadence list page opens.
+1. Complete the fields.
+ - Enter the title and description of the iteration cadence.
+ - Enter the start date of the iteration cadence.
+ - From the **Duration** dropdown list, select how many weeks each iteration should last.
+ - From the **Future iterations** dropdown list, select how many future iterations should be
+ created and maintained by GitLab.
+ - Optional. To move incomplete issues to the next iteration, select **Roll over issues**.
+1. Select **Create cadence**. The cadence list page opens.
+
+### Edit an iteration cadence
+
+Prerequisites:
+
+- You must have at least the Developer role for a group.
+
+To edit an iteration cadence:
+
+1. On the top bar, select **Menu > Groups** and find your group.
+1. On the left sidebar, select **Issues > Iterations**.
+1. Select **Edit iteration cadence**.
+
+When you edit the **Duration**, **Future iterations**, or **Start date** fields,
+only future iterations are affected.
+
+You can edit the start date of a cadence if the cadence has not started yet.
+
+Editing **Future iterations** is a non-destructive action.
+If ten future iterations already exist, changing the number under **Future iterations** to `2`
+doesn't delete the eight existing future iterations.
### Delete an iteration cadence
@@ -217,18 +245,38 @@ To delete an iteration cadence:
1. Select the three-dot menu (**{ellipsis_v}**) > **Delete cadence** for the cadence you want to delete.
1. Select **Delete cadence** in the confirmation modal.
-### Convert manual cadence to use automatic scheduling
+### Manual iteration cadences
+
+When you **enable** the iteration cadences feature, all previously
+created iterations are added to a default iteration cadence.
+You can continue to add, edit, and remove iterations in
+this default cadence.
+
+#### Convert a manual cadence to use automatic scheduling
WARNING:
-The upgrade is irreversible. After it's done, manual iteration cadences cannot be created.
+The upgrade is irreversible. After it's done, a new manual iteration cadence cannot be created.
-When you **enable** the iteration cadences feature, all iterations are added
-to a default iteration cadence.
-In this default iteration cadence, you can continue to add, edit, and remove iterations.
+Prerequisites:
+- You must have created [iterations](#iterations) without cadences before enabling iteration cadences for your group.
To upgrade the iteration cadence to use the automation features:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Issues > Iterations**.
1. Select the three-dot menu (**{ellipsis_v}**) > **Edit cadence** for the cadence you want to upgrade.
-1. Fill out required fields, and select **Save changes**.
+1. Complete the required fields **Duration** and **Future iterations**.
+1. Select **Save changes**.
+
+#### Start dates of converted cadences
+
+The start date of your converted cadence is set to the start date of its
+**first** existing iteration.
+
+If you attempt to set a new start date, the conversion fails with an error message.
+If your manual cadence is empty, converting it to use automatic scheduling is effectively
+the same as creating a new automated cadence.
+
+During the conversion process GitLab does not delete or modify existing **ongoing** or
+**closed** iterations. If you have iterations with start dates in the future,
+they are updated to fit your cadence settings.
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb
index eb43ed1d37a..7163225777a 100644
--- a/lib/api/admin/instance_clusters.rb
+++ b/lib/api/admin/instance_clusters.rb
@@ -137,7 +137,7 @@ module API
end
def ensure_feature_enabled!
- not_found! unless Feature.enabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops)
+ not_found! unless clusterable_instance.certificate_based_clusters_enabled?
end
end
end
diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb
index b95d17494db..edaa32c26c4 100644
--- a/lib/api/group_clusters.rb
+++ b/lib/api/group_clusters.rb
@@ -139,7 +139,7 @@ module API
end
def ensure_feature_enabled!
- not_found! unless Feature.enabled?(:certificate_based_clusters, user_group, default_enabled: :yaml, type: :ops)
+ not_found! unless user_group.certificate_based_clusters_enabled?
end
end
end
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index 00ac255c365..4644d38ea80 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -144,7 +144,9 @@ module API
end
def ensure_feature_enabled!
- not_found! unless Feature.enabled?(:certificate_based_clusters, user_project.namespace, default_enabled: :yaml, type: :ops)
+ namespace = user_project.namespace
+
+ not_found! unless namespace.certificate_based_clusters_enabled?
end
end
end
diff --git a/lib/gitlab/background_migration/backfill_integrations_type_new.rb b/lib/gitlab/background_migration/backfill_integrations_type_new.rb
index a234cebfce5..6f33472af7d 100644
--- a/lib/gitlab/background_migration/backfill_integrations_type_new.rb
+++ b/lib/gitlab/background_migration/backfill_integrations_type_new.rb
@@ -22,7 +22,7 @@ module Gitlab
private
def connection
- ActiveRecord::Base.connection
+ ApplicationRecord.connection
end
def process_sub_batch(sub_batch)
diff --git a/lib/gitlab/background_migration/backfill_issue_search_data.rb b/lib/gitlab/background_migration/backfill_issue_search_data.rb
index ec206cbfd41..e408fd0cda6 100644
--- a/lib/gitlab/background_migration/backfill_issue_search_data.rb
+++ b/lib/gitlab/background_migration/backfill_issue_search_data.rb
@@ -9,7 +9,7 @@ module Gitlab
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, stop_id, batch_table, batch_column, sub_batch_size, pause_ms)
- define_batchable_model(batch_table, connection: ActiveRecord::Base.connection).where(batch_column => start_id..stop_id).each_batch(of: sub_batch_size) do |sub_batch|
+ define_batchable_model(batch_table, connection: ApplicationRecord.connection).where(batch_column => start_id..stop_id).each_batch(of: sub_batch_size) do |sub_batch|
update_search_data(sub_batch)
sleep(pause_ms * 0.001)
diff --git a/lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb b/lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb
index 1ed147d67c7..5f3d830c48d 100644
--- a/lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb
+++ b/lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb
@@ -26,7 +26,7 @@ module Gitlab
private
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
- define_batchable_model(source_table, connection: ActiveRecord::Base.connection)
+ define_batchable_model(source_table, connection: ApplicationRecord.connection)
.joins('INNER JOIN namespaces ON members.source_id = namespaces.id')
.where(source_key_column => start_id..stop_id)
.where(type: 'GroupMember')
diff --git a/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb b/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb
index fe3edd3322b..0585924cb7b 100644
--- a/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb
+++ b/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb
@@ -27,7 +27,7 @@ module Gitlab
private
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
- define_batchable_model(source_table, connection: ActiveRecord::Base.connection)
+ define_batchable_model(source_table, connection: ApplicationRecord.connection)
.joins('inner join namespaces on routes.source_id = namespaces.id')
.where(source_key_column => start_id..stop_id)
.where(namespace_id: nil)
diff --git a/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb b/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
index 1f0d606f001..7f75b64e98a 100644
--- a/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
+++ b/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
@@ -13,7 +13,7 @@ module Gitlab
cleanup_gin_index('routes')
batch_metrics.time_operation(:update_all) do
- ActiveRecord::Base.connection.execute <<~SQL
+ ApplicationRecord.connection.execute <<~SQL
WITH route_and_ns(route_id, project_namespace_id) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
#{sub_batch.to_sql}
)
@@ -37,15 +37,15 @@ module Gitlab
def cleanup_gin_index(table_name)
sql = "select indexname::text from pg_indexes where tablename = '#{table_name}' and indexdef ilike '%gin%'"
- index_names = ActiveRecord::Base.connection.select_values(sql)
+ index_names = ApplicationRecord.connection.select_values(sql)
index_names.each do |index_name|
- ActiveRecord::Base.connection.execute("select gin_clean_pending_list('#{index_name}')")
+ ApplicationRecord.connection.execute("select gin_clean_pending_list('#{index_name}')")
end
end
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
- define_batchable_model(source_table, connection: ActiveRecord::Base.connection)
+ define_batchable_model(source_table, connection: ApplicationRecord.connection)
.joins('INNER JOIN projects ON routes.source_id = projects.id')
.where(source_key_column => start_id..stop_id)
.where(namespace_id: nil)
diff --git a/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb b/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb
index 79e7a2f2279..587de1bcb5a 100644
--- a/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb
+++ b/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb
@@ -29,7 +29,7 @@ module Gitlab
WHERE namespaces.id = calculated_ids.id
AND namespaces.traversal_ids = '{}'
SQL
- ActiveRecord::Base.connection.execute(update_sql)
+ ApplicationRecord.connection.execute(update_sql)
sleep PAUSE_SECONDS
end
diff --git a/lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb b/lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb
index 170af90805a..3bf6bf993dd 100644
--- a/lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb
+++ b/lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def execute(sql)
- @connection ||= ::ActiveRecord::Base.connection
+ @connection ||= ApplicationRecord.connection
@connection.execute(sql)
end
diff --git a/lib/gitlab/background_migration/backfill_user_namespace.rb b/lib/gitlab/background_migration/backfill_user_namespace.rb
index ab569e236fb..df6b1f083c3 100644
--- a/lib/gitlab/background_migration/backfill_user_namespace.rb
+++ b/lib/gitlab/background_migration/backfill_user_namespace.rb
@@ -25,7 +25,7 @@ module Gitlab
private
def connection
- ActiveRecord::Base.connection
+ ApplicationRecord.connection
end
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
diff --git a/lib/gitlab/background_migration/delete_orphaned_deployments.rb b/lib/gitlab/background_migration/delete_orphaned_deployments.rb
index 5d41a46c8cd..4a3a12ab53d 100644
--- a/lib/gitlab/background_migration/delete_orphaned_deployments.rb
+++ b/lib/gitlab/background_migration/delete_orphaned_deployments.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def orphaned_deployments
- define_batchable_model('deployments', connection: ActiveRecord::Base.connection)
+ define_batchable_model('deployments', connection: ApplicationRecord.connection)
.where('NOT EXISTS (SELECT 1 FROM environments WHERE deployments.environment_id = environments.id)')
end
diff --git a/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb b/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb
index 9a88eb8ea06..dad5da875ab 100644
--- a/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb
+++ b/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb
@@ -32,7 +32,7 @@ module Gitlab
private
def execute(sql)
- ActiveRecord::Base
+ ApplicationRecord
.connection
.execute(sql)
end
diff --git a/lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb b/lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb
index defd9ea832b..3772430d0b7 100644
--- a/lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb
+++ b/lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb
@@ -21,7 +21,7 @@ module Gitlab
backfill_project_namespaces_service.cleanup_gin_index('projects')
project_ids.each_slice(SUB_BATCH_SIZE) do |ids|
- ActiveRecord::Base.connection.execute(update_projects_name_and_path_sql(ids))
+ ApplicationRecord.connection.execute(update_projects_name_and_path_sql(ids))
end
backfill_project_namespaces_service.backfill_project_namespaces
diff --git a/lib/gitlab/background_migration/fix_projects_without_project_feature.rb b/lib/gitlab/background_migration/fix_projects_without_project_feature.rb
index 83c01afa432..c21f9c1d50f 100644
--- a/lib/gitlab/background_migration/fix_projects_without_project_feature.rb
+++ b/lib/gitlab/background_migration/fix_projects_without_project_feature.rb
@@ -14,7 +14,7 @@ module Gitlab
private
def create_missing!(from_id, to_id)
- result = ActiveRecord::Base.connection.select_one(sql(from_id, to_id))
+ result = ApplicationRecord.connection.select_one(sql(from_id, to_id))
return 0 unless result
result['number_of_created_records']
diff --git a/lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb b/lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb
index b8e4562b3bf..496ec0bd0a1 100644
--- a/lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb
+++ b/lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb
@@ -120,14 +120,14 @@ module Gitlab
end
def create_missing(from_id, to_id)
- result = ActiveRecord::Base.connection.select_one(create_sql(from_id, to_id))
+ result = ApplicationRecord.connection.select_one(create_sql(from_id, to_id))
return unless result
logger.info(message: "#{self.class}: created missing services for #{result['number_of_created_records']} projects in id=#{from_id}...#{to_id}")
end
def update_inconsistent(from_id, to_id)
- result = ActiveRecord::Base.connection.select_one(update_sql(from_id, to_id))
+ result = ApplicationRecord.connection.select_one(update_sql(from_id, to_id))
return unless result
logger.info(message: "#{self.class}: updated inconsistent services for #{result['number_of_updated_records']} projects in id=#{from_id}...#{to_id}")
diff --git a/lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb b/lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb
index ec4631d1e34..d7d24960a41 100644
--- a/lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb
+++ b/lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb
@@ -7,7 +7,7 @@ module Gitlab
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, end_id)
- define_batchable_model('integrations', connection: ::ActiveRecord::Base.connection)
+ define_batchable_model('integrations', connection: ApplicationRecord.connection)
.where(id: start_id..end_id, type_new: %w[Integrations::Confluence Integrations::Shimo])
.update_all(category: 'third_party_wiki')
diff --git a/lib/gitlab/background_migration/migrate_stage_status.rb b/lib/gitlab/background_migration/migrate_stage_status.rb
deleted file mode 100644
index 6a29a632577..00000000000
--- a/lib/gitlab/background_migration/migrate_stage_status.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Metrics/AbcSize
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class MigrateStageStatus
- STATUSES = { created: 0, pending: 1, running: 2, success: 3,
- failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
-
- class Build < ActiveRecord::Base
- self.table_name = 'ci_builds'
-
- scope :latest, -> { where(retried: [false, nil]) }
- scope :created, -> { where(status: 'created') }
- scope :running, -> { where(status: 'running') }
- scope :pending, -> { where(status: 'pending') }
- scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :canceled, -> { where(status: 'canceled') }
- scope :skipped, -> { where(status: 'skipped') }
- scope :manual, -> { where(status: 'manual') }
-
- scope :failed_but_allowed, -> do
- where(allow_failure: true, status: [:failed, :canceled])
- end
-
- scope :exclude_ignored, -> do
- where("allow_failure = ? OR status IN (?)",
- false, %w[created pending running success skipped])
- end
-
- def self.status_sql
- scope_relevant = latest.exclude_ignored
- scope_warnings = latest.failed_but_allowed
-
- builds = scope_relevant.select('count(*)').to_sql
- created = scope_relevant.created.select('count(*)').to_sql
- success = scope_relevant.success.select('count(*)').to_sql
- manual = scope_relevant.manual.select('count(*)').to_sql
- pending = scope_relevant.pending.select('count(*)').to_sql
- running = scope_relevant.running.select('count(*)').to_sql
- skipped = scope_relevant.skipped.select('count(*)').to_sql
- canceled = scope_relevant.canceled.select('count(*)').to_sql
- warnings = scope_warnings.select('count(*) > 0').to_sql
-
- <<-SQL.strip_heredoc
- (CASE
- WHEN (#{builds}) = (#{skipped}) AND (#{warnings}) THEN #{STATUSES[:success]}
- WHEN (#{builds}) = (#{skipped}) THEN #{STATUSES[:skipped]}
- WHEN (#{builds}) = (#{success}) THEN #{STATUSES[:success]}
- WHEN (#{builds}) = (#{created}) THEN #{STATUSES[:created]}
- WHEN (#{builds}) = (#{success}) + (#{skipped}) THEN #{STATUSES[:success]}
- WHEN (#{builds}) = (#{success}) + (#{skipped}) + (#{canceled}) THEN #{STATUSES[:canceled]}
- WHEN (#{builds}) = (#{created}) + (#{skipped}) + (#{pending}) THEN #{STATUSES[:pending]}
- WHEN (#{running}) + (#{pending}) > 0 THEN #{STATUSES[:running]}
- WHEN (#{manual}) > 0 THEN #{STATUSES[:manual]}
- WHEN (#{created}) > 0 THEN #{STATUSES[:running]}
- ELSE #{STATUSES[:failed]}
- END)
- SQL
- end
- end
-
- def perform(start_id, stop_id)
- status_sql = Build
- .where('ci_builds.commit_id = ci_stages.pipeline_id')
- .where('ci_builds.stage = ci_stages.name')
- .status_sql
-
- sql = <<-SQL
- UPDATE ci_stages SET status = (#{status_sql})
- WHERE ci_stages.status IS NULL
- AND ci_stages.id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
- SQL
-
- ActiveRecord::Base.connection.execute(sql)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb b/lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb
index c01545e5dca..06422ed282f 100644
--- a/lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb
+++ b/lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb
@@ -22,7 +22,7 @@ module Gitlab
private
def process_batch(from_id, to_id)
- ActiveRecord::Base.connection.execute(update_sql(from_id, to_id))
+ ApplicationRecord.connection.execute(update_sql(from_id, to_id))
logger.info(message: "#{self.class}: Copied container_registry_enabled values for projects with IDs between #{from_id}..#{to_id}")
end
diff --git a/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb b/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb
index 9e102ea1517..a9611e9814c 100644
--- a/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb
+++ b/lib/gitlab/background_migration/populate_container_repository_migration_plan.rb
@@ -33,7 +33,7 @@ module Gitlab
private
def connection
- @connection ||= ::ActiveRecord::Base.connection
+ @connection ||= ApplicationRecord.connection
end
def execute(sql)
diff --git a/lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb b/lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb
index 769ca4be7f3..1f2b55004e4 100644
--- a/lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb
+++ b/lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb
@@ -15,7 +15,7 @@ module Gitlab
def perform(start_id, stop_id)
Topic.where(id: start_id..stop_id).each_batch(of: SUB_BATCH_SIZE) do |batch|
- ActiveRecord::Base.connection.execute(<<~SQL)
+ ApplicationRecord.connection.execute(<<~SQL)
WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{batch.select(:id).limit(SUB_BATCH_SIZE).to_sql})
UPDATE topics
SET non_private_projects_count = (
diff --git a/lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb b/lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb
index 1d96872d445..2495cb51364 100644
--- a/lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb
+++ b/lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb
@@ -15,7 +15,7 @@ module Gitlab
def perform(start_id, stop_id)
Topic.where(id: start_id..stop_id).each_batch(of: SUB_BATCH_SIZE) do |batch|
- ActiveRecord::Base.connection.execute(<<~SQL)
+ ApplicationRecord.connection.execute(<<~SQL)
WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{batch.select(:id).limit(SUB_BATCH_SIZE).to_sql})
UPDATE topics
SET total_projects_count = (SELECT COUNT(*) FROM project_topics WHERE topic_id = batched_relation.id)
diff --git a/lib/gitlab/background_migration/populate_vulnerability_reads.rb b/lib/gitlab/background_migration/populate_vulnerability_reads.rb
index 7b6d4c1ff81..5e6475a3d1a 100644
--- a/lib/gitlab/background_migration/populate_vulnerability_reads.rb
+++ b/lib/gitlab/background_migration/populate_vulnerability_reads.rb
@@ -26,7 +26,7 @@ module Gitlab
end
def connection
- ActiveRecord::Base.connection
+ ApplicationRecord.connection
end
def insert_query(start_id, end_id)
diff --git a/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb b/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
index bd7d7d02162..91a0db08d5b 100644
--- a/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
+++ b/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
@@ -55,10 +55,10 @@ module Gitlab
end
def cleanup_gin_index(table_name)
- index_names = ActiveRecord::Base.connection.select_values("select indexname::text from pg_indexes where tablename = '#{table_name}' and indexdef ilike '%gin%'")
+ index_names = ApplicationRecord.connection.select_values("select indexname::text from pg_indexes where tablename = '#{table_name}' and indexdef ilike '%gin%'")
index_names.each do |index_name|
- ActiveRecord::Base.connection.execute("select gin_clean_pending_list('#{index_name}')")
+ ApplicationRecord.connection.execute("select gin_clean_pending_list('#{index_name}')")
end
end
@@ -77,7 +77,7 @@ module Gitlab
projects = IsolatedModels::Project.where(id: project_ids)
.select("projects.id, projects.name, projects.path, projects.namespace_id, projects.visibility_level, shared_runners_enabled, '#{PROJECT_NAMESPACE_STI_NAME}', now(), now()")
- ActiveRecord::Base.connection.execute <<~SQL
+ ApplicationRecord.connection.execute <<~SQL
INSERT INTO namespaces (tmp_project_id, name, path, parent_id, visibility_level, shared_runners_enabled, type, created_at, updated_at)
#{projects.to_sql}
ON CONFLICT DO NOTHING;
@@ -89,7 +89,7 @@ module Gitlab
.joins("INNER JOIN namespaces ON projects.id = namespaces.tmp_project_id")
.select("namespaces.id, namespaces.tmp_project_id")
- ActiveRecord::Base.connection.execute <<~SQL
+ ApplicationRecord.connection.execute <<~SQL
WITH cte(project_namespace_id, project_id) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
#{projects.to_sql}
)
@@ -105,7 +105,7 @@ module Gitlab
.joins("INNER JOIN namespaces n2 ON namespaces.parent_id = n2.id")
.select("namespaces.id as project_namespace_id, n2.traversal_ids")
- ActiveRecord::Base.connection.execute <<~SQL
+ ApplicationRecord.connection.execute <<~SQL
UPDATE namespaces
SET traversal_ids = array_append(project_namespaces.traversal_ids, project_namespaces.project_namespace_id)
FROM (#{namespaces.to_sql}) as project_namespaces(project_namespace_id, traversal_ids)
diff --git a/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb b/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb
index 323f109449b..4acef9029f9 100644
--- a/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb
+++ b/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb
@@ -10,7 +10,7 @@ module Gitlab
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, stop_id)
- define_batchable_model('vulnerability_finding_links', connection: ActiveRecord::Base.connection)
+ define_batchable_model('vulnerability_finding_links', connection: ApplicationRecord.connection)
.where(id: start_id..stop_id)
.delete_all
end
diff --git a/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb b/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb
index f54bb8256d0..38932e52bb0 100644
--- a/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb
+++ b/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb
@@ -28,7 +28,7 @@ module Gitlab
end
def connection
- @connection ||= ::ActiveRecord::Base.connection
+ @connection ||= ApplicationRecord.connection
end
def execute(sql)
diff --git a/lib/gitlab/background_migration/update_timelogs_project_id.rb b/lib/gitlab/background_migration/update_timelogs_project_id.rb
index 24c9967b88e..69bb5cf6e6d 100644
--- a/lib/gitlab/background_migration/update_timelogs_project_id.rb
+++ b/lib/gitlab/background_migration/update_timelogs_project_id.rb
@@ -36,7 +36,7 @@ module Gitlab
end
def execute(sql)
- @connection ||= ::ActiveRecord::Base.connection
+ @connection ||= ApplicationRecord.connection
@connection.execute(sql)
end
end
diff --git a/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb b/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
index f5ba9e63333..10db9f5064a 100644
--- a/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
+++ b/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
@@ -5,7 +5,7 @@ module Gitlab
module BackgroundMigration
class UpdateUsersWhereTwoFactorAuthRequiredFromGroup # rubocop:disable Metrics/ClassLength
def perform(start_id, stop_id)
- ActiveRecord::Base.connection.execute <<~SQL
+ ApplicationRecord.connection.execute <<~SQL
UPDATE
users
SET
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index f8fce1abc06..606814c72ad 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -15,7 +15,7 @@ module Gitlab
ALLOWED_KEYS =
%i[junit codequality sast secret_detection dependency_scanning container_scanning
dast performance browser_performance load_performance license_scanning metrics lsif
- dotenv cobertura terraform accessibility cluster_applications
+ dotenv cobertura terraform accessibility
requirements coverage_fuzzing api_fuzzing cluster_image_scanning
coverage_report].freeze
@@ -48,7 +48,6 @@ module Gitlab
validates :cobertura, array_of_strings_or_string: true
validates :terraform, array_of_strings_or_string: true
validates :accessibility, array_of_strings_or_string: true
- validates :cluster_applications, array_of_strings_or_string: true # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/333441
validates :requirements, array_of_strings_or_string: true
end
diff --git a/lib/gitlab/graphql/find_argument_in_parent.rb b/lib/gitlab/graphql/find_argument_in_parent.rb
deleted file mode 100644
index 1f83f8fce7a..00000000000
--- a/lib/gitlab/graphql/find_argument_in_parent.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Graphql
- module FindArgumentInParent
- # Searches up the GraphQL AST and returns the first matching argument
- # passed to a node
- def self.find(parent, argument, limit_depth: nil)
- argument = argument.to_s.camelize(:lower).to_sym
- depth = 0
-
- while parent.respond_to?(:parent)
- args = node_args(parent)
- return args[argument] if args.key?(argument)
-
- depth += 1
- return if limit_depth && depth >= limit_depth
-
- parent = parent.parent
- end
- end
-
- class << self
- private
-
- def node_args(node)
- node.irep_node.arguments
- end
- end
- end
- end
-end
diff --git a/lib/sidebars/groups/menus/kubernetes_menu.rb b/lib/sidebars/groups/menus/kubernetes_menu.rb
index e3b6104e517..0d845978a93 100644
--- a/lib/sidebars/groups/menus/kubernetes_menu.rb
+++ b/lib/sidebars/groups/menus/kubernetes_menu.rb
@@ -22,7 +22,8 @@ module Sidebars
override :render?
def render?
clusterable = context.group
- Feature.enabled?(:certificate_based_clusters, clusterable, default_enabled: :yaml, type: :ops) &&
+
+ clusterable.certificate_based_clusters_enabled? &&
can?(context.current_user, :read_cluster, clusterable)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 923d72b9f20..5311e9edbdc 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -43374,6 +43374,12 @@ msgstr ""
msgid "You cannot approve your own deployment."
msgstr ""
+msgid "You cannot change the start date after the cadence has started. Please create a new cadence."
+msgstr ""
+
+msgid "You cannot change the start date because the first iteration has already started on %{start_date}."
+msgstr ""
+
msgid "You cannot combine replace_ids with add_ids or remove_ids"
msgstr ""
diff --git a/qa/qa/page/component/new_snippet.rb b/qa/qa/page/component/new_snippet.rb
index 6ccf8a4043e..9c4408e36e4 100644
--- a/qa/qa/page/component/new_snippet.rb
+++ b/qa/qa/page/component/new_snippet.rb
@@ -76,8 +76,7 @@ module QA
end
def click_create_snippet_button
- wait_until(reload: false) { !find_element(:submit_button).disabled? }
- click_element(:submit_button)
+ click_element_coordinates(:submit_button)
wait_until(reload: false) do
has_no_element?(:snippet_title_field)
end
diff --git a/qa/qa/page/dashboard/snippet/edit.rb b/qa/qa/page/dashboard/snippet/edit.rb
index d84a053591c..8af3eb5693c 100644
--- a/qa/qa/page/dashboard/snippet/edit.rb
+++ b/qa/qa/page/dashboard/snippet/edit.rb
@@ -63,8 +63,7 @@ module QA
end
def save_changes
- wait_until(reload: false) { !find_element(:submit_button).disabled? }
- click_element(:submit_button)
+ click_element_coordinates(:submit_button)
wait_until(reload: false) do
has_no_element?(:file_name_field)
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
index 38adf2f5d55..b20d0929e9c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
context 'Add batch suggestions to a Merge Request' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index f8129c9ccba..91b0940f137 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
describe 'Protected branch support' do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb
index 0b63d9a1edb..a50b995e483 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
describe 'Multiple file snippet' do
let(:first_file_content) { 'First file content' }
let(:second_file_content) { 'Second file content' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
index b45624381c8..4b8edf8b792 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
context 'Content Editor' do
let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
let(:page_title) { 'Content Editor Page' }
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index a983ec43557..d7d46d0d415 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -54,13 +54,22 @@ describe('App component', () => {
const createComponent = ({
shouldShowCallout = true,
- license = LICENSE_ULTIMATE,
+ licenseQueryResponse = LICENSE_ULTIMATE,
...propsData
}) => {
userCalloutDismissSpy = jest.fn();
mockApollo = createMockApollo([
- [currentLicenseQuery, jest.fn().mockResolvedValue(getCurrentLicensePlanResponse(license))],
+ [
+ currentLicenseQuery,
+ jest
+ .fn()
+ .mockResolvedValue(
+ licenseQueryResponse instanceof Error
+ ? licenseQueryResponse
+ : getCurrentLicensePlanResponse(licenseQueryResponse),
+ ),
+ ],
]);
wrapper = extendedWrapper(
@@ -484,19 +493,23 @@ describe('App component', () => {
});
it.each`
- license | display
- ${LICENSE_ULTIMATE} | ${true}
- ${LICENSE_PREMIUM} | ${false}
- ${LICENSE_FREE} | ${false}
- ${null} | ${false}
- `('displays $display for license $license', async ({ license, display }) => {
- createComponent({
- license,
- augmentedSecurityFeatures: securityFeaturesMock,
- augmentedComplianceFeatures: complianceFeaturesMock,
- });
- await waitForPromises();
- expect(findVulnerabilityManagementTab().exists()).toBe(display);
- });
+ licenseQueryResponse | display
+ ${LICENSE_ULTIMATE} | ${true}
+ ${LICENSE_PREMIUM} | ${false}
+ ${LICENSE_FREE} | ${false}
+ ${null} | ${true}
+ ${new Error()} | ${true}
+ `(
+ 'displays $display for license $licenseQueryResponse',
+ async ({ licenseQueryResponse, display }) => {
+ createComponent({
+ licenseQueryResponse,
+ augmentedSecurityFeatures: securityFeaturesMock,
+ augmentedComplianceFeatures: complianceFeaturesMock,
+ });
+ await waitForPromises();
+ expect(findVulnerabilityManagementTab().exists()).toBe(display);
+ },
+ );
});
});
diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
index d98138f6385..8eab0222cf6 100644
--- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
@@ -18,8 +18,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
let(:project) { issue.project }
let(:params) { {} }
let(:current_user) { authorized_user }
- let(:parent_args) { { irrelevant: 1.2 } }
- let(:parent) { double('Parent', parent: nil, irep_node: double(arguments: parent_args)) }
+ let(:query_context) { { current_user: current_user } }
before do
enable_design_management
@@ -107,7 +106,9 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
end
context 'by at_version in parent' do
- let(:parent_args) { { atVersion: global_id_of(first_version) } }
+ before do
+ query_context[:at_version_argument] = first_version.to_global_id
+ end
it_behaves_like 'a query for all_versions up to the first_version'
end
@@ -126,8 +127,8 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do
it_behaves_like 'a source of versions'
end
- def resolve_versions(obj, context = { current_user: current_user })
- eager_resolve(resolver, obj: obj, parent: parent, args: params, ctx: context)
+ def resolve_versions(obj)
+ eager_resolve(resolver, obj: obj, args: params, ctx: query_context)
end
end
end
diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb
index c93762416f5..0304aac18ae 100644
--- a/spec/helpers/application_settings_helper_spec.rb
+++ b/spec/helpers/application_settings_helper_spec.rb
@@ -295,7 +295,7 @@ RSpec.describe ApplicationSettingsHelper do
it { is_expected.to eq([%w(Track track), %w(Compress compress)]) }
end
- describe '#instance_clusters_enabled?' do
+ describe '#instance_clusters_enabled?', :request_store do
let_it_be(:user) { create(:user) }
subject { helper.instance_clusters_enabled? }
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 061d8f34c8d..ff3ec6196f0 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -48,7 +48,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
:cobertura | 'cobertura-coverage.xml'
:terraform | 'tfplan.json'
:accessibility | 'gl-accessibility.json'
- :cluster_applications | 'gl-cluster-applications.json'
end
with_them do
diff --git a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
deleted file mode 100644
index 1b9301cd1aa..00000000000
--- a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Graphql::FindArgumentInParent do
- describe '#find' do
- def build_node(parent = nil, args: {})
- props = { irep_node: double(arguments: args) }
- props[:parent] = parent if parent # The root node shouldn't respond to parent
-
- double(props)
- end
-
- let(:parent) do
- build_node(
- build_node(
- build_node(
- build_node,
- args: { myArg: 1 }
- )
- )
- )
- end
-
- let(:arg_name) { :my_arg }
-
- it 'searches parents and returns the argument' do
- expect(described_class.find(parent, :my_arg)).to eq(1)
- end
-
- it 'can find argument when passed in as both Ruby and GraphQL-formatted symbols and strings' do
- [:my_arg, :myArg, 'my_arg', 'myArg'].each do |arg|
- expect(described_class.find(parent, arg)).to eq(1)
- end
- end
-
- it 'returns nil if no arguments found in parents' do
- expect(described_class.find(parent, :bar)).to eq(nil)
- end
-
- it 'can limit the depth it searches to' do
- expect(described_class.find(parent, :my_arg, limit_depth: 1)).to eq(nil)
- end
- end
-end
diff --git a/spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb b/spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb
index 36d5b3376b7..5bf8be9d6e5 100644
--- a/spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb
+++ b/spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Sidebars::Groups::Menus::KubernetesMenu do
+RSpec.describe Sidebars::Groups::Menus::KubernetesMenu, :request_store do
let_it_be(:owner) { create(:user) }
let_it_be(:group) do
build(:group, :private).tap do |g|
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e03996e96ff..f95feee47a9 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1446,6 +1446,44 @@ RSpec.describe Ci::Build do
expect(deployment).to be_canceled
end
end
+
+ # Mimic playing a manual job that needs another job.
+ # `needs + when:manual` scenario, see: https://gitlab.com/gitlab-org/gitlab/-/issues/347502
+ context 'when transits from skipped to created to running' do
+ before do
+ build.skip!
+ end
+
+ context 'during skipped to created' do
+ let(:event) { :process! }
+
+ it 'transitions to created' do
+ subject
+
+ expect(deployment).to be_created
+ end
+ end
+
+ context 'during created to running' do
+ let(:event) { :run! }
+
+ before do
+ build.process!
+ build.enqueue!
+ end
+
+ it 'transitions to running and calls webhook' do
+ freeze_time do
+ expect(Deployments::HooksWorker)
+ .to receive(:perform_async).with(deployment_id: deployment.id, status_changed_at: Time.current)
+
+ subject
+ end
+
+ expect(deployment).to be_running
+ end
+ end
+ end
end
describe '#on_stop' do
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 705b9b4cc65..409353bdbcf 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -858,12 +858,24 @@ RSpec.describe Deployment do
end
end
- it 'tracks an exception if an invalid status transition is detected' do
- expect(Gitlab::ErrorTracking)
+ context 'tracks an exception if an invalid status transition is detected' do
+ it do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(instance_of(described_class::StatusUpdateError), deployment_id: deploy.id)
+
+ expect(deploy.update_status('running')).to eq(false)
+ end
+
+ it do
+ deploy.update_status('success')
+
+ expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(instance_of(described_class::StatusUpdateError), deployment_id: deploy.id)
- expect(deploy.update_status('running')).to eq(false)
+ expect(deploy.update_status('created')).to eq(false)
+ end
end
it 'tracks an exception if an invalid argument' do
@@ -871,7 +883,7 @@ RSpec.describe Deployment do
.to receive(:track_exception)
.with(instance_of(described_class::StatusUpdateError), deployment_id: deploy.id)
- expect(deploy.update_status('created')).to eq(false)
+ expect(deploy.update_status('recreate')).to eq(false)
end
context 'mapping status to event' do
@@ -893,6 +905,16 @@ RSpec.describe Deployment do
deploy.update_status(status)
end
end
+
+ context 'for created status update' do
+ let(:deploy) { create(:deployment, status: :created) }
+
+ it 'calls the correct method' do
+ expect(deploy).to receive(:create!)
+
+ deploy.update_status('created')
+ end
+ end
end
end
@@ -974,7 +996,9 @@ RSpec.describe Deployment do
context 'with created build' do
let(:build_status) { :created }
- it_behaves_like 'ignoring build'
+ it_behaves_like 'gracefully handling error' do
+ let(:error_message) { %Q{Status cannot transition via \"create\"} }
+ end
end
context 'with running build' do
@@ -1002,7 +1026,9 @@ RSpec.describe Deployment do
context 'with created build' do
let(:build_status) { :created }
- it_behaves_like 'ignoring build'
+ it_behaves_like 'gracefully handling error' do
+ let(:error_message) { %Q{Status cannot transition via \"create\"} }
+ end
end
context 'with running build' do
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index a2b914c42f2..7b524be0c43 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -782,8 +782,16 @@ RSpec.describe Integration do
end
end
- describe '#api_field_names' do
- shared_examples 'api field names' do
+ describe 'field definitions' do
+ shared_examples '#fields' do
+ it 'does not return the same array' do
+ integration = fake_integration.new
+
+ expect(integration.fields).not_to be(integration.fields)
+ end
+ end
+
+ shared_examples '#api_field_names' do
it 'filters out secret fields' do
safe_fields = %w[some_safe_field safe_field url trojan_gift]
@@ -816,7 +824,8 @@ RSpec.describe Integration do
end
end
- it_behaves_like 'api field names'
+ it_behaves_like '#fields'
+ it_behaves_like '#api_field_names'
end
context 'when the class uses the field DSL' do
@@ -839,7 +848,8 @@ RSpec.describe Integration do
end
end
- it_behaves_like 'api field names'
+ it_behaves_like '#fields'
+ it_behaves_like '#api_field_names'
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 09ac15429a5..b70d90d861b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -2236,4 +2236,40 @@ RSpec.describe Namespace do
it_behaves_like 'blocks unsafe serialization'
end
+
+ describe '#certificate_based_clusters_enabled?' do
+ it 'does not call Feature.enabled? twice with request_store', :request_store do
+ expect(Feature).to receive(:enabled?).once
+
+ namespace.certificate_based_clusters_enabled?
+ namespace.certificate_based_clusters_enabled?
+ end
+
+ it 'call Feature.enabled? twice without request_store' do
+ expect(Feature).to receive(:enabled?).twice
+
+ namespace.certificate_based_clusters_enabled?
+ namespace.certificate_based_clusters_enabled?
+ end
+
+ context 'with ff disabled' do
+ before do
+ stub_feature_flags(certificate_based_clusters: false)
+ end
+
+ it 'is truthy' do
+ expect(namespace.certificate_based_clusters_enabled?).to be_falsy
+ end
+ end
+
+ context 'with ff enabled' do
+ before do
+ stub_feature_flags(certificate_based_clusters: true)
+ end
+
+ it 'is truthy' do
+ expect(namespace.certificate_based_clusters_enabled?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index dc0fa6eff66..42fe9f03141 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -833,6 +833,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) }
+ it { is_expected.to delegate_method(:certificate_based_clusters_enabled?).to(:namespace).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:container_registry_enabled?).to(:project_feature) }
it { is_expected.to delegate_method(:container_registry_access_level).to(:project_feature) }
diff --git a/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb b/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb
index c698e06c2a2..fbec6f98e76 100644
--- a/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb
+++ b/spec/support/shared_contexts/models/concerns/integrations/enable_ssl_verification_shared_context.rb
@@ -43,5 +43,9 @@ RSpec.shared_context Integrations::EnableSslVerification do
expect(names.index('enable_ssl_verification')).to eq insert_index
end
+
+ it 'does not insert the field repeatedly' do
+ expect(integration.fields.pluck(:name)).to eq(integration.fields.pluck(:name))
+ end
end
end
diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
index 8c9d1b32671..428e9cc8490 100644
--- a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
@@ -101,7 +101,7 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
- describe 'Kubernetes menu' do
+ describe 'Kubernetes menu', :request_store do
it 'has a link to the group cluster list path' do
render