diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8e276163848..6ef74f40d47 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -42,13 +42,16 @@ default:
CREATE_RAILS_FLAKY_TEST_ISSUES: "true"
CREATE_RAILS_SLOW_TEST_ISSUES: "true"
CREATE_RAILS_TEST_FAILURE_ISSUES: "true"
+ GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS: "true"
.default-merge-request-variables: &default-merge-request-variables
- NO_SOURCEMAPS: "true"
ADD_SLOW_TEST_NOTE_TO_MERGE_REQUEST: "true"
+ CREATE_RAILS_TEST_FAILURE_ISSUES: "false"
FF_NETWORK_PER_BUILD: "true"
FF_TIMESTAMPS: "true"
FF_USE_FASTZIP: "true"
+ NO_SOURCEMAPS: "true"
+ GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS: "true"
.if-merge-request-security-canonical-sync: &if-merge-request-security-canonical-sync
if: '$CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == "gitlab-org/security/gitlab" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == $CI_DEFAULT_BRANCH && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
diff --git a/.gitlab/ci/as-if-foss.gitlab-ci.yml b/.gitlab/ci/as-if-foss.gitlab-ci.yml
index c3913a93c6f..237e9590650 100644
--- a/.gitlab/ci/as-if-foss.gitlab-ci.yml
+++ b/.gitlab/ci/as-if-foss.gitlab-ci.yml
@@ -70,6 +70,7 @@ start-as-if-foss:
variables:
- GITLAB_LARGE_RUNNER_OPTIONAL
- GLCI_PRODUCTION_ASSETS_RUNNER_OPTIONAL
+ - GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS
variables:
START_AS_IF_FOSS: $START_AS_IF_FOSS
RUBY_VERSION: $RUBY_VERSION
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml
index c359d226946..7c27162e0fd 100644
--- a/.gitlab/ci/rails/shared.gitlab-ci.yml
+++ b/.gitlab/ci/rails/shared.gitlab-ci.yml
@@ -119,7 +119,7 @@ include:
- bundle exec gem list gitlab_quality-test_tooling
- |
section_start "failed-test-issues" "Report test failures"
- if [[ "$CREATE_RAILS_TEST_FAILURE_ISSUES" == "true" ]] && [[ -n "$TEST_FAILURES_PROJECT_TOKEN" ]]; then
+ if [[ -n "$TEST_FAILURES_PROJECT_TOKEN" ]]; then
input_file="rspec/rspec-${CI_JOB_ID}.json"
# The actual failures will always be part of the retry report
@@ -127,14 +127,35 @@ include:
input_file="rspec/rspec-retry-${CI_JOB_ID}.json"
fi
- bundle exec failed-test-issues \
- --token "${TEST_FAILURES_PROJECT_TOKEN}" \
- --project "gitlab-org/gitlab" \
- --input-files "${input_file}" \
- --exclude-labels-for-search "QA,knapsack_report" \
- --related-issues-file "rspec/${CI_JOB_ID}-failed-test-issues.json";
+ cmd_args=()
+ cmd_args+=(--token "${TEST_FAILURES_PROJECT_TOKEN}")
+ cmd_args+=(--project "gitlab-org/gitlab")
+ cmd_args+=(--input-files "${input_file}")
+ cmd_args+=(--exclude-labels-for-search "QA,knapsack_report")
+ cmd_args+=(--related-issues-file "rspec/${CI_JOB_ID}-failed-test-issues.json")
+
+ if [[ "$CREATE_RAILS_TEST_FAILURE_ISSUES" != "true" ]]; then
+ cmd_args+=(--enable-issue-update false)
+ echoinfo "Disabling issue creation because \$CREATE_RAILS_TEST_FAILURE_ISSUES != 'true'"
+ fi
+
+ if [[ "$GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS" == "true" ]]; then
+ if [[ -n "$GLCI_FAILED_TESTS_GCS_PROJECT_ID" && -n "$GLCI_FAILED_TESTS_GCS_BUCKET" && -n "$GLCI_FAILED_TESTS_GCS_CREDENTIALS_FILE" ]]; then
+ cmd_args+=(--enable-gcs true)
+ cmd_args+=(--gcs-project-id "$GLCI_FAILED_TESTS_GCS_PROJECT_ID")
+ cmd_args+=(--gcs-bucket "$GLCI_FAILED_TESTS_GCS_BUCKET")
+ cmd_args+=(--gcs-credentials "$GLCI_FAILED_TESTS_GCS_CREDENTIALS_FILE")
+ else
+ echoerr "Skipping GCS push because one or more GLCI_FAILED_TESTS_* environment variables are not set"
+ fi
+ else
+ echoinfo "Skipping GCS push because \$GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS != 'true'"
+ fi
+
+ # Execute command with all arguments
+ bundle exec failed-test-issues "${cmd_args[@]}"
else
- echoinfo "Not reporting test failures because \$CREATE_RAILS_TEST_FAILURE_ISSUES != 'true' or TEST_FAILURES_PROJECT_TOKEN is not set"
+ echoinfo "Not reporting test failures because TEST_FAILURES_PROJECT_TOKEN is not set"
fi
section_end "failed-test-issues"
- |
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 29bef23974f..6cac2f23468 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -2678,7 +2678,6 @@ Layout/LineLength:
- 'spec/features/projects/show/user_sees_deletion_failure_message_spec.rb'
- 'spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb'
- 'spec/features/projects/terraform_spec.rb'
- - 'spec/features/projects/tree/upload_file_spec.rb'
- 'spec/features/projects_spec.rb'
- 'spec/features/search/user_searches_for_comments_spec.rb'
- 'spec/features/search/user_searches_for_merge_requests_spec.rb'
diff --git a/.rubocop_todo/rails/file_path.yml b/.rubocop_todo/rails/file_path.yml
index 3026e4efe73..b2433868daf 100644
--- a/.rubocop_todo/rails/file_path.yml
+++ b/.rubocop_todo/rails/file_path.yml
@@ -80,7 +80,6 @@ Rails/FilePath:
- 'spec/features/projects/settings/repository_settings_spec.rb'
- 'spec/features/projects/settings/user_changes_avatar_spec.rb'
- 'spec/features/projects/snippets/create_snippet_spec.rb'
- - 'spec/features/projects/tree/upload_file_spec.rb'
- 'spec/features/snippets/user_creates_snippet_spec.rb'
- 'spec/features/snippets/user_edits_snippet_spec.rb'
- 'spec/features/uploads/user_uploads_avatar_to_group_spec.rb'
diff --git a/.rubocop_todo/rspec/avoid_conditional_statements.yml b/.rubocop_todo/rspec/avoid_conditional_statements.yml
index ef65040453f..fc589a52ac5 100644
--- a/.rubocop_todo/rspec/avoid_conditional_statements.yml
+++ b/.rubocop_todo/rspec/avoid_conditional_statements.yml
@@ -62,8 +62,6 @@ RSpec/AvoidConditionalStatements:
- 'spec/features/projects/settings/repository_settings_spec.rb'
- 'spec/features/projects/settings/user_transfers_a_project_spec.rb'
- 'spec/features/projects/show/user_sees_git_instructions_spec.rb'
- - 'spec/features/projects/tree/create_directory_spec.rb'
- - 'spec/features/projects/tree/create_file_spec.rb'
- 'spec/features/projects_spec.rb'
- 'spec/features/search/user_uses_header_search_field_spec.rb'
- 'spec/features/usage_stats_consent_spec.rb'
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index c01573433a4..c4c8cc06f49 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -102,7 +102,6 @@ RSpec/ContextWording:
- 'ee/spec/features/groups/saml_providers_spec.rb'
- 'ee/spec/features/groups/security/compliance_dashboards_spec.rb'
- 'ee/spec/features/groups_spec.rb'
- - 'ee/spec/features/ide/user_opens_ide_spec.rb'
- 'ee/spec/features/issues/epic_in_issue_sidebar_spec.rb'
- 'ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb'
- 'ee/spec/features/issues/form_spec.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index bd3be4cc017..6ccb6357d17 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -675,8 +675,6 @@ Style/IfUnlessModifier:
- 'spec/factories/users.rb'
- 'spec/features/merge_request/batch_comments_spec.rb'
- 'spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb'
- - 'spec/features/projects/tree/create_directory_spec.rb'
- - 'spec/features/projects/tree/create_file_spec.rb'
- 'spec/graphql/mutations/releases/update_spec.rb'
- 'spec/lib/container_registry/gitlab_api_client_spec.rb'
- 'spec/lib/gitlab/config/entry/validators/nested_array_helpers_spec.rb'
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 4b856550f2b..5d126a2e000 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -2,8 +2,6 @@ import Vue from 'vue';
import { IDE_ELEMENT_ID } from '~/ide/constants';
import PerformancePlugin from '~/performance/vue_performance_plugin';
import Translate from '~/vue_shared/translate';
-import { parseBoolean } from '../lib/utils/common_utils';
-import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
import { OAuthCallbackDomainMismatchErrorApp } from './oauth_callback_domain_mismatch_error';
Vue.use(Translate);
@@ -17,22 +15,13 @@ Vue.use(PerformancePlugin, {
*
* @param {Objects} options - Extra options for the IDE (Used by EE).
*/
-export async function startIde(options) {
+export async function startIde() {
const ideElement = document.getElementById(IDE_ELEMENT_ID);
if (!ideElement) {
return;
}
- const useNewWebIde = parseBoolean(ideElement.dataset.useNewWebIde);
-
- if (!useNewWebIde) {
- resetServiceWorkersPublicPath();
- const { initLegacyWebIDE } = await import('./init_legacy_web_ide');
- initLegacyWebIDE(ideElement, options);
- return;
- }
-
const oAuthCallbackDomainMismatchApp = new OAuthCallbackDomainMismatchErrorApp(ideElement);
if (oAuthCallbackDomainMismatchApp.shouldRenderError()) {
diff --git a/app/assets/javascripts/work_items/components/create_work_item.vue b/app/assets/javascripts/work_items/components/create_work_item.vue
index adc9cfbba60..88bc513cd81 100644
--- a/app/assets/javascripts/work_items/components/create_work_item.vue
+++ b/app/assets/javascripts/work_items/components/create_work_item.vue
@@ -520,7 +520,7 @@ export default {
Boolean(this.workItemDueDateIsFixed) ||
Boolean(this.workItemStartDateIsFixed) ||
Boolean(this.workItemIterationId) ||
- (this.glFeatures.customFieldsFeature && isCustomFieldsFilled)
+ isCustomFieldsFilled
);
},
shouldDatesRollup() {
@@ -529,9 +529,6 @@ export default {
workItemCustomFields() {
return findWidget(WIDGET_TYPE_CUSTOM_FIELDS, this.workItem)?.customFieldValues ?? null;
},
- showWorkItemCustomFields() {
- return this.glFeatures.customFieldsFeature && this.workItemCustomFields;
- },
},
watch: {
shouldDiscardDraft: {
@@ -1049,7 +1046,7 @@ export default {
@error="$emit('error', $event)"
/>
"
@@ -24,7 +27,7 @@ module RapidDiffs
rescue StandardError => e
Gitlab::AppLogger.error("Error streaming diffs: #{e.message}")
error_component = ::RapidDiffs::StreamingErrorComponent.new(message: e.message)
- response.stream.write error_component.render_in(view_context)
+ response.stream.write error_component.render_in(context)
ensure
response.stream.close
end
@@ -55,7 +58,7 @@ module RapidDiffs
helpers.diff_view
end
- def stream_diff_files(options)
+ def stream_diff_files(options, view_context)
return unless resource
diffs = resource.diffs_for_streaming(options)
@@ -68,26 +71,40 @@ module RapidDiffs
# NOTE: This is a temporary flag to test out the new diff_blobs
if !!ActiveModel::Type::Boolean.new.cast(params.permit(:diff_blobs)[:diff_blobs])
- stream_diff_blobs(options)
+ stream_diff_blobs(options, view_context)
else
- diffs.diff_files.each do |diff_file|
- response.stream.write(render_diff_file(diff_file))
- end
+ stream_diff_collection(diffs.diff_files, view_context)
end
end
- def render_diff_file(diff_file)
- render_to_string(
- ::RapidDiffs::DiffFileComponent.new(diff_file: diff_file, parallel_view: view == :parallel),
- layout: false
- )
+ def stream_diff_collection(diff_files, view_context)
+ each_growing_slice(diff_files, 5, 2) do |slice|
+ response.stream.write(render_diff_files_collection(slice, view_context))
+ end
end
- def stream_diff_blobs(options)
+ def each_growing_slice(collection, initial_size, growth_factor = 2)
+ position = 0
+ size = initial_size
+ total = collection.count
+
+ while position < total
+ end_pos = [position + size, total].min
+ yield collection.to_a[position...end_pos] if block_given?
+
+ position = end_pos
+ size = (size * growth_factor).to_i
+ end
+ end
+
+ def render_diff_files_collection(diff_files, view_context)
+ ::RapidDiffs::DiffFileComponent.with_collection(diff_files, parallel_view: view == :parallel)
+ .render_in(view_context)
+ end
+
+ def stream_diff_blobs(options, view_context)
resource.diffs_for_streaming(options) do |diff_files_batch|
- diff_files_batch.each do |diff_file|
- response.stream.write(render_diff_file(diff_file))
- end
+ response.stream.write(render_diff_files_collection(diff_files_batch, view_context))
end
end
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index a84903fd6ef..41133caa210 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -26,11 +26,10 @@ class IdeController < ApplicationController
@fork_info = fork_info(project, params[:branch])
push_frontend_feature_flag(:web_ide_multi_domain, @project.group)
- render layout: helpers.use_new_web_ide? ? 'fullscreen' : 'application'
+ render layout: 'fullscreen'
end
def oauth_redirect
- return render_404 unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
# TODO - It's **possible** we end up here and no oauth application has been set up.
# We need to have better handling of these edge cases. Here's a follow-up issue:
# https://gitlab.com/gitlab-org/gitlab/-/issues/433322
@@ -54,8 +53,6 @@ class IdeController < ApplicationController
end
def ensure_web_ide_oauth_application!
- return unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
-
::WebIde::DefaultOauthApplication.ensure_oauth_application!
end
diff --git a/app/controllers/projects/merge_requests/diffs_stream_controller.rb b/app/controllers/projects/merge_requests/diffs_stream_controller.rb
index 1e03f1edaf3..b1b98aa82e0 100644
--- a/app/controllers/projects/merge_requests/diffs_stream_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_stream_controller.rb
@@ -11,15 +11,10 @@ module Projects
@merge_request
end
- def render_diff_file(diff_file)
- render_to_string(
- ::RapidDiffs::MergeRequestDiffFileComponent.new(
- diff_file: diff_file,
- merge_request: @merge_request,
- parallel_view: view == :parallel
- ),
- layout: false
- )
+ def render_diff_files_collection(diff_files, view_context)
+ ::RapidDiffs::MergeRequestDiffFileComponent
+ .with_collection(diff_files, merge_request: @merge_request, parallel_view: view == :parallel)
+ .render_in(view_context)
end
end
end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 44d1879bed1..743c315be83 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -21,6 +21,7 @@
# Both parent and include_parent_descendants params must be present.
# include_ancestors: boolean (defaults to true)
# organization: Scope the groups to the Organizations::Organization
+# active: boolean - filters for active groups.
#
# Users with full private access can see all groups. The `owned` and `parent`
# params can be used to restrict the groups that are returned.
@@ -103,6 +104,7 @@ class GroupsFinder < UnionFinder
def filter_groups(groups)
groups = by_organization(groups)
+ groups = by_active(groups)
groups = by_parent(groups)
groups = by_custom_attributes(groups)
groups = filter_group_ids(groups)
@@ -161,6 +163,12 @@ class GroupsFinder < UnionFinder
groups.id_not_in(params[:exclude_group_ids])
end
+ def by_active(groups)
+ return groups if params[:active].nil?
+
+ params[:active] ? groups.active : groups.inactive
+ end
+
def include_parent_shared_groups?
params.fetch(:include_parent_shared_groups, false)
end
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index 8c7a24b2ce2..6e3408a0376 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -4,12 +4,11 @@ module IdeHelper
# Overridden in EE
def ide_data(project:, fork_info:, params:)
base_data = {
- 'use-new-web-ide' => use_new_web_ide?.to_s,
'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/_index.md'),
'sign-in-path' => new_session_path(current_user),
'sign-out-path' => destroy_user_session_path,
'user-preferences-path' => profile_preferences_path
- }.merge(use_new_web_ide? ? new_ide_data(project: project) : legacy_ide_data(project: project))
+ }.merge(new_ide_data(project: project))
return base_data unless project
@@ -22,8 +21,6 @@ module IdeHelper
end
def show_web_ide_oauth_callback_mismatch_callout?
- return false unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
-
callback_urls = ::WebIde::DefaultOauthApplication.oauth_application_callback_urls
callback_url_domains = callback_urls.map { |url| URI.parse(url).origin }
callback_url_domains.any? && callback_url_domains.exclude?(request.base_url)
@@ -33,10 +30,6 @@ module IdeHelper
::WebIde::DefaultOauthApplication.oauth_application_id
end
- def use_new_web_ide?
- Feature.enabled?(:vscode_web_ide, current_user)
- end
-
private
def new_ide_fonts
@@ -66,7 +59,6 @@ module IdeHelper
end
def new_ide_oauth_data
- return {} unless ::WebIde::DefaultOauthApplication.feature_enabled?(current_user)
return {} unless ::WebIde::DefaultOauthApplication.oauth_application
client_id = ::WebIde::DefaultOauthApplication.oauth_application.uid
@@ -95,32 +87,6 @@ module IdeHelper
}.merge(new_ide_code_suggestions_data).merge(new_ide_oauth_data)
end
- def legacy_ide_data(project:)
- {
- 'empty-state-svg-path' => image_path('illustrations/empty-state/empty-variables-md.svg'),
- 'no-changes-state-svg-path' => image_path('illustrations/status/status-nothing-sm.svg'),
- 'committed-state-svg-path' => image_path('illustrations/rocket-launch-md.svg'),
- 'pipelines-empty-state-svg-path': image_path('illustrations/empty-state/empty-pipeline-md.svg'),
- 'switch-editor-svg-path': image_path('illustrations/rocket-launch-md.svg'),
- 'ci-help-page-path' => help_page_path('ci/quick_start/_index.md'),
- 'web-ide-help-page-path' => help_page_path('user/project/web_ide/_index.md'),
- 'render-whitespace-in-code': current_user.render_whitespace_in_code.to_s,
- 'default-branch' => project && project.default_branch,
- 'project' => convert_to_project_entity_json(project),
- 'preview-markdown-path' => project && preview_markdown_path(project),
- 'web-terminal-svg-path' => image_path('illustrations/empty-state/empty-cloud-md.svg'),
- 'web-terminal-help-path' => help_page_path('user/project/web_ide/_index.md'),
- 'web-terminal-config-help-path' => help_page_path('user/project/web_ide/_index.md'),
- 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/_index.md')
- }
- end
-
- def convert_to_project_entity_json(project)
- return unless project
-
- API::Entities::Project.represent(project, current_user: current_user).to_json
- end
-
def has_dismissed_ide_environments_callout?
current_user.dismissed_callout?(feature_name: 'web_ide_ci_environments_guidance')
end
diff --git a/app/models/alert_management/http_integration.rb b/app/models/alert_management/http_integration.rb
index a70168dc0d8..3005793722c 100644
--- a/app/models/alert_management/http_integration.rb
+++ b/app/models/alert_management/http_integration.rb
@@ -3,6 +3,7 @@
module AlertManagement
class HttpIntegration < ApplicationRecord
include ::Gitlab::Routing
+ include Gitlab::EncryptedAttribute
LEGACY_IDENTIFIERS = %w[legacy legacy-prometheus].freeze
@@ -10,7 +11,7 @@ module AlertManagement
attr_encrypted :token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
attribute :endpoint_identifier, default: -> { SecureRandom.hex(8) }
diff --git a/app/models/alerting/project_alerting_setting.rb b/app/models/alerting/project_alerting_setting.rb
index 7e94d41137f..1da9faa65b1 100644
--- a/app/models/alerting/project_alerting_setting.rb
+++ b/app/models/alerting/project_alerting_setting.rb
@@ -4,13 +4,15 @@ require 'securerandom'
module Alerting
class ProjectAlertingSetting < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
belongs_to :project
validates :token, presence: true
attr_encrypted :token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
before_validation :ensure_token
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 98d49ef05cb..03ee4a3efba 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -6,6 +6,7 @@ class ApplicationSetting < ApplicationRecord
include TokenAuthenticatable
include ChronicDurationAttribute
include Sanitizable
+ include Gitlab::EncryptedAttribute
ignore_column :pre_receive_secret_detection_enabled, remove_with: '17.9', remove_after: '2025-02-15'
@@ -854,14 +855,14 @@ class ApplicationSetting < ApplicationRecord
attr_encrypted :asset_proxy_secret_key,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
+ key: :db_key_base_truncated,
algorithm: 'aes-256-cbc',
insecure_mode: true
private_class_method def self.encryption_options_base_32_aes_256_gcm
{
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: true
}
diff --git a/app/models/atlassian/identity.rb b/app/models/atlassian/identity.rb
index 1ad7f657db1..3186f0c0c3a 100644
--- a/app/models/atlassian/identity.rb
+++ b/app/models/atlassian/identity.rb
@@ -2,6 +2,8 @@
module Atlassian
class Identity < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
self.table_name = 'atlassian_identities'
belongs_to :user
@@ -11,14 +13,14 @@ module Atlassian
attr_encrypted :token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
attr_encrypted :refresh_token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/app/models/bulk_imports/configuration.rb b/app/models/bulk_imports/configuration.rb
index a4758ee75cf..00343cb4a3c 100644
--- a/app/models/bulk_imports/configuration.rb
+++ b/app/models/bulk_imports/configuration.rb
@@ -3,6 +3,8 @@
# Stores the authentication data required to access another GitLab instance on
# behalf of a user, to import Groups and Projects directly from that instance.
class BulkImports::Configuration < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
self.table_name = 'bulk_import_configurations'
belongs_to :bulk_import, inverse_of: :configuration, optional: true
@@ -12,11 +14,11 @@ class BulkImports::Configuration < ApplicationRecord
allow_nil: true
attr_encrypted :url,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm'
attr_encrypted :access_token,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm'
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index a187e81c882..f562189d0a9 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -165,7 +165,7 @@ module Ci
preload(
:job_artifacts_archive, :ci_stage, :job_artifacts, :runner, :tags, :runner_manager, :metadata,
pipeline: :project,
- user: [:user_preference, :user_detail, :followees]
+ user: [:user_preference, :user_detail, :followees, :followers]
)
end
diff --git a/app/models/cloud_connector/service_access_token.rb b/app/models/cloud_connector/service_access_token.rb
index 9da6d614024..40d2b58f16a 100644
--- a/app/models/cloud_connector/service_access_token.rb
+++ b/app/models/cloud_connector/service_access_token.rb
@@ -2,6 +2,8 @@
module CloudConnector
class ServiceAccessToken < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
self.table_name = 'service_access_tokens'
scope :expired, -> { where('expires_at < :now', now: Time.current) }
@@ -9,7 +11,7 @@ module CloudConnector
attr_encrypted :token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/app/models/clusters/integrations/prometheus.rb b/app/models/clusters/integrations/prometheus.rb
index 935d6238dba..671de45c66d 100644
--- a/app/models/clusters/integrations/prometheus.rb
+++ b/app/models/clusters/integrations/prometheus.rb
@@ -5,6 +5,7 @@ module Clusters
class Prometheus < ApplicationRecord
include ::Clusters::Concerns::PrometheusClient
include AfterCommitQueue
+ include Gitlab::EncryptedAttribute
self.table_name = 'clusters_integration_prometheus'
self.primary_key = :cluster_id
@@ -23,7 +24,7 @@ module Clusters
attr_encrypted :alert_manager_token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
after_initialize :set_alert_manager_token, if: :new_record?
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index e3da4e2a698..ac80e8494c0 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -3,6 +3,7 @@
module Clusters
class KubernetesNamespace < ApplicationRecord
include Gitlab::Kubernetes
+ include Gitlab::EncryptedAttribute
self.table_name = 'clusters_kubernetes_namespaces'
@@ -23,7 +24,7 @@ module Clusters
attr_encrypted :service_account_token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
+ key: :db_key_base_truncated,
algorithm: 'aes-256-cbc'
scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) }
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 85d4e89a5b2..8b8affd90b2 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -7,6 +7,7 @@ module Clusters
include AfterCommitQueue
include ReactiveCaching
include NullifyIfBlank
+ include Gitlab::EncryptedAttribute
RESERVED_NAMESPACES = %w[gitlab-managed-apps].freeze
REQUIRED_K8S_MIN_VERSION = 23
@@ -30,12 +31,12 @@ module Clusters
attr_encrypted :password,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
+ key: :db_key_base_truncated,
algorithm: 'aes-256-cbc'
attr_encrypted :token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
+ key: :db_key_base_truncated,
algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case
diff --git a/app/models/clusters/providers/aws.rb b/app/models/clusters/providers/aws.rb
index 969820459e3..04b2d8b19ab 100644
--- a/app/models/clusters/providers/aws.rb
+++ b/app/models/clusters/providers/aws.rb
@@ -5,6 +5,7 @@ module Clusters
class Aws < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include Clusters::Concerns::ProviderStatus
+ include Gitlab::EncryptedAttribute
self.table_name = 'cluster_providers_aws'
@@ -18,7 +19,7 @@ module Clusters
attr_encrypted :secret_access_key,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
validates :role_arn,
diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb
index 6f39037b947..9438f69aefe 100644
--- a/app/models/clusters/providers/gcp.rb
+++ b/app/models/clusters/providers/gcp.rb
@@ -4,6 +4,7 @@ module Clusters
module Providers
class Gcp < ApplicationRecord
include Clusters::Concerns::ProviderStatus
+ include Gitlab::EncryptedAttribute
self.table_name = 'cluster_providers_gcp'
@@ -18,7 +19,7 @@ module Clusters
attr_encrypted :access_token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
+ key: :db_key_base_truncated,
algorithm: 'aes-256-cbc'
validates :gcp_project_id,
diff --git a/app/models/concerns/integrations/base/integration.rb b/app/models/concerns/integrations/base/integration.rb
index 53605d23312..bcfcfde3218 100644
--- a/app/models/concerns/integrations/base/integration.rb
+++ b/app/models/concerns/integrations/base/integration.rb
@@ -454,6 +454,8 @@ module Integrations
include Integrations::ResetSecretFields
include FromUnion
include EachBatch
+ include Gitlab::EncryptedAttribute
+
extend SafeFormatHelper
extend ::Gitlab::Utils::Override
@@ -462,7 +464,7 @@ module Integrations
attr_encrypted :properties,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
marshal: true,
marshaler: ::Gitlab::Json,
@@ -669,9 +671,10 @@ module Integrations
def reencrypt_properties
unless properties.nil? || properties.empty?
- alg = self.class.attr_encrypted_attributes[:properties][:algorithm]
- iv = generate_iv(alg)
- ep = self.class.attr_encrypt(:properties, properties, { iv: iv })
+ attr_encrypted_attributes = self.class.attr_encrypted_attributes[:properties]
+ key = dynamic_encryption_key_for_operation(attr_encrypted_attributes[:key])
+ iv = generate_iv(attr_encrypted_attributes[:algorithm])
+ ep = self.class.attr_encrypt(:properties, properties, { key: key, iv: iv })
end
{ 'encrypted_properties' => ep, 'encrypted_properties_iv' => iv }
diff --git a/app/models/concerns/packages/debian/distribution_key.rb b/app/models/concerns/packages/debian/distribution_key.rb
index 7023e2dcd37..3f22d4886ff 100644
--- a/app/models/concerns/packages/debian/distribution_key.rb
+++ b/app/models/concerns/packages/debian/distribution_key.rb
@@ -6,6 +6,8 @@ module Packages
extend ActiveSupport::Concern
included do
+ include Gitlab::EncryptedAttribute
+
belongs_to :distribution, class_name: "Packages::Debian::#{container_type.capitalize}Distribution", inverse_of: :key
validates :distribution,
presence: true
@@ -19,11 +21,11 @@ module Packages
attr_encrypted :private_key,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
attr_encrypted :passphrase,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
private
diff --git a/app/models/concerns/web_hooks/hook.rb b/app/models/concerns/web_hooks/hook.rb
index 5660c697148..fae6efbd756 100644
--- a/app/models/concerns/web_hooks/hook.rb
+++ b/app/models/concerns/web_hooks/hook.rb
@@ -14,20 +14,21 @@ module WebHooks
included do
include Sortable
include WebHooks::AutoDisabling
+ include Gitlab::EncryptedAttribute
attr_encrypted :token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32
+ key: :db_key_base_32
attr_encrypted :url,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32
+ key: :db_key_base_32
attr_encrypted :url_variables,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
marshal: true,
marshaler: ::Gitlab::Json,
@@ -36,7 +37,7 @@ module WebHooks
attr_encrypted :custom_headers,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
marshal: true,
marshaler: ::Gitlab::Json,
@@ -168,11 +169,21 @@ module WebHooks
end
def decrypt_url_was
- self.class.decrypt_url(encrypted_url_was, iv: Base64.decode64(encrypted_url_iv_was))
+ options = {
+ key: dynamic_encryption_key_for_operation(attr_encrypted_attributes[:url][:key]),
+ iv: Base64.decode64(encrypted_url_iv_was)
+ }
+
+ self.class.attr_decrypt(:url, encrypted_url_was, options)
end
def url_variables_were
- self.class.decrypt_url_variables(encrypted_url_variables_was, iv: encrypted_url_variables_iv_was)
+ options = {
+ key: dynamic_encryption_key_for_operation(attr_encrypted_attributes[:url_variables][:key]),
+ iv: encrypted_url_variables_iv_was
+ }
+
+ self.class.attr_decrypt(:url_variables, encrypted_url_variables_was, options)
end
def initialize_url_variables
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index abe883d6f1b..34b7641111f 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -5,6 +5,7 @@ module ErrorTracking
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
include Gitlab::Routing
+ include Gitlab::EncryptedAttribute
SENTRY_API_ERROR_TYPE_BAD_REQUEST = 'bad_request_for_sentry_api'
SENTRY_API_ERROR_TYPE_MISSING_KEYS = 'missing_keys_in_sentry_response'
@@ -42,7 +43,7 @@ module ErrorTracking
attr_encrypted :token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm'
before_validation :reset_token
diff --git a/app/models/grafana_integration.rb b/app/models/grafana_integration.rb
index 37e69102521..b669d924ca9 100644
--- a/app/models/grafana_integration.rb
+++ b/app/models/grafana_integration.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
class GrafanaIntegration < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
belongs_to :project
attr_encrypted :token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32
+ key: :db_key_base_32
before_validation :check_token_changes
diff --git a/app/models/group.rb b/app/models/group.rb
index b043a30e457..166f5ec5789 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -205,6 +205,19 @@ class Group < Namespace
scope :with_users, -> { includes(:users) }
+ scope :active, -> do
+ non_archived.not_aimed_for_deletion
+ end
+
+ scope :inactive, -> do
+ joins(:namespace_settings)
+ .left_joins(:deletion_schedule)
+ .where(<<~SQL)
+ #{reflections['namespace_settings'].table_name}.archived = TRUE
+ OR #{reflections['deletion_schedule'].table_name}.#{reflections['deletion_schedule'].foreign_key} IS NOT NULL
+ SQL
+ end
+
scope :with_non_archived_projects, -> { includes(:non_archived_projects) }
scope :with_non_invite_group_members, -> { includes(:non_invite_group_members) }
diff --git a/app/models/incident_management/project_incident_management_setting.rb b/app/models/incident_management/project_incident_management_setting.rb
index b6da93508c2..f7a91e83e8d 100644
--- a/app/models/incident_management/project_incident_management_setting.rb
+++ b/app/models/incident_management/project_incident_management_setting.rb
@@ -3,6 +3,7 @@
module IncidentManagement
class ProjectIncidentManagementSetting < ApplicationRecord
include Gitlab::Utils::StrongMemoize
+ include Gitlab::EncryptedAttribute
belongs_to :project
@@ -12,7 +13,7 @@ module IncidentManagement
attr_encrypted :pagerduty_token,
mode: :per_attribute_iv,
- key: ::Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false, # No need to encode for binary column https://github.com/attr-encrypted/attr_encrypted#the-encode-encode_iv-encode_salt-and-default_encoding-options
encode_iv: false
diff --git a/app/models/jira_connect_installation.rb b/app/models/jira_connect_installation.rb
index 0da8482fea4..8141839407e 100644
--- a/app/models/jira_connect_installation.rb
+++ b/app/models/jira_connect_installation.rb
@@ -2,11 +2,12 @@
class JiraConnectInstallation < ApplicationRecord
include Gitlab::Routing
+ include Gitlab::EncryptedAttribute
attr_encrypted :shared_secret,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32
+ key: :db_key_base_32
has_many :subscriptions, class_name: 'JiraConnectSubscription'
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 841229b5421..f4d4e5d3b7c 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -4,6 +4,7 @@ class PagesDomain < ApplicationRecord
include Presentable
include FromUnion
include AfterCommitQueue
+ include Gitlab::EncryptedAttribute
VERIFICATION_KEY = 'gitlab-pages-verification-code'
VERIFICATION_THRESHOLD = 3.days.freeze
@@ -48,7 +49,7 @@ class PagesDomain < ApplicationRecord
attr_encrypted :key,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
- key: Settings.attr_encrypted_db_key_base,
+ key: :db_key_base,
algorithm: 'aes-256-cbc'
scope :for_project, ->(project) { where(project: project) }
diff --git a/app/models/pages_domain_acme_order.rb b/app/models/pages_domain_acme_order.rb
index 80688e8d247..4872bc55e0c 100644
--- a/app/models/pages_domain_acme_order.rb
+++ b/app/models/pages_domain_acme_order.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class PagesDomainAcmeOrder < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
belongs_to :pages_domain
scope :expired, -> { where("expires_at < ?", Time.current) }
@@ -14,7 +16,7 @@ class PagesDomainAcmeOrder < ApplicationRecord
attr_encrypted :private_key,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: true
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 30cdb6ea62d..d0ab049678c 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -3,6 +3,8 @@
require 'carrierwave/orm/activerecord'
class ProjectImportData < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
prepend_mod_with('ProjectImportData') # rubocop: disable Cop/InjectEnterpriseEditionModule
# Timeout strategy can only be changed via API, currently only with GitHub and BitBucket Server
@@ -12,7 +14,7 @@ class ProjectImportData < ApplicationRecord
belongs_to :project, inverse_of: :import_data
attr_encrypted :credentials,
- key: Settings.attr_encrypted_db_key_base,
+ key: :db_key_base,
marshal: true,
encode: true,
mode: :per_attribute_iv_and_salt,
diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb
index 25c38936e93..e11d832b13a 100644
--- a/app/models/project_setting.rb
+++ b/app/models/project_setting.rb
@@ -5,6 +5,7 @@ class ProjectSetting < ApplicationRecord
include EachBatch
include CascadingProjectSettingAttribute
include Projects::SquashOption
+ include Gitlab::EncryptedAttribute
ALLOWED_TARGET_PLATFORMS = %w[ios osx tvos watchos android].freeze
@@ -18,14 +19,14 @@ class ProjectSetting < ApplicationRecord
attr_encrypted :cube_api_key,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
attr_encrypted :product_analytics_configurator_connection_string,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 5914799a3e3..819480a6e7d 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -4,6 +4,7 @@ class RemoteMirror < ApplicationRecord
include AfterCommitQueue
include MirrorAuthentication
include SafeUrl
+ include Gitlab::EncryptedAttribute
MAX_FIRST_RUNTIME = 3.hours
MAX_INCREMENTAL_RUNTIME = 1.hour
@@ -11,7 +12,7 @@ class RemoteMirror < ApplicationRecord
UNPROTECTED_BACKOFF_DELAY = 5.minutes
attr_encrypted :credentials,
- key: Settings.attr_encrypted_db_key_base,
+ key: :db_key_base,
marshal: true,
encode: true,
mode: :per_attribute_iv_and_salt,
diff --git a/app/models/service_desk/custom_email_credential.rb b/app/models/service_desk/custom_email_credential.rb
index f3c201f58d1..2cf037b873e 100644
--- a/app/models/service_desk/custom_email_credential.rb
+++ b/app/models/service_desk/custom_email_credential.rb
@@ -2,6 +2,8 @@
module ServiceDesk
class CustomEmailCredential < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
# Used to explicitly set the SMTP AUTH method.
# If nil Net::SMTP will choose one of methods listed by the SMTP server.
enum smtp_authentication: {
@@ -13,13 +15,13 @@ module ServiceDesk
attr_encrypted :smtp_username,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
encode: false,
encode_iv: false
attr_encrypted :smtp_password,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
encode: false,
encode_iv: false
diff --git a/app/models/service_desk/custom_email_verification.rb b/app/models/service_desk/custom_email_verification.rb
index a03c984c3a6..62ab63d7339 100644
--- a/app/models/service_desk/custom_email_verification.rb
+++ b/app/models/service_desk/custom_email_verification.rb
@@ -2,6 +2,8 @@
module ServiceDesk
class CustomEmailVerification < ApplicationRecord
+ include Gitlab::EncryptedAttribute
+
TIMEFRAME = 30.minutes
STATES = { started: 0, finished: 1, failed: 2 }.freeze
@@ -18,7 +20,7 @@ module ServiceDesk
attr_encrypted :token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
encode: false,
encode_iv: false
diff --git a/app/models/slack_integration.rb b/app/models/slack_integration.rb
index 8e852aa4fa3..7900203b993 100644
--- a/app/models/slack_integration.rb
+++ b/app/models/slack_integration.rb
@@ -2,6 +2,7 @@
class SlackIntegration < ApplicationRecord
include EachBatch
+ include Gitlab::EncryptedAttribute
ALL_FEATURES = %i[commands notifications].freeze
@@ -21,7 +22,7 @@ class SlackIntegration < ApplicationRecord
attr_encrypted :bot_access_token,
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 93acff57547..5275c44a0d4 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -20,6 +20,7 @@ class Snippet < ApplicationRecord
include CreatedAtFilterable
include EachBatch
include Import::HasImportSource
+ include Gitlab::EncryptedAttribute
MAX_FILE_COUNT = 10
@@ -94,7 +95,7 @@ class Snippet < ApplicationRecord
attr_spammable :description, spam_description: true
attr_encrypted :secret_token,
- key: Settings.attr_encrypted_db_key_base_truncated,
+ key: :db_key_base_truncated,
mode: :per_attribute_iv,
algorithm: 'aes-256-cbc'
diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml
index b9a95d18804..bed087db1b5 100644
--- a/app/views/ide/index.html.haml
+++ b/app/views/ide/index.html.haml
@@ -1,23 +1,6 @@
- disable_fixed_body_scroll
- page_title _("IDE"), @project.full_name
- add_page_specific_style 'page_bundles/web_ide_loader'
-
-// The block below is for the Web IDE
-// See: https://gitlab.com/groups/gitlab-org/-/epics/7683
-- unless use_new_web_ide?
- - @breadcrumb_title = _("IDE")
- - @breadcrumb_link = '#'
- - @no_container = true
- - @content_wrapper_class = 'pb-0'
- - add_to_breadcrumbs(s_('Navigation|Your work'), root_path)
- - nav 'your_work' # Couldn't get the `project` nav to work easily
- - add_page_specific_style 'page_bundles/build'
- - add_page_specific_style 'page_bundles/ide'
- - add_page_specific_style 'page_bundles/terminal'
-
- - content_for :prefetch_asset_tags do
- - webpack_preload_asset_tag('monaco')
-
- data = ide_data(project: @project, fork_info: @fork_info, params: params)
= render partial: 'shared/ide_root', locals: { data: data, loading_text: _('Loading the Web IDE') }
diff --git a/app/workers/namespaces/enable_descendants_cache_cron_worker.rb b/app/workers/namespaces/enable_descendants_cache_cron_worker.rb
index 0b8801e84f9..fb9005f5e28 100644
--- a/app/workers/namespaces/enable_descendants_cache_cron_worker.rb
+++ b/app/workers/namespaces/enable_descendants_cache_cron_worker.rb
@@ -68,7 +68,7 @@ module Namespaces
def persist(ids_to_cache)
ids_to_cache.each_slice(PERSIST_SLICE_SIZE) do |slice|
- Namespaces::Descendants.upsert_all(slice.map { |id| { namespace_id: id } })
+ Namespaces::Descendants.upsert_all(slice.map { |id| { namespace_id: id, outdated_at: Time.current } })
end
end
diff --git a/config/feature_flags/development/vscode_web_ide.yml b/config/feature_flags/development/vscode_web_ide.yml
deleted file mode 100644
index 93f4141ed97..00000000000
--- a/config/feature_flags/development/vscode_web_ide.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: vscode_web_ide
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95169
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371084
-milestone: '15.4'
-type: development
-group: group::ide
-default_enabled: true
diff --git a/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb b/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb
index 5eb372af695..1ab1446dc02 100644
--- a/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb
+++ b/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb
@@ -5,9 +5,11 @@ class GenerateCiJobTokenSigningKey < Gitlab::Database::Migration[2.2]
restrict_gitlab_migration gitlab_schema: :gitlab_main
class ApplicationSetting < MigrationRecord
+ include Gitlab::EncryptedAttribute
+
attr_encrypted :ci_job_token_signing_key, {
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb b/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb
index a0151b6be4b..54f0db9746b 100644
--- a/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb
+++ b/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb
@@ -8,9 +8,11 @@ class RegenerateCiJobTokenSigningKey < Gitlab::Database::Migration[2.2]
restrict_gitlab_migration gitlab_schema: :gitlab_main
class ApplicationSetting < MigrationRecord
+ include Gitlab::EncryptedAttribute
+
attr_encrypted :ci_job_token_signing_key, {
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/db/migrate/20250228183319_migrate_vscode_extension_marketplace_feature_flag_to_data.rb b/db/migrate/20250228183319_migrate_vscode_extension_marketplace_feature_flag_to_data.rb
index eb0a681f273..68b6631177e 100644
--- a/db/migrate/20250228183319_migrate_vscode_extension_marketplace_feature_flag_to_data.rb
+++ b/db/migrate/20250228183319_migrate_vscode_extension_marketplace_feature_flag_to_data.rb
@@ -47,7 +47,6 @@ class MigrateVSCodeExtensionMarketplaceFeatureFlagToData < Gitlab::Database::Mig
Feature.enabled?(:web_ide_extensions_marketplace, nil) &&
# NOTE: We only want to migrate instances that have **explicitly** opted in to the early
# extensions marketplace experience (not just enabled by default feature flag).
- Feature.persisted_name?(:web_ide_extensions_marketplace) &&
- Feature.enabled?(:vscode_web_ide, nil)
+ Feature.persisted_name?(:web_ide_extensions_marketplace)
end
end
diff --git a/db/post_migrate/20250422093734_outdate_namespace_descendants_cache.rb b/db/post_migrate/20250422093734_outdate_namespace_descendants_cache.rb
new file mode 100644
index 00000000000..9b346a4743d
--- /dev/null
+++ b/db/post_migrate/20250422093734_outdate_namespace_descendants_cache.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class OutdateNamespaceDescendantsCache < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ milestone '18.0'
+
+ def up
+ table = Gitlab::Database::PostgresPartitionedTable.find_by_name_in_current_schema("namespace_descendants")
+ table.postgres_partitions.each do |partition|
+ partition_name = "#{quote_table_name(partition.schema)}.#{quote_table_name(partition.name)}"
+ with_lock_retries do
+ execute "UPDATE #{partition_name} SET outdated_at = NOW()"
+ end
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20250422093734 b/db/schema_migrations/20250422093734
new file mode 100644
index 00000000000..aa0a46ac57d
--- /dev/null
+++ b/db/schema_migrations/20250422093734
@@ -0,0 +1 @@
+d5ffdd5d041245f597dd74a033354c5302a808d8d03014a2f65e57be0a2fba16
\ No newline at end of file
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index f7c05a6072f..adbadb04262 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -558,7 +558,7 @@ Returns [`CurrentUser`](#currentuser).
### `Query.customField`
-Find a custom field by its ID. Available only when feature flag `custom_fields_feature` is enabled.
+Find a custom field by its ID.
{{< details >}}
**Introduced** in GitLab 17.10.
@@ -27751,7 +27751,7 @@ four standard [pagination arguments](#pagination-arguments):
##### `Group.customField`
-A custom field configured for the group. Available only when feature flag `custom_fields_feature` is enabled.
+A custom field configured for the group.
{{< details >}}
**Introduced** in GitLab 17.6.
@@ -27768,7 +27768,7 @@ Returns [`CustomField`](#customfield).
##### `Group.customFields`
-Custom fields configured for the group. Available only when feature flag `custom_fields_feature` is enabled.
+Custom fields configured for the group.
{{< details >}}
**Introduced** in GitLab 17.5.
@@ -32870,7 +32870,7 @@ four standard [pagination arguments](#pagination-arguments):
##### `Namespace.customFields`
-Custom fields configured for the namespace. Available only when feature flag `custom_fields_feature` is enabled.
+Custom fields configured for the namespace.
{{< details >}}
**Introduced** in GitLab 17.10.
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 54bd5c62ac2..4b264c0ba0f 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -311,6 +311,7 @@ Parameters:
| `top_level_only` | boolean | no | Limit to top-level groups, excluding all subgroups |
| `repository_storage` | string | no | Filter by repository storage used by the group _(administrators only)_. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/419643) in GitLab 16.3. Premium and Ultimate only. |
| `marked_for_deletion_on` | date | no | Filter by date when group was marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429315) in GitLab 17.1. Premium and Ultimate only. |
+| `active` | boolean | no | Limit by groups that are not archived and not marked for deletion. |
```plaintext
GET /groups
@@ -929,6 +930,7 @@ Parameters:
| `owned` | boolean | no | Limit to groups explicitly owned by the current user |
| `min_access_level` | integer | no | Limit to groups where current user has at least this [role (`access_level`)](members.md#roles) |
| `all_available` | boolean | no | When `true`, returns all accessible groups. When `false`, returns only groups where the user is a member. Defaults to `false` for users, `true` for administrators. Unauthenticated requests always return all public groups. The `owned` and `min_access_level` attributes take precedence. |
+| `active` | boolean | no | Limit by groups that are not archived and not marked for deletion. |
```plaintext
GET /groups/:id/subgroups
@@ -1006,6 +1008,7 @@ Parameters:
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (administrators only) |
| `owned` | boolean | no | Limit to groups explicitly owned by the current user |
| `min_access_level` | integer | no | Limit to groups where current user has at least this [role (`access_level`)](members.md#roles) |
+| `active` | boolean | no | Limit by groups that are not archived and not marked for deletion. |
```plaintext
GET /groups/:id/descendant_groups
diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml
index 1d37055ddba..a0d00906a3b 100644
--- a/doc/api/openapi/openapi_v2.yaml
+++ b/doc/api/openapi/openapi_v2.yaml
@@ -1061,6 +1061,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by groups that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: repository_storage
description: Filter by repository storage used by the group
@@ -1800,6 +1805,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by groups that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: repository_storage
description: Filter by repository storage used by the group
@@ -1937,6 +1947,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by groups that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: repository_storage
description: Filter by repository storage used by the group
@@ -31969,6 +31984,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by projects that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: wiki_checksum_failed
description: Limit by projects where wiki checksum is failed
@@ -32452,6 +32472,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by projects that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: wiki_checksum_failed
description: Limit by projects where wiki checksum is failed
@@ -39917,6 +39942,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by projects that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: wiki_checksum_failed
description: Limit by projects where wiki checksum is failed
@@ -40248,6 +40278,11 @@ paths:
type: string
format: date
required: false
+ - in: query
+ name: active
+ description: Limit by projects that are not archived and not marked for deletion
+ type: boolean
+ required: false
- in: query
name: wiki_checksum_failed
description: Limit by projects where wiki checksum is failed
diff --git a/doc/api/projects.md b/doc/api/projects.md
index c0fff27edde..bcc17931182 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -369,39 +369,40 @@ GET /projects
Supported attributes:
-| Attribute | Type | Required | Description |
-|:------------------------------|:---------|:---------|:------------|
-| `archived` | boolean | No | Limit by archived status. |
-| `id_after` | integer | No | Limit results to projects with IDs greater than the specified ID. |
-| `id_before` | integer | No | Limit results to projects with IDs less than the specified ID. |
-| `imported` | boolean | No | Limit results to projects which were imported from external systems by current user. |
-| `include_hidden` | boolean | No | Include hidden projects. _(administrators only)_ Premium and Ultimate only. |
-| `include_pending_delete` | boolean | No | Include projects pending deletion. _(administrators only)_ |
-| `last_activity_after` | datetime | No | Limit results to projects with last activity after specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
-| `last_activity_before` | datetime | No | Limit results to projects with last activity before specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
-| `membership` | boolean | No | Limit by projects that the current user is a member of. |
-| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
+| Attribute | Type | Required | Description |
+|:------------------------------|:---------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `archived` | boolean | No | Limit by archived status. |
+| `id_after` | integer | No | Limit results to projects with IDs greater than the specified ID. |
+| `id_before` | integer | No | Limit results to projects with IDs less than the specified ID. |
+| `imported` | boolean | No | Limit results to projects which were imported from external systems by current user. |
+| `include_hidden` | boolean | No | Include hidden projects. _(administrators only)_ Premium and Ultimate only. |
+| `include_pending_delete` | boolean | No | Include projects pending deletion. _(administrators only)_ |
+| `last_activity_after` | datetime | No | Limit results to projects with last activity after specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
+| `last_activity_before` | datetime | No | Limit results to projects with last activity before specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
+| `membership` | boolean | No | Limit by projects that the current user is a member of. |
+| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, `last_activity_at`, or `similarity` fields. `repository_size`, `storage_size`, `packages_size` or `wiki_size` fields are only allowed for administrators. `similarity` is only available when searching and is limited to projects that the current user is a member of. Default is `created_at`. |
-| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
-| `repository_checksum_failed` | boolean | No | Limit projects where the repository checksum calculation has failed. Premium and Ultimate only. |
-| `repository_storage` | string | No | Limit results to projects stored on `repository_storage`. _(administrators only)_ |
-| `search_namespaces` | boolean | No | Include ancestor namespaces when matching search criteria. Default is `false`. |
-| `search` | string | No | Return list of projects with a `path`, `name`, or `description` matching the search criteria (case-insensitive, substring match). Multiple terms can be provided, separated by an escaped space, either `+` or `%20`, and will be ANDed together. Example: `one+two` will match substrings `one` and `two` (in any order). |
-| `simple` | boolean | No | Return only limited fields for each project. This operation is a no-op without authentication where only simple fields are returned. |
-| `sort` | string | No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
-| `starred` | boolean | No | Limit by projects starred by the current user. |
-| `statistics` | boolean | No | Include project statistics. Available only to users with at least the Reporter role. |
-| `topic_id` | integer | No | Limit results to projects with the assigned topic given by the topic ID. |
-| `topic` | string | No | Comma-separated topic names. Limit results to projects that match all of given topics. See `topics` attribute. |
-| `updated_after` | datetime | No | Limit results to projects last updated after the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
-| `updated_before` | datetime | No | Limit results to projects last updated before the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
-| `visibility` | string | No | Limit by visibility `public`, `internal`, or `private`. |
-| `wiki_checksum_failed` | boolean | No | Limit projects where the wiki checksum calculation has failed. Premium and Ultimate only. |
-| `with_custom_attributes` | boolean | No | Include [custom attributes](custom_attributes.md) in response. _(administrator only)_ |
-| `with_issues_enabled` | boolean | No | Limit by enabled issues feature. |
-| `with_merge_requests_enabled` | boolean | No | Limit by enabled merge requests feature. |
-| `with_programming_language` | string | No | Limit by projects which use the given programming language. |
-| `marked_for_deletion_on` | date | No | Filter by date when project was marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463939) in GitLab 17.1. Premium and Ultimate only. |
+| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
+| `repository_checksum_failed` | boolean | No | Limit projects where the repository checksum calculation has failed. Premium and Ultimate only. |
+| `repository_storage` | string | No | Limit results to projects stored on `repository_storage`. _(administrators only)_ |
+| `search_namespaces` | boolean | No | Include ancestor namespaces when matching search criteria. Default is `false`. |
+| `search` | string | No | Return list of projects with a `path`, `name`, or `description` matching the search criteria (case-insensitive, substring match). Multiple terms can be provided, separated by an escaped space, either `+` or `%20`, and will be ANDed together. Example: `one+two` will match substrings `one` and `two` (in any order). |
+| `simple` | boolean | No | Return only limited fields for each project. This operation is a no-op without authentication where only simple fields are returned. |
+| `sort` | string | No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
+| `starred` | boolean | No | Limit by projects starred by the current user. |
+| `statistics` | boolean | No | Include project statistics. Available only to users with at least the Reporter role. |
+| `topic_id` | integer | No | Limit results to projects with the assigned topic given by the topic ID. |
+| `topic` | string | No | Comma-separated topic names. Limit results to projects that match all of given topics. See `topics` attribute. |
+| `updated_after` | datetime | No | Limit results to projects last updated after the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
+| `updated_before` | datetime | No | Limit results to projects last updated before the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
+| `visibility` | string | No | Limit by visibility `public`, `internal`, or `private`. |
+| `wiki_checksum_failed` | boolean | No | Limit projects where the wiki checksum calculation has failed. Premium and Ultimate only. |
+| `with_custom_attributes` | boolean | No | Include [custom attributes](custom_attributes.md) in response. _(administrator only)_ |
+| `with_issues_enabled` | boolean | No | Limit by enabled issues feature. |
+| `with_merge_requests_enabled` | boolean | No | Limit by enabled merge requests feature. |
+| `with_programming_language` | string | No | Limit by projects which use the given programming language. |
+| `marked_for_deletion_on` | date | No | Filter by date when project was marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463939) in GitLab 17.1. Premium and Ultimate only. |
+| `active` | boolean | No | Limit by projects that are not archived and not marked for deletion. |
This endpoint supports [keyset pagination](rest/_index.md#keyset-based-pagination) for selected `order_by` options.
diff --git a/doc/integration/exact_code_search/zoekt.md b/doc/integration/exact_code_search/zoekt.md
index d0d045116c4..8442ce96819 100644
--- a/doc/integration/exact_code_search/zoekt.md
+++ b/doc/integration/exact_code_search/zoekt.md
@@ -72,6 +72,20 @@ To enable [exact code search](../../user/search/exact_code_search.md) in GitLab:
## Check indexing status
+{{< history >}}
+
+- Stopping indexing when Zoekt node storage exceeds the critical watermark [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/504945) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `zoekt_critical_watermark_stop_indexing`. Disabled by default.
+- [Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/505334) in GitLab 18.0.
+
+{{< /history >}}
+
+{{< alert type="flag" >}}
+
+The availability of this feature is controlled by a feature flag.
+For more information, see the history.
+
+{{< /alert >}}
+
Prerequisites:
- You must have administrator access to the instance.
diff --git a/doc/user/work_items/custom_fields.md b/doc/user/work_items/custom_fields.md
index 6881f815fab..402074e6c33 100644
--- a/doc/user/work_items/custom_fields.md
+++ b/doc/user/work_items/custom_fields.md
@@ -10,7 +10,6 @@ title: Custom fields
- Tier: Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed
-- Status: Beta
{{< /details >}}
@@ -18,16 +17,10 @@ title: Custom fields
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/479571) in GitLab 17.11 [with a flag](../../administration/feature_flags.md) named `custom_fields_feature`.
Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated.
+- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/479571) in GitLab 18.0. Feature flag `custom_fields_feature` removed.
{{< /history >}}
-{{< alert type="flag" >}}
-
-The availability of this feature is controlled by a feature flag.
-For more information, see the history.
-
-{{< /alert >}}
-
Custom fields add specialized information to work items, such as issues and epics, that match your specific planning needs.
Configure custom fields for a group to track data points like business value, risk assessment, priority ranking, or team attributes.
These fields appear in all work items across the group, its subgroups, and projects.
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f6295634344..3c346c22a73 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -33,6 +33,7 @@ module API
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user'
optional :top_level_only, type: Boolean, desc: 'Only include top-level groups'
optional :marked_for_deletion_on, type: Date, desc: 'Return groups that are marked for deletion on this date'
+ optional :active, type: Boolean, desc: 'Limit by groups that are not archived and not marked for deletion'
use :optional_group_list_params_ee
use :pagination
end
@@ -61,7 +62,8 @@ module API
[:all_available,
:custom_attributes,
:owned, :min_access_level,
- :include_parent_descendants, :search, :visibility, :archived, :marked_for_deletion_on]
+ :include_parent_descendants, :search, :visibility, :archived,
+ :active, :marked_for_deletion_on]
end
# This is a separate method so that EE can extend its behaviour, without
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3c403b471b8..ce38a30e7ab 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -810,6 +810,7 @@ module API
finder_params[:non_public] = true if params[:membership].present?
finder_params[:starred] = true if params[:starred].present?
finder_params[:archived] = archived_param unless params[:archived].nil?
+ finder_params[:active] = params[:active] unless params[:active].nil?
finder_params
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 715027f9e53..fb5a3f214e9 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -181,6 +181,7 @@ module API
optional :updated_after, type: DateTime, desc: 'Return projects updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :include_pending_delete, type: Boolean, desc: 'Include projects in pending delete state. Can only be set by admins'
optional :marked_for_deletion_on, type: Date, desc: 'Date when the project was marked for deletion'
+ optional :active, type: Boolean, desc: 'Limit by projects that are not archived and not marked for deletion'
use :optional_filter_params_ee
end
diff --git a/lib/gitlab/database/migration.rb b/lib/gitlab/database/migration.rb
index 850476f8aad..36ad50efc0d 100644
--- a/lib/gitlab/database/migration.rb
+++ b/lib/gitlab/database/migration.rb
@@ -79,6 +79,10 @@ module Gitlab
include Gitlab::Database::Migrations::MilestoneMixin
end
+ class V2_3 < V2_2
+ include Gitlab::Database::MigrationHelpers::RequireDisableDdlTransactionForMultipleLocks
+ end
+
def self.[](version)
version = version.to_s
name = "V#{version.tr('.', '_')}"
@@ -89,7 +93,7 @@ module Gitlab
# The current version to be used in new migrations
def self.current_version
- 2.2
+ 2.3
end
end
end
diff --git a/lib/gitlab/database/migration_helpers/require_disable_ddl_transaction_for_multiple_locks.rb b/lib/gitlab/database/migration_helpers/require_disable_ddl_transaction_for_multiple_locks.rb
new file mode 100644
index 00000000000..63226c1ce22
--- /dev/null
+++ b/lib/gitlab/database/migration_helpers/require_disable_ddl_transaction_for_multiple_locks.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module MigrationHelpers
+ module RequireDisableDdlTransactionForMultipleLocks
+ extend ActiveSupport::Concern
+
+ LOCK_ACQUIRING_COMMANDS = %w[ALTER CREATE DROP TRUNCATE LOCK UPDATE DELETE INSERT].freeze
+
+ # Reference: https://www.postgresql.org/docs/current/explicit-locking.html
+ LOCK_TYPES = {
+ high_severity: [
+ 'AccessExclusiveLock', # Conflicts with all lock modes
+ 'ExclusiveLock' # Conflicts with all except ROW SHARE
+ ],
+
+ low_severity: [
+ 'RowShareLock', # Conflicts with EXCLUSIVE
+ 'AccessShareLock' # Conflicts with ACCESS EXCLUSIVE only
+ ]
+ }.freeze
+
+ class_methods do
+ def skip_require_disable_ddl_transactions!
+ @skip_require_disable_ddl_transactions = true
+ end
+
+ def skip_require_disable_ddl_transactions?
+ @skip_require_disable_ddl_transactions
+ end
+ end
+
+ def exec_migration(connection, direction)
+ return super if should_skip_check?
+
+ # In-memory tracking structures
+ statement_tracking = []
+ tables_locked_up_till_now = Set.new
+
+ begin
+ # Subscribe to SQL execution to track each statement
+ subscription_id = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ sql = event.payload[:sql].strip
+
+ next if should_skip_sql_statement?(sql)
+
+ newly_locked_tables = []
+ if likely_to_acquire_locks?(sql)
+ newly_locked_tables = check_current_locks(connection).excluding(tables_locked_up_till_now.to_a)
+ end
+
+ newly_locked_tables.each { |table| tables_locked_up_till_now.add(table) }
+
+ # Record new statement
+ current_statement = {
+ number: 1 + statement_tracking.size,
+ sql: sql,
+ locked_tables: newly_locked_tables.uniq
+ }
+ statement_tracking << current_statement
+ end
+
+ # Run the migration
+ super.tap do
+ # After the migration completes, analyze the collected lock data
+ verify_single_table_per_statement(statement_tracking)
+ end
+ ensure
+ # Cleanup
+ ActiveSupport::Notifications.unsubscribe(subscription_id) if subscription_id
+ end
+ end
+
+ private
+
+ def should_skip_check?
+ return true if disable_ddl_transaction
+
+ self.class.skip_require_disable_ddl_transactions?
+ end
+
+ def should_skip_sql_statement?(sql)
+ sql.empty? ||
+ sql.start_with?('SET', 'BEGIN', 'COMMIT', 'ROLLBACK') ||
+ sql.include?('pg_locks') ||
+ (sql.start_with?('SELECT') && sql.exclude?('FOR UPDATE') && sql.exclude?('FOR SHARE'))
+ end
+
+ def likely_to_acquire_locks?(sql)
+ first_word = sql.split(' ').first&.upcase
+
+ LOCK_ACQUIRING_COMMANDS.include?(first_word) ||
+ sql.include?('FOR UPDATE') ||
+ sql.include?('FOR SHARE') ||
+ sql.match?(/CREATE\s+(OR\s+REPLACE\s+)?TRIGGER/i)
+ end
+
+ def check_current_locks(connection)
+ # Get current locks excluding system tables and read-only locks
+ low_severity_locks = LOCK_TYPES[:low_severity].map { |lock| connection.quote(lock) }.join(', ')
+
+ locks = connection.execute(<<-SQL)
+ SELECT DISTINCT relation::regclass AS table_name
+ FROM pg_locks
+ WHERE relation IS NOT NULL
+ AND pid = pg_backend_pid()
+ AND relation::regclass::text NOT LIKE 'pg_%'
+ AND relation::regclass::text NOT LIKE 'information_schema.%'
+ AND relation::regclass::text NOT IN ('schema_migrations', 'ar_internal_metadata')
+ AND mode NOT IN (#{low_severity_locks})
+ SQL
+
+ locks.pluck('table_name').map(&:to_s).uniq
+ end
+
+ def verify_single_table_per_statement(statement_tracking)
+ # Get all tables locked across all statements
+ table_lock_statements = []
+
+ statement_tracking.each do |statement|
+ # Skip statements with no locks
+ next if statement[:locked_tables].empty?
+
+ # Check if this specific statement locked tables
+ table_lock_statements << statement if statement[:locked_tables].any?
+ end
+
+ # Check if we have locks on multiple tables across the entire migration
+ return unless table_lock_statements.many?
+
+ error_message = ["This migration locks multiple tables across different statements:"]
+ error_message << table_lock_statements.flatten.uniq.to_a.join(', ')
+
+ error_message << "\nTables locked by each statement:"
+ statement_tracking.each do |stmt|
+ next if stmt[:locked_tables].empty?
+
+ error_message << " Statement ##{stmt[:number]}: #{stmt[:locked_tables].join(', ')}"
+ error_message << " SQL: #{stmt[:sql]}"
+ end
+
+ error_message << "\nPlease do one of the following:"
+ error_message << " - Split this migration into smaller migrations that each lock only a single table"
+ error_message << " - Disable the outer transaction by calling disable_ddl_transaction!"
+ error_message <<
+ " - Disable check if you feel it is a false positive by calling skip_require_disable_ddl_transactions!"
+ raise error_message.join("\n")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 0dacd8ad920..b418c075a39 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -81,7 +81,6 @@ module Gitlab
# Initialize gon.features with any flags that should be
# made globally available to the frontend
- push_frontend_feature_flag(:vscode_web_ide, current_user)
push_frontend_feature_flag(:ui_for_organizations, current_user)
push_frontend_feature_flag(:organization_switching, current_user)
push_frontend_feature_flag(:find_and_replace, current_user)
diff --git a/lib/web_ide/default_oauth_application.rb b/lib/web_ide/default_oauth_application.rb
index 959db953a8b..389ad526412 100644
--- a/lib/web_ide/default_oauth_application.rb
+++ b/lib/web_ide/default_oauth_application.rb
@@ -3,10 +3,6 @@
module WebIde
module DefaultOauthApplication
class << self
- def feature_enabled?(current_user)
- Feature.enabled?(:vscode_web_ide, current_user)
- end
-
def oauth_application
application_settings.web_ide_oauth_application
end
diff --git a/lib/web_ide/extension_marketplace.rb b/lib/web_ide/extension_marketplace.rb
index a2da3bb3731..5791bede7b2 100644
--- a/lib/web_ide/extension_marketplace.rb
+++ b/lib/web_ide/extension_marketplace.rb
@@ -8,8 +8,7 @@ module WebIde
def self.feature_enabled_for_any_user?
# note: Intentionally pass `nil` here since we don't have a user in scope
feature_enabled_from_application_settings?(user: nil) &&
- feature_flag_enabled_for_any_actor?(:web_ide_extensions_marketplace) &&
- feature_flag_enabled_for_any_actor?(:vscode_web_ide)
+ feature_flag_enabled_for_any_actor?(:web_ide_extensions_marketplace)
end
# Returns true if the extensions marketplace feature is enabled for the given user
@@ -93,8 +92,7 @@ module WebIde
# @param user [User]
# @return [Boolean]
def self.feature_enabled_from_flags?(user:)
- Feature.enabled?(:web_ide_extensions_marketplace, user) &&
- Feature.enabled?(:vscode_web_ide, user)
+ Feature.enabled?(:web_ide_extensions_marketplace, user)
end
private_class_method :feature_flag_enabled_for_any_actor?, :should_use_application_settings?,
diff --git a/rubocop/cop/migration/versioned_migration_class.rb b/rubocop/cop/migration/versioned_migration_class.rb
index 3fc1d595398..00c4266f19b 100644
--- a/rubocop/cop/migration/versioned_migration_class.rb
+++ b/rubocop/cop/migration/versioned_migration_class.rb
@@ -8,8 +8,8 @@ module RuboCop
class VersionedMigrationClass < RuboCop::Cop::Base
include MigrationHelpers
- ENFORCED_SINCE = 2023_11_01_02_15_00
- CURRENT_MIGRATION_VERSION = 2.2 # Should be the same value as Gitlab::Database::Migration.current_version
+ ENFORCED_SINCE = 2025_04_27_00_00_00
+ CURRENT_MIGRATION_VERSION = 2.3 # Should be the same value as Gitlab::Database::Migration.current_version
DOC_LINK = "https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning"
MSG_INHERIT = "Don't inherit from ActiveRecord::Migration or old versions of Gitlab::Database::Migration. " \
diff --git a/spec/controllers/concerns/rapid_diffs/streaming_resource_spec.rb b/spec/controllers/concerns/rapid_diffs/streaming_resource_spec.rb
index 697ecb1fdfb..1dcabf7a7c5 100644
--- a/spec/controllers/concerns/rapid_diffs/streaming_resource_spec.rb
+++ b/spec/controllers/concerns/rapid_diffs/streaming_resource_spec.rb
@@ -49,33 +49,46 @@ RSpec.describe RapidDiffs::StreamingResource, type: :controller, feature_categor
end
end
- describe '#stream_diff_files' do
+ describe '#diffs' do
let(:controller_instance) { controller.new }
let(:mock_resource) { instance_double(::Commit) }
let(:mock_diffs) { instance_double(Gitlab::Diff::FileCollection::Commit, diff_files: diff_files) }
- let(:diff_files) { [] }
- let(:response) do
- instance_double(ActionDispatch::Response,
- stream: instance_double(ActionDispatch::Response::Buffer, write: nil))
- end
+ let(:diff_files) { [{}] }
+ let(:stream) { instance_double(ActionDispatch::Response::Buffer, write: nil, close: nil) }
+ let(:response) { instance_double(ActionDispatch::Response, stream: stream) }
let(:empty_state_html) { 'No changes' }
+ let(:diffs_html) { '' }
before do
- allow(controller_instance).to receive_messages(resource: mock_resource, response: response)
- allow(controller_instance).to receive(:view_context)
+ allow(controller_instance).to receive_messages(
+ resource: mock_resource,
+ response: response,
+ rapid_diffs_enabled?: true,
+ view_context: nil,
+ stream_headers: nil,
+ params: ActionController::Parameters.new)
+ allow(controller_instance).to receive_message_chain(:helpers, :diff_view).and_return('inline')
allow(mock_resource).to receive(:diffs_for_streaming).and_return(mock_diffs)
+ allow(RapidDiffs::DiffFileComponent).to receive_message_chain(:with_collection, :render_in)
+ .and_return(diffs_html)
+ end
+
+ it 'renders diffs' do
+ controller_instance.send(:diffs)
+ expect(response.stream).to have_received(:write).with(diffs_html)
end
context 'when no diffs and no offset' do
+ let(:diff_files) { [] }
+
before do
allow(controller_instance).to receive(:params).and_return(ActionController::Parameters.new)
allow(RapidDiffs::EmptyStateComponent).to receive_message_chain(:new, :render_in).and_return(empty_state_html)
end
it 'renders empty state' do
- controller_instance.send(:stream_diff_files, {})
-
+ controller_instance.send(:diffs)
expect(response.stream).to have_received(:write).with(empty_state_html)
end
end
@@ -85,10 +98,9 @@ RSpec.describe RapidDiffs::StreamingResource, type: :controller, feature_categor
allow(controller_instance).to receive(:params).and_return(ActionController::Parameters.new(offset: '5'))
end
- it 'does not render empty state' do
- expect(RapidDiffs::EmptyStateComponent).not_to receive(:new)
-
- controller_instance.send(:stream_diff_files, {})
+ it 'renders diffs' do
+ controller_instance.send(:diffs)
+ expect(response.stream).to have_received(:write).with(diffs_html)
end
end
end
diff --git a/spec/db/migration_spec.rb b/spec/db/migration_spec.rb
index 784f8753893..d7cc31cbd23 100644
--- a/spec/db/migration_spec.rb
+++ b/spec/db/migration_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe 'Migrations Validation', feature_category: :database do
# The range describes the timestamps that given migration helper can be used
let(:all_migration_classes) do
{
+ 2025_04_18_17_31_00.. => Gitlab::Database::Migration[2.3],
2023_10_10_02_15_00.. => Gitlab::Database::Migration[2.2],
2022_12_01_02_15_00..2023_11_01_02_15_00 => Gitlab::Database::Migration[2.1],
2022_01_26_21_06_58..2023_01_11_12_45_12 => Gitlab::Database::Migration[2.0],
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 34842602311..3fec44b4994 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Edit group settings', feature_category: :groups_and_projects do
include Spec::Support::Helpers::ModalHelpers
+ include Features::WebIdeSpecHelpers
let(:user) { create(:user) }
let(:group) { create(:group, path: 'foo') }
@@ -280,7 +281,7 @@ RSpec.describe 'Edit group settings', feature_category: :groups_and_projects do
expect(page).to have_current_path("/-/ide/project/#{group.readme_project.present.path_with_namespace}/edit/main/-/README.md/")
- page.within('.ide') do
+ within_web_ide do
expect(page).to have_text('README.md')
end
end
@@ -300,7 +301,7 @@ RSpec.describe 'Edit group settings', feature_category: :groups_and_projects do
expect(page).to have_current_path("/-/ide/project/#{group.full_path}/gitlab-profile/edit/main/-/README.md/")
- page.within('.ide') do
+ within_web_ide do
expect(page).to have_text('README.md')
end
end
diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb
index 615af4f5c5c..46d1ee96e12 100644
--- a/spec/features/ide_spec.rb
+++ b/spec/features/ide_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe 'IDE', :js, :with_current_organization, feature_category: :web_ide do
include Features::WebIdeSpecHelpers
- let_it_be(:ide_iframe_selector) { '#ide iframe' }
let_it_be(:normal_project) { create(:project, :repository) }
let(:project) { normal_project }
@@ -21,22 +20,10 @@ RSpec.describe 'IDE', :js, :with_current_organization, feature_category: :web_id
sign_in(user)
end
- shared_examples "legacy Web IDE" do
- it 'loads legacy Web IDE', :aggregate_failures do
- expect(page).to have_selector('.context-header', text: project.name)
-
- # Assert new Web IDE is not loaded
- expect(page).not_to have_selector(ide_iframe_selector)
- end
- end
-
- shared_examples "new Web IDE" do
- it 'loads new Web IDE', :aggregate_failures do
- iframe = find(ide_iframe_selector)
-
- page.within_frame(iframe) do
- expect(page).to have_selector('.title', text: project.path.upcase)
-
+ shared_examples "Web IDE" do
+ it 'loads Web IDE', :aggregate_failures do
+ within_web_ide do
+ expect(page).to have_text(project.path.upcase)
# Verify that the built-in GitLab Workflow Extension loads
expect(page).to have_css('#GitLab\\.gitlab-workflow\\.gl\\.status\\.code_suggestions')
end
@@ -58,24 +45,12 @@ RSpec.describe 'IDE', :js, :with_current_organization, feature_category: :web_id
let(:project) { subgroup_project }
before do
- stub_feature_flags(vscode_web_ide: true)
stub_feature_flags(directory_code_dropdown_updates: directory_code_dropdown_updates)
ide_visit(project)
end
- it_behaves_like 'new Web IDE'
- end
-
- describe 'with vscode feature flag off' do
- before do
- stub_feature_flags(vscode_web_ide: false)
- stub_feature_flags(directory_code_dropdown_updates: directory_code_dropdown_updates)
-
- ide_visit(project)
- end
-
- it_behaves_like 'legacy Web IDE'
+ it_behaves_like 'Web IDE'
end
end
end
diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb
index 5fe0f969b89..7f0177ec1a3 100644
--- a/spec/features/profiles/user_edit_preferences_spec.rb
+++ b/spec/features/profiles/user_edit_preferences_spec.rb
@@ -8,11 +8,9 @@ RSpec.describe 'User edit preferences profile', :js, feature_category: :user_pro
# Empty value doesn't change the levels
let(:language_percentage_levels) { nil }
let(:user) { create(:user) }
- let(:vscode_web_ide) { true }
before do
stub_languages_translation_percentage(language_percentage_levels)
- stub_feature_flags(vscode_web_ide: vscode_web_ide)
sign_in(user)
visit(profile_preferences_path)
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index f73fcc774b4..522aea72e57 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -10,8 +10,6 @@ RSpec.describe 'Projects > Files > Project owner sees a link to create a license
let(:project_maintainer) { project.first_owner }
before do
- stub_feature_flags(vscode_web_ide: false)
-
sign_in(project_maintainer)
end
@@ -21,29 +19,8 @@ RSpec.describe 'Projects > Files > Project owner sees a link to create a license
expect(page).to have_current_path("/-/ide/project/#{project.full_path}/edit/master/-/LICENSE", ignore_query: true)
- expect(page).to have_selector('[data-testid="file-templates-bar"]')
-
- select_template('MIT License')
-
- file_content = "Copyright (c) #{Time.zone.now.year} #{project.namespace.human_name}"
-
- expect(find('.monaco-editor')).to have_content('MIT License')
- expect(find('.monaco-editor')).to have_content(file_content)
-
- ide_commit
-
- expect(page).to have_current_path("/-/ide/project/#{project.full_path}/tree/master/-/LICENSE/", ignore_query: true)
-
- expect(page).to have_content('All changes are committed')
-
- license_file = project.repository.blob_at('master', 'LICENSE').data
- expect(license_file).to have_content('MIT License')
- expect(license_file).to have_content(file_content)
- end
-
- def select_template(template)
- click_button 'Choose a template…'
- click_button template
- wait_for_requests
+ within_web_ide do
+ expect(page).to have_text('LICENSE')
+ end
end
end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 919a63e3ef7..0d5803e2729 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :so
include Features::SourceEditorSpecHelpers
include ProjectForksHelper
include Features::BlobSpecHelpers
+ include Features::WebIdeSpecHelpers
include TreeHelper
let_it_be(:json_text) { '{"name":"Best package ever!"}' }
@@ -23,7 +24,6 @@ RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :so
let_it_be(:project_with_crlf) { create(:project, :custom_repo, name: 'Project with crlf', files: { 'crlf_file.txt' => crlf_text }) }
before do
- stub_feature_flags(vscode_web_ide: false)
stub_feature_flags(blob_overflow_menu: false)
sign_in(user)
@@ -210,10 +210,10 @@ RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :so
click_link_or_button('Fork')
- expect_fork_status
-
- expect(page).to have_css('.ide-sidebar-project-title', text: "#{project2.name} #{user.namespace.full_path}/#{project2.path}")
- expect(page).to have_css('.ide .multi-file-tab', text: '.gitignore')
+ within_web_ide do
+ expect(page).to have_text(project2.path.upcase)
+ expect(page).to have_text('.gitignore')
+ end
end
it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do
diff --git a/spec/features/projects/show/user_interacts_with_stars_spec.rb b/spec/features/projects/show/user_interacts_with_stars_spec.rb
index 272c3dabb35..ecc13372329 100644
--- a/spec/features/projects/show/user_interacts_with_stars_spec.rb
+++ b/spec/features/projects/show/user_interacts_with_stars_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe 'Projects > Show > User interacts with project stars', :js, featu
let(:user) { create(:user) }
before do
- stub_feature_flags(vscode_web_ide: false)
sign_in(user)
visit(project_path(project))
end
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index 430cd3449eb..a3ce675ea64 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -6,7 +6,6 @@ RSpec.describe 'Projects > Show > User manages notifications', :js, feature_cate
let(:project) { create(:project, :public, :repository) }
before do
- stub_feature_flags(vscode_web_ide: false)
sign_in(project.first_owner)
end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
deleted file mode 100644
index e073a52f3b4..00000000000
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Multi-file editor new directory', :js, feature_category: :web_ide do
- include Features::WebIdeSpecHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- where(:directory_code_dropdown_updates) do
- [true, false]
- end
-
- with_them do
- before do
- stub_feature_flags(vscode_web_ide: false)
- stub_feature_flags(directory_code_dropdown_updates: directory_code_dropdown_updates)
-
- project.add_maintainer(user)
- sign_in(user)
-
- visit project_tree_path(project, :master)
-
- wait_for_requests
-
- ide_visit_from_link
- end
-
- after do
- set_cookie('new_repo', 'false')
- end
-
- it 'creates directory in current directory' do
- wait_for_all_requests
-
- all('.ide-tree-actions button').last.click
-
- page.within('.modal') do
- find('.form-control').set('folder name')
-
- click_button('Create directory')
- end
-
- expect(page).to have_content('folder name')
-
- first('.ide-tree-actions button').click
-
- page.within('.modal') do
- find('.form-control').set('folder name/file name')
-
- click_button('Create file')
- end
-
- wait_for_requests
-
- find('.js-ide-commit-mode').click
-
- # Compact mode depends on the size of window. If it is shorter than MAX_WINDOW_HEIGHT_COMPACT,
- # (as it is with WEBDRIVER_HEADLESS=0), this initial commit button will exist. Otherwise, if it is
- # taller (as it is by default with chrome headless) then the button will not exist.
- if page.has_css?('[data-testid="begin-commit-button"]')
- find_by_testid('begin-commit-button').click
- end
-
- fill_in('commit-message', with: 'commit message ide')
-
- find(:css, ".js-ide-commit-new-mr input").set(false)
-
- wait_for_requests
-
- page.within '.multi-file-commit-form' do
- click_button('Commit')
-
- wait_for_requests
- end
-
- find('.js-ide-edit-mode').click
-
- expect(page).to have_content('folder name')
- end
- end
-end
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
deleted file mode 100644
index a731249cdb1..00000000000
--- a/spec/features/projects/tree/create_file_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Multi-file editor new file', :js, feature_category: :web_ide do
- include Features::WebIdeSpecHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- where(:directory_code_dropdown_updates) do
- [true, false]
- end
-
- with_them do
- before do
- stub_feature_flags(vscode_web_ide: false)
- stub_feature_flags(directory_code_dropdown_updates: directory_code_dropdown_updates)
-
- project.add_maintainer(user)
- sign_in(user)
-
- visit project_path(project)
-
- wait_for_requests
-
- ide_visit_from_link
- end
-
- after do
- set_cookie('new_repo', 'false')
- end
-
- it 'creates file in current directory' do
- wait_for_all_requests
-
- first('.ide-tree-actions button').click
-
- page.within('.modal') do
- find('.form-control').set('file name')
-
- click_button('Create file')
- end
-
- wait_for_requests
-
- find('.js-ide-commit-mode').click
-
- # Compact mode depends on the size of window. If it is shorter than MAX_WINDOW_HEIGHT_COMPACT,
- # (as it is with WEBDRIVER_HEADLESS=0), this initial commit button will exist. Otherwise, if it is
- # taller (as it is by default with chrome headless) then the button will not exist.
- if page.has_css?('[data-testid="begin-commit-button"]')
- find_by_testid('begin-commit-button').click
- end
-
- fill_in('commit-message', with: 'commit message ide')
-
- find(:css, ".js-ide-commit-new-mr input").set(false)
-
- page.within '.multi-file-commit-form' do
- click_button('Commit')
-
- wait_for_requests
- end
-
- find('.js-ide-edit-mode').click
-
- expect(page).to have_content('file name')
- end
- end
-end
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index e99666ea8be..5d011ae2757 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -135,40 +135,6 @@ RSpec.describe 'Projects tree', :js, feature_category: :web_ide do
end
end
- context 'web IDE' do
- before do
- stub_feature_flags(vscode_web_ide: false)
- end
-
- context 'when directory_code_dropdown_updates is enabled' do
- it 'opens folder in IDE' do
- stub_feature_flags(directory_code_dropdown_updates: true)
-
- visit project_tree_path(project, File.join('master', 'bar'))
- ide_visit_from_link
-
- wait_for_all_requests
- find('.ide-file-list')
- wait_for_requests
- expect(page).to have_selector('.is-open', text: 'bar')
- end
- end
-
- context 'when directory_code_dropdown_updates is disabled' do
- it 'opens folder in IDE' do
- stub_feature_flags(directory_code_dropdown_updates: false)
-
- visit project_tree_path(project, File.join('master', 'bar'))
- ide_visit_from_link
-
- wait_for_all_requests
- find('.ide-file-list')
- wait_for_requests
- expect(page).to have_selector('.is-open', text: 'bar')
- end
- end
- end
-
context 'for subgroups' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
deleted file mode 100644
index 47139013b67..00000000000
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Multi-file editor upload file', :js, feature_category: :web_ide do
- include Features::WebIdeSpecHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') }
- let(:img_file) { File.join(Rails.root, 'spec', 'fixtures', 'dk.png') }
-
- before do
- stub_feature_flags(vscode_web_ide: false)
-
- project.add_maintainer(user)
- sign_in(user)
-
- visit project_tree_path(project, :master)
-
- wait_for_requests
-
- ide_visit_from_link
- end
-
- after do
- set_cookie('new_repo', 'false')
- end
-
- it 'uploads text file', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/415220' do
- wait_for_all_requests
- # make the field visible so capybara can use it
- execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
- attach_file('file-upload', txt_file)
-
- expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt')
- expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline).gsub!(/\s+/, ' '))
- end
-end
diff --git a/spec/features/work_items/shortcuts_work_item_spec.rb b/spec/features/work_items/shortcuts_work_item_spec.rb
index 74701caa975..d97be6f90ef 100644
--- a/spec/features/work_items/shortcuts_work_item_spec.rb
+++ b/spec/features/work_items/shortcuts_work_item_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Work item keyboard shortcuts', :js, feature_category: :team_planning do
+ include Features::WebIdeSpecHelpers
+
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:work_item) { create(:work_item, project: project) }
@@ -82,7 +84,9 @@ RSpec.describe 'Work item keyboard shortcuts', :js, feature_category: :team_plan
new_tab = window_opened_by { find('body').native.send_key('.') }
within_window new_tab do
- expect(page).to have_selector('.ide-view')
+ within_web_ide do
+ expect(page).to have_text(project.path.upcase)
+ end
end
end
end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 4657e10ac1c..e3e5d19bb2e 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -487,6 +487,32 @@ RSpec.describe GroupsFinder, feature_category: :groups_and_projects do
end
end
+ context 'with active' do
+ let_it_be(:active_group) { create(:group, :public) }
+ let_it_be(:marked_for_deletion_group) { create(:group_with_deletion_schedule, :public) }
+ let_it_be(:archived_group) do
+ create(:group, :public, namespace_settings: create(:namespace_settings, archived: true))
+ end
+
+ subject { described_class.new(nil, params).execute.to_a }
+
+ context 'when true' do
+ let(:params) { { active: true } }
+
+ it 'returns active projects only' do
+ is_expected.to contain_exactly(active_group)
+ end
+ end
+
+ context 'when false' do
+ let(:params) { { active: false } }
+
+ it 'returns inactive projects only' do
+ is_expected.to contain_exactly(archived_group, marked_for_deletion_group)
+ end
+ end
+ end
+
describe 'group sorting' do
let_it_be(:all_groups) { create_list(:group, 3, :public) }
diff --git a/spec/frontend/ide/index_spec.js b/spec/frontend/ide/index_spec.js
index 338cc2a6f97..d9027a4e9c8 100644
--- a/spec/frontend/ide/index_spec.js
+++ b/spec/frontend/ide/index_spec.js
@@ -2,7 +2,6 @@ import { startIde } from '~/ide/index';
import { IDE_ELEMENT_ID } from '~/ide/constants';
import { OAuthCallbackDomainMismatchErrorApp } from '~/ide/oauth_callback_domain_mismatch_error';
import { initGitlabWebIDE } from '~/ide/init_gitlab_web_ide';
-import { initLegacyWebIDE } from '~/ide/init_legacy_web_ide';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
@@ -13,7 +12,6 @@ const MOCK_MISMATCH_CALLBACK_URL = 'https://example.com/ide/redirect';
const MOCK_DATA_SET = {
callbackUrls: JSON.stringify([`${TEST_HOST}/-/ide/oauth_redirect`]),
- useNewWebIde: true,
};
/**
*
@@ -43,7 +41,7 @@ describe('startIde', () => {
document.getElementById(IDE_ELEMENT_ID)?.remove();
});
- describe('when useNewWebIde feature flag is true', () => {
+ describe('default', () => {
let ideElement;
beforeEach(async () => {
@@ -66,7 +64,6 @@ describe('startIde', () => {
it('renders error page', async () => {
setupMockIdeElement({
callbackUrls: JSON.stringify([MOCK_MISMATCH_CALLBACK_URL]),
- useNewWebIde: true,
});
await startIde();
@@ -84,7 +81,6 @@ describe('startIde', () => {
callbackUrls: JSON.stringify([
`${parsedUrl.protocol}//${parsedUrl.host.toUpperCase()}/-/ide/oauth_redirect`,
]),
- useNewWebIde: true,
});
await startIde();
@@ -98,7 +94,6 @@ describe('startIde', () => {
it('renders error page', async () => {
setupMockIdeElement({
callbackUrls: JSON.stringify(['/-/ide/oauth_redirect']),
- useNewWebIde: true,
});
await startIde();
@@ -120,21 +115,4 @@ describe('startIde', () => {
expect(initGitlabWebIDE).not.toHaveBeenCalled();
});
});
-
- describe('when useNewWebIde feature flag is false', () => {
- beforeEach(async () => {
- setupMockIdeElement({ useNewWebIde: false });
-
- await startIde();
- });
-
- it('calls initGitlabWebIDE', () => {
- expect(initLegacyWebIDE).toHaveBeenCalledTimes(1);
- expect(initGitlabWebIDE).toHaveBeenCalledTimes(0);
- });
-
- it('does not render error page', () => {
- expect(renderErrorSpy).not.toHaveBeenCalled();
- });
- });
});
diff --git a/spec/frontend/work_items/components/create_work_item_spec.js b/spec/frontend/work_items/components/create_work_item_spec.js
index ced9fce487a..c68ecb44ddb 100644
--- a/spec/frontend/work_items/components/create_work_item_spec.js
+++ b/spec/frontend/work_items/components/create_work_item_spec.js
@@ -64,7 +64,7 @@ describe('Create work item component', () => {
const createWorkItemSuccessHandler = jest.fn().mockResolvedValue(createWorkItemMutationResponse);
const mutationErrorHandler = jest.fn().mockResolvedValue(createWorkItemMutationErrorResponse);
const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
- const workItemQuerySuccessHandler = jest.fn().mockResolvedValue(createWorkItemQueryResponse);
+ const workItemQuerySuccessHandler = jest.fn().mockResolvedValue(createWorkItemQueryResponse());
const namespaceWorkItemTypesHandler = jest
.fn()
.mockResolvedValue(namespaceWorkItemTypesQueryResponse);
diff --git a/spec/frontend/work_items/graphql/resolvers_spec.js b/spec/frontend/work_items/graphql/resolvers_spec.js
index 9819c882f2a..4edc644fd35 100644
--- a/spec/frontend/work_items/graphql/resolvers_spec.js
+++ b/spec/frontend/work_items/graphql/resolvers_spec.js
@@ -55,7 +55,7 @@ describe('work items graphql resolvers', () => {
mockApollo.clients.defaultClient.cache.writeQuery({
query: workItemByIidQuery,
variables: { fullPath: fullPathWithId, iid },
- data: createWorkItemQueryResponse.data,
+ data: createWorkItemQueryResponse().data,
});
mockApolloClient = mockApollo.clients.defaultClient;
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 94dc82c9d03..5002abb6cf0 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -5803,7 +5803,7 @@ export const namespaceGroupsList = {
},
};
-export const createWorkItemQueryResponse = {
+export const createWorkItemQueryResponse = (widgets = []) => ({
data: {
workspace: {
id: 'full-path-epic-id',
@@ -6013,14 +6013,14 @@ export const createWorkItemQueryResponse = {
},
__typename: 'WorkItemWidgetWeight',
},
- customFieldsWidgetResponseFactory(),
+ ...widgets,
],
__typename: 'WorkItem',
},
__typename: 'Namespace',
},
},
-};
+});
export const mockToggleResolveDiscussionResponse = {
data: {
diff --git a/spec/graphql/resolvers/users/participants_resolver_spec.rb b/spec/graphql/resolvers/users/participants_resolver_spec.rb
index 8d1c9a6bd87..c146a413053 100644
--- a/spec/graphql/resolvers/users/participants_resolver_spec.rb
+++ b/spec/graphql/resolvers/users/participants_resolver_spec.rb
@@ -137,10 +137,9 @@ RSpec.describe Resolvers::Users::ParticipantsResolver do
# 1 extra query per source (3 emojis + 2 notes) to fetch participables collection
# 2 extra queries to load work item widgets collection
- # 1 extra query for root_ancestor in custom_fields_feature feature flag check
# 1 extra query to load the project creator to check if they are banned
# 1 extra query to load the invited groups to see if the user is banned from any of them
- expect { query.call }.not_to exceed_query_limit(control_count).with_threshold(10)
+ expect { query.call }.not_to exceed_query_limit(control_count).with_threshold(9)
end
it 'does not execute N+1 for system note metadata relation' do
diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb
index 207c344151d..b1401a3f81b 100644
--- a/spec/helpers/ide_helper_spec.rb
+++ b/spec/helpers/ide_helper_spec.rb
@@ -32,11 +32,12 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
let(:base_data) do
{
- 'use-new-web-ide' => 'false',
'user-preferences-path' => profile_preferences_path,
'sign-in-path' => 'test-sign-in-path',
- 'project' => nil,
- 'preview-markdown-path' => nil
+ 'project-path' => nil,
+ 'new-web-ide-help-page-path' =>
+ help_page_path('user/project/web_ide/_index.md'),
+ 'csp-nonce' => 'test-csp-nonce'
}
end
@@ -47,8 +48,6 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
context 'with project' do
it 'returns hash with parameters' do
- serialized_project = API::Entities::Project.represent(project, current_user: user).to_json
-
expect(
helper.ide_data(project: project, fork_info: nil, params: params)
).to include(base_data.merge(
@@ -56,8 +55,7 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
'branch-name' => params[:branch],
'file-path' => params[:path],
'merge-request' => params[:merge_request_id],
- 'project' => serialized_project,
- 'preview-markdown-path' => Gitlab::Routing.url_helpers.project_preview_markdown_path(project)
+ 'project-path' => project.full_path
))
end
@@ -69,93 +67,51 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
end
end
- context 'with vscode_web_ide=true' do
- let(:base_data) do
- {
- 'use-new-web-ide' => 'true',
- 'user-preferences-path' => profile_preferences_path,
- 'sign-in-path' => 'test-sign-in-path',
- 'new-web-ide-help-page-path' =>
- help_page_path('user/project/web_ide/_index.md'),
- 'csp-nonce' => 'test-csp-nonce'
- }
+ it 'includes editor font configuration' do
+ ide_data = helper.ide_data(project: nil, fork_info: fork_info, params: params)
+ editor_font = ::Gitlab::Json.parse(ide_data.fetch('editor-font'), symbolize_names: true)
+
+ expect(editor_font).to include({
+ fallback_font_family: 'monospace',
+ font_faces: [
+ {
+ family: 'GitLab Mono',
+ display: 'block',
+ src: [{
+ url: a_string_matching(%r{gitlab-mono/GitLabMono-[^I]}),
+ format: 'woff2'
+ }]
+ },
+ {
+ family: 'GitLab Mono',
+ display: 'block',
+ style: 'italic',
+ src: [{
+ url: a_string_matching(%r{gitlab-mono/GitLabMono-Italic}),
+ format: 'woff2'
+ }]
+ }
+ ]
+ })
+ end
+
+ context 'for extension marketplace data' do
+ where(:settings, :expected_settings_hash) do
+ ref(:disabled_vscode_settings) | nil
+ ref(:enabled_vscode_settings) | 'c6620244fe72864fa8d8'
end
- before do
- stub_feature_flags(vscode_web_ide: true)
- end
+ with_them do
+ it 'includes extension marketplace settings and settings context hash' do
+ expect(WebIde::ExtensionMarketplace).to receive(:webide_extension_marketplace_settings)
+ .with(user: user).and_return(settings)
- it 'returns hash' do
- expect(helper.ide_data(project: nil, fork_info: fork_info, params: params))
- .to include(base_data)
- end
+ actual = helper.ide_data(project: nil, fork_info: fork_info, params: params)
- it 'includes editor font configuration' do
- ide_data = helper.ide_data(project: nil, fork_info: fork_info, params: params)
- editor_font = ::Gitlab::Json.parse(ide_data.fetch('editor-font'), symbolize_names: true)
-
- expect(editor_font).to include({
- fallback_font_family: 'monospace',
- font_faces: [
- {
- family: 'GitLab Mono',
- display: 'block',
- src: [{
- url: a_string_matching(%r{gitlab-mono/GitLabMono-[^I]}),
- format: 'woff2'
- }]
- },
- {
- family: 'GitLab Mono',
- display: 'block',
- style: 'italic',
- src: [{
- url: a_string_matching(%r{gitlab-mono/GitLabMono-Italic}),
- format: 'woff2'
- }]
- }
- ]
- })
- end
-
- it 'does not use new web ide if feature flag is disabled' do
- stub_feature_flags(vscode_web_ide: false)
-
- expect(helper.ide_data(project: nil, fork_info: fork_info, params: params))
- .to include('use-new-web-ide' => 'false')
- end
-
- context 'for extension marketplace data' do
- where(:settings, :expected_settings_hash) do
- ref(:disabled_vscode_settings) | nil
- ref(:enabled_vscode_settings) | 'c6620244fe72864fa8d8'
- end
-
- with_them do
- it 'includes extension marketplace settings and settings context hash' do
- expect(WebIde::ExtensionMarketplace).to receive(:webide_extension_marketplace_settings)
- .with(user: user).and_return(settings)
-
- actual = helper.ide_data(project: nil, fork_info: fork_info, params: params)
-
- expect(actual).to include({
- 'extension-marketplace-settings' => settings.to_json,
- 'settings-context-hash' => expected_settings_hash
- })
- end
- end
- end
-
- context 'with project' do
- it 'returns hash with parameters' do
- expect(
- helper.ide_data(project: project, fork_info: nil, params: params)
- ).to include(base_data.merge(
- 'branch-name' => params[:branch],
- 'file-path' => params[:path],
- 'merge-request' => params[:merge_request_id],
- 'fork-info' => nil
- ))
+ expect(actual).to include({
+ 'extension-marketplace-settings' => settings.to_json,
+ 'settings-context-hash' => expected_settings_hash
+ })
end
end
end
@@ -164,10 +120,6 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
describe '#show_web_ide_oauth_callback_mismatch_callout?' do
let_it_be(:oauth_application) { create(:oauth_application, owner: nil) }
- before do
- stub_feature_flags(vscode_web_ide: true)
- end
-
it 'returns false if no Web IDE OAuth application found' do
expect(helper.show_web_ide_oauth_callback_mismatch_callout?).to be false
end
diff --git a/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt b/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt
index 32f6b1843be..7e1f9c808d6 100644
--- a/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt
+++ b/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt
@@ -5,7 +5,7 @@
# Update below commented lines with appropriate values.
-class QueueMyBatchedMigration < Gitlab::Database::Migration[2.2]
+class QueueMyBatchedMigration < Gitlab::Database::Migration[2.3]
milestone '16.6'
# Select the applicable gitlab schema for your batched background migration
diff --git a/spec/lib/generators/model/mocks/migration_file.txt b/spec/lib/generators/model/mocks/migration_file.txt
index d0a5c71ffc3..0681fb287fc 100644
--- a/spec/lib/generators/model/mocks/migration_file.txt
+++ b/spec/lib/generators/model/mocks/migration_file.txt
@@ -3,7 +3,7 @@
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
-class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.2]
+class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.3]
# When using the methods "add_concurrent_index" or "remove_concurrent_index"
# you must disable the use of transactions
# as these methods can not run in an existing transaction.
diff --git a/spec/lib/gitlab/background_migration/encrypt_ci_trigger_token_spec.rb b/spec/lib/gitlab/background_migration/encrypt_ci_trigger_token_spec.rb
index 035220e8407..b01040adadd 100644
--- a/spec/lib/gitlab/background_migration/encrypt_ci_trigger_token_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_ci_trigger_token_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::BackgroundMigration::EncryptCiTriggerToken, feature_categ
ci_trigger.send :attr_encrypted, :encrypted_token_tmp,
attribute: :encrypted_token,
mode: :per_attribute_iv,
- key: ::Settings.attr_encrypted_db_key_base_32,
+ key: Settings.attr_encrypted_db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false
end
diff --git a/spec/lib/gitlab/database/migration_helpers/require_disable_ddl_transaction_for_multiple_locks_spec.rb b/spec/lib/gitlab/database/migration_helpers/require_disable_ddl_transaction_for_multiple_locks_spec.rb
new file mode 100644
index 00000000000..c17533dab0e
--- /dev/null
+++ b/spec/lib/gitlab/database/migration_helpers/require_disable_ddl_transaction_for_multiple_locks_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::MigrationHelpers::RequireDisableDdlTransactionForMultipleLocks,
+ query_analyzers: false, feature_category: :database do
+ let_it_be(:multiple_locks_migration_module) do
+ Module.new do
+ include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
+
+ def partitioned_table
+ :ci_runner_machines
+ end
+
+ def tables
+ %i[
+ instance_type_ci_runner_machines group_type_ci_runner_machines project_type_ci_runner_machines
+ ci_runner_machines
+ ].freeze
+ end
+
+ def up
+ drop_trigger(:ci_runner_machines_archived, :ci_runner_machines_loose_fk_trigger)
+
+ tables.each do |table|
+ untrack_record_deletions(table)
+
+ track_record_deletions_override_table_name(table, partitioned_table)
+ end
+ end
+
+ def down
+ tables.each do |table|
+ untrack_record_deletions(table)
+ end
+
+ execute(<<~SQL.squish)
+ CREATE TRIGGER #{record_deletion_trigger_name(partitioned_table)}
+ AFTER DELETE ON ci_runner_machines_archived REFERENCING OLD TABLE AS old_table
+ FOR EACH STATEMENT
+ EXECUTE FUNCTION #{INSERT_FUNCTION_NAME}();
+ SQL
+ end
+ end
+ end
+
+ let_it_be(:multiple_locks_single_statement_migration_module) do
+ Module.new do
+ def up
+ # Simulate an operation that locks multiple tables
+ execute "LOCK TABLE ci_runners, ci_runner_machines IN ACCESS EXCLUSIVE MODE"
+ end
+
+ def down; end
+ end
+ end
+
+ let(:migration_base_klass) do
+ Class.new(Gitlab::Database::Migration[migration_version])
+ end
+
+ before do
+ stub_const('TestMultipleLocksMigrationModule', multiple_locks_migration_module)
+ stub_const('TestMultipleLocksInSingleStmtMigrationModule', multiple_locks_single_statement_migration_module)
+
+ migration.instance_variable_set(:@_defining_file, 'db/migrate/00000000000000_example.rb')
+ migration.milestone '18.0'
+ end
+
+ context 'when executing migrations' do
+ subject(:migrate_up) { migration.migrate(:up) }
+
+ let(:migration_version) { 2.3 }
+
+ context 'when migration locks multiple tables in single statement' do
+ let(:migration) { migration_base_klass.extend(TestMultipleLocksInSingleStmtMigrationModule) }
+
+ it 'does not raise an error' do
+ expect { migrate_up }.not_to raise_error
+ end
+ end
+
+ context 'when migration locks multiple tables across multiple statements' do
+ let(:migration) { migration_base_klass.extend(TestMultipleLocksMigrationModule) }
+
+ context 'when migration does not include module' do
+ let(:migration_version) { 2.2 }
+
+ it 'does not raise an error' do
+ expect { migrate_up }.not_to raise_error
+ end
+ end
+
+ context 'when migration includes module' do
+ let(:migration_version) { 2.3 }
+
+ it 'fails with error about multiple locks' do
+ expect do
+ migration.migrate(:up)
+ end.to raise_error(/This migration locks multiple tables across different statements/)
+ end
+
+ context 'when migration disables ddl transaction' do
+ let(:migration) do
+ Class.new(Gitlab::Database::Migration[migration_version]) do
+ disable_ddl_transaction!
+
+ extend TestMultipleLocksMigrationModule
+ end
+ end
+
+ it 'does not raise an error' do
+ expect { migrate_up }.not_to raise_error
+ end
+ end
+
+ context 'when migration skips check' do
+ let(:migration) do
+ Class.new(Gitlab::Database::Migration[migration_version]) do
+ skip_require_disable_ddl_transactions!
+
+ extend TestMultipleLocksMigrationModule
+ end
+ end
+
+ it 'does not raise an error' do
+ expect { migrate_up }.not_to raise_error
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/web_ide/default_oauth_application_spec.rb b/spec/lib/web_ide/default_oauth_application_spec.rb
index 3d67ab7f5c7..146b3988d67 100644
--- a/spec/lib/web_ide/default_oauth_application_spec.rb
+++ b/spec/lib/web_ide/default_oauth_application_spec.rb
@@ -6,23 +6,6 @@ RSpec.describe WebIde::DefaultOauthApplication, feature_category: :web_ide do
let_it_be(:current_user) { create(:user) }
let_it_be(:oauth_application) { create(:oauth_application, owner: nil) }
- describe '#feature_enabled?' do
- where(:vscode_web_ide, :expectation) do
- [
- [ref(:current_user), true],
- [false, false]
- ]
- end
-
- with_them do
- it 'returns the expected value' do
- stub_feature_flags(vscode_web_ide: vscode_web_ide)
-
- expect(described_class.feature_enabled?(current_user)).to be(expectation)
- end
- end
- end
-
describe '#oauth_application' do
it 'returns web_ide_oauth_application from application_settings' do
expect(described_class.oauth_application).to be_nil
diff --git a/spec/lib/web_ide/extension_marketplace_spec.rb b/spec/lib/web_ide/extension_marketplace_spec.rb
index 7d542d4c072..19f69a3926b 100644
--- a/spec/lib/web_ide/extension_marketplace_spec.rb
+++ b/spec/lib/web_ide/extension_marketplace_spec.rb
@@ -25,19 +25,17 @@ RSpec.describe WebIde::ExtensionMarketplace, feature_category: :web_ide do
let_it_be_with_reload(:current_user) { create(:user) }
describe 'feature enabled methods' do
- where(:vscode_web_ide, :web_ide_extensions_marketplace, :vscode_extension_marketplace_settings, :app_setting,
+ where(:web_ide_extensions_marketplace, :vscode_extension_marketplace_settings, :app_setting,
:expectation) do
- ref(:current_user) | ref(:current_user) | false | {} | true
- ref(:current_user) | ref(:current_user) | true | {} | false
- ref(:current_user) | ref(:current_user) | true | { enabled: true } | true
- ref(:current_user) | false | false | { enabled: true } | false
- false | ref(:current_user) | false | {} | false
+ ref(:current_user) | false | {} | true
+ ref(:current_user) | true | {} | false
+ ref(:current_user) | true | { enabled: true } | true
+ false | false | { enabled: true } | false
end
with_them do
before do
stub_feature_flags(
- vscode_web_ide: vscode_web_ide,
web_ide_extensions_marketplace: web_ide_extensions_marketplace,
vscode_extension_marketplace_settings: vscode_extension_marketplace_settings
)
@@ -129,8 +127,7 @@ RSpec.describe WebIde::ExtensionMarketplace, feature_category: :web_ide do
before do
stub_feature_flags(
vscode_extension_marketplace_settings: vscode_extension_marketplace_settings,
- web_ide_extensions_marketplace: web_ide_extensions_marketplace,
- vscode_web_ide: true
+ web_ide_extensions_marketplace: web_ide_extensions_marketplace
)
stub_application_setting(vscode_extension_marketplace: app_setting)
diff --git a/spec/migrations/db/post_migrate/20250422093734_outdate_namespace_descendants_cache_spec.rb b/spec/migrations/db/post_migrate/20250422093734_outdate_namespace_descendants_cache_spec.rb
new file mode 100644
index 00000000000..4ca08d60c47
--- /dev/null
+++ b/spec/migrations/db/post_migrate/20250422093734_outdate_namespace_descendants_cache_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe OutdateNamespaceDescendantsCache, migration: :gitlab_main, feature_category: :database do
+ let(:migration) { described_class.new }
+
+ let(:namespace_descendants) { table(:namespace_descendants) }
+
+ let(:outdated_at) { Date.new(2022, 1, 1) }
+ let!(:ns_outdated) { namespace_descendants.create!(namespace_id: 1, outdated_at: outdated_at) }
+ let!(:ns_up_to_date) { namespace_descendants.create!(namespace_id: 2) }
+
+ describe '#up' do
+ it 'bumps all timestamp values' do
+ migrate!
+
+ records = namespace_descendants.all
+ outdated_ats = records.map(&:outdated_at)
+ expect(outdated_ats).to all(be_present)
+ end
+ end
+end
diff --git a/spec/migrations/generate_ci_job_token_signing_key_spec.rb b/spec/migrations/generate_ci_job_token_signing_key_spec.rb
index b260ddea74b..60e900baa68 100644
--- a/spec/migrations/generate_ci_job_token_signing_key_spec.rb
+++ b/spec/migrations/generate_ci_job_token_signing_key_spec.rb
@@ -6,11 +6,13 @@ require_migration!
RSpec.describe GenerateCiJobTokenSigningKey, feature_category: :continuous_integration do
let(:application_settings) do
Class.new(ActiveRecord::Base) do
+ include Gitlab::EncryptedAttribute
+
self.table_name = 'application_settings'
attr_encrypted :ci_job_token_signing_key, {
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb b/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb
index 73acdabdd71..25e75d9052e 100644
--- a/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb
+++ b/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb
@@ -6,11 +6,13 @@ require_migration!
RSpec.describe RegenerateCiJobTokenSigningKey, feature_category: :continuous_integration do
let(:application_settings) do
Class.new(ActiveRecord::Base) do
+ include Gitlab::EncryptedAttribute
+
self.table_name = 'application_settings'
attr_encrypted :ci_job_token_signing_key, {
mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb
index 5ac446fc56a..e60789c6253 100644
--- a/spec/models/concerns/bulk_insert_safe_spec.rb
+++ b/spec/models/concerns/bulk_insert_safe_spec.rb
@@ -69,6 +69,7 @@ RSpec.describe BulkInsertSafe, feature_category: :database do
include BulkInsertSafe
include ShaAttribute
+ include Gitlab::EncryptedAttribute
validates :name, :enum_value, :secret_value, :sha_value, :jsonb_value, presence: true
@@ -81,7 +82,7 @@ RSpec.describe BulkInsertSafe, feature_category: :database do
attr_encrypted :secret_value,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32,
+ key: :db_key_base_32,
insecure_mode: false
attribute :enum_value, default: 'case_1'
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index a25a1136241..0ec6e60c3dc 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1643,6 +1643,30 @@ RSpec.describe Group, feature_category: :groups_and_projects do
it { is_expected.to match_array([subgroup, subsubgroup]) }
end
end
+
+ describe '.active' do
+ let_it_be(:active_group) { create(:group) }
+ let_it_be(:archived_group) { create(:group, namespace_settings: create(:namespace_settings, archived: true)) }
+ let_it_be(:marked_for_deletion_group) { create(:group_with_deletion_schedule) }
+
+ subject { described_class.active }
+
+ it { is_expected.to include(active_group) }
+ it { is_expected.not_to include(marked_for_deletion_group) }
+ it { is_expected.not_to include(archived_group) }
+ end
+
+ describe '.inactive' do
+ let_it_be(:active_group) { create(:group) }
+ let_it_be(:archived_group) { create(:group, namespace_settings: create(:namespace_settings, archived: true)) }
+ let_it_be(:marked_for_deletion_group) { create(:group_with_deletion_schedule) }
+
+ subject { described_class.inactive }
+
+ it { is_expected.to include(archived_group) }
+ it { is_expected.to include(marked_for_deletion_group) }
+ it { is_expected.not_to include(active_group) }
+ end
end
describe '.project_creation_levels_for_user' do
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index 295d8714aba..333af52af12 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -1446,10 +1446,12 @@ RSpec.describe Integration, feature_category: :integrations do
expect(hash['encrypted_properties']).not_to eq(record.encrypted_properties)
expect(hash['encrypted_properties_iv']).not_to eq(record.encrypted_properties_iv)
+ options = record.send(:evaluated_attr_encrypted_options_for, :properties)
+ .merge(iv: hash['encrypted_properties_iv'])
decrypted = described_class.attr_decrypt(
:properties,
hash['encrypted_properties'],
- { iv: hash['encrypted_properties_iv'] }
+ options
)
expect(decrypted).to eq db_props
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 8f0aa706182..3796b2ba3a4 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -100,6 +100,19 @@ RSpec.describe API::Groups, :with_current_organization, feature_category: :group
end
end
+ shared_examples 'API that accepts active parameter for GroupFinder' do
+ [nil, true, false].each do |active_value|
+ it "passes #{active_value.inspect} active parameter to the GroupsFinder" do
+ params = { active: active_value }
+
+ expect(GroupsFinder).to receive(:new)
+ .with(user1, hash_including(active: active_value)).and_call_original
+
+ get api('/groups', user1), params: params
+ end
+ end
+ end
+
describe "GET /groups" do
shared_examples 'groups list N+1' do
it 'avoids N+1 queries', :use_sql_query_cache do
@@ -602,6 +615,8 @@ RSpec.describe API::Groups, :with_current_organization, feature_category: :group
expect(response_groups).to match_array([group1.id, subgroup1.id])
end
end
+
+ it_behaves_like 'API that accepts active parameter for GroupFinder'
end
describe "GET /groups/:id" do
@@ -2816,6 +2831,8 @@ RSpec.describe API::Groups, :with_current_organization, feature_category: :group
let(:parent) { group1 }
let(:endpoint) { api("/groups/#{group1.id}/subgroups", user1) }
end
+
+ it_behaves_like 'API that accepts active parameter for GroupFinder'
end
describe 'GET /groups/:id/descendant_groups' do
@@ -2959,6 +2976,8 @@ RSpec.describe API::Groups, :with_current_organization, feature_category: :group
let(:parent) { group1 }
let(:endpoint) { api("/groups/#{group1.id}/descendant_groups", user1) }
end
+
+ it_behaves_like 'API that accepts active parameter for GroupFinder'
end
describe "POST /groups" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a71cc79c2b8..840a6158856 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -279,6 +279,38 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
end
end
+ context 'when filtering by active parameter' do
+ let_it_be(:marked_for_deletion_project) do
+ create(:project, marked_for_deletion_on: Date.parse('2024-01-01'), namespace: user.namespace)
+ end
+
+ let_it_be(:archived_project) do
+ create(:project, :archived, namespace: user.namespace)
+ end
+
+ context 'when active is true' do
+ it 'returns only non archived and not marked for deletion projects' do
+ get api(path, user), params: { active: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(archived_project.id, marked_for_deletion_project.id)
+ end
+ end
+
+ context 'when active is false' do
+ it 'returns only archived or marked for deletion projects' do
+ get api(path, user), params: { active: false }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(archived_project.id, marked_for_deletion_project.id)
+ end
+ end
+ end
+
shared_examples 'includes container_registry_access_level' do
specify do
project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
diff --git a/spec/requests/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb
index 50f0393fa46..f2a45140b86 100644
--- a/spec/requests/ide_controller_spec.rb
+++ b/spec/requests/ide_controller_spec.rb
@@ -20,7 +20,6 @@ RSpec.describe IdeController, feature_category: :web_ide do
let(:user) { creator }
before do
- stub_feature_flags(vscode_web_ide: true)
sign_in(user)
end
@@ -147,36 +146,10 @@ RSpec.describe IdeController, feature_category: :web_ide do
end
end
- describe 'legacy Web IDE' do
- before do
- stub_feature_flags(vscode_web_ide: false)
- end
+ it 'uses fullscreen layout' do
+ subject
- it 'uses application layout' do
- subject
-
- expect(response).to render_template('layouts/application')
- end
-
- it 'does not create oauth application' do
- expect(Doorkeeper::Application).not_to receive(:new)
-
- subject
-
- expect(web_ide_oauth_application).to be_nil
- end
- end
-
- describe 'vscode IDE' do
- before do
- stub_feature_flags(vscode_web_ide: true)
- end
-
- it 'uses fullscreen layout' do
- subject
-
- expect(response).to render_template('layouts/fullscreen')
- end
+ expect(response).to render_template('layouts/fullscreen')
end
it 'ensures web_ide_oauth_application' do
@@ -242,14 +215,6 @@ RSpec.describe IdeController, feature_category: :web_ide do
expect(response).to have_gitlab_http_status(:ok)
end
-
- it 'with vscode_web_ide flag off, returns not_found' do
- stub_feature_flags(vscode_web_ide: false)
-
- oauth_redirect
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
end
end
diff --git a/spec/requests/projects/merge_requests/diffs_stream_spec.rb b/spec/requests/projects/merge_requests/diffs_stream_spec.rb
index d722ff99202..ad57117bc66 100644
--- a/spec/requests/projects/merge_requests/diffs_stream_spec.rb
+++ b/spec/requests/projects/merge_requests/diffs_stream_spec.rb
@@ -44,8 +44,10 @@ RSpec.describe 'Merge Requests Diffs stream', feature_category: :code_review_wor
context 'when accessed' do
it 'passes hash of options to #diffs_for_streaming' do
expect_next_instance_of(::Projects::MergeRequests::DiffsStreamController) do |controller|
+ context = {}
+ allow(controller).to receive(:view_context).and_return(context)
expect(controller).to receive(:stream_diff_files)
- .with(diff_options_hash)
+ .with(diff_options_hash, context)
.and_call_original
end
diff --git a/spec/rubocop/cop/gitlab/rails/attr_encrypted_spec.rb b/spec/rubocop/cop/gitlab/rails/attr_encrypted_spec.rb
index 53ac1358272..262aaf425ec 100644
--- a/spec/rubocop/cop/gitlab/rails/attr_encrypted_spec.rb
+++ b/spec/rubocop/cop/gitlab/rails/attr_encrypted_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe RuboCop::Cop::Gitlab::Rails::AttrEncrypted, feature_category: :sh
^^^^^^^^^^^^^^^^^^^^^^^ Use `encrypts` over deprecated `attr_encrypted` to encrypt a column. See https://docs.gitlab.com/development/migration_style_guide/#encrypted-attributes
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
- key: Settings.attr_encrypted_db_key_base,
+ key: :db_key_base,
algorithm: 'aes-256-cbc'
end
RUBY
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a68c01c23d8..dc407b9d462 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -310,10 +310,6 @@ RSpec.configure do |config|
# cause spec failures.
stub_feature_flags(gitlab_error_tracking: false)
- # Disable this to avoid the Web IDE modals popping up in tests:
- # https://gitlab.com/gitlab-org/gitlab/-/issues/385453
- stub_feature_flags(vscode_web_ide: false)
-
# Disable `main_branch_over_master` as we migrate
# from `master` to `main` accross our codebase.
# It's done in order to preserve the concistency in tests
diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb
index 32b27864e0b..0ad8abb966f 100644
--- a/spec/support/helpers/features/web_ide_spec_helpers.rb
+++ b/spec/support/helpers/features/web_ide_spec_helpers.rb
@@ -30,97 +30,9 @@ module Features
switch_to_window new_tab
end
- def ide_tree_body
- page.find('.ide-tree-body')
- end
-
- def ide_tree_actions
- page.find('.ide-tree-actions')
- end
-
- def ide_tab_selector(mode)
- ".js-ide-#{mode}-mode"
- end
-
- def ide_folder_row_open?(row)
- row.matches_css?('.folder.is-open')
- end
-
- # Deletes a file by traversing to `path`
- # then clicking the 'Delete' action.
- #
- # - Throws an error if the file is not found
- def ide_delete_file(path)
- container = ide_traverse_to_file(path)
-
- click_file_action(container, 'Delete')
- end
-
- # Opens parent directories until the file at `path`
- # is exposed.
- #
- # - Returns a reference to the file row at `path`
- # - Throws an error if the file is not found
- def ide_traverse_to_file(path)
- paths = path.split('/')
- container = nil
-
- paths.each_with_index do |path, index|
- ide_open_file_row(container) if container
- container = find_file_child(container, path, level: index)
- end
-
- container
- end
-
- def ide_open_file_row(row)
- return if ide_folder_row_open?(row)
-
- row.click
- end
-
- def ide_set_editor_value(value)
- editor_set_value(value)
- end
-
- def ide_commit_tab_selector
- ide_tab_selector('commit')
- end
-
- def ide_commit
- find(ide_commit_tab_selector).click
-
- commit_to_current_branch
- end
-
- private
-
- def file_row_container(row)
- row ? row.find(:xpath, '..') : ide_tree_body
- end
-
- def find_file_child(row, name, level: nil)
- container = file_row_container(row)
- container.find(".file-row[data-level=\"#{level}\"]", text: name)
- end
-
- def click_file_action(row, text)
- row.hover
- dropdown = row.find('.ide-new-btn')
- dropdown.find('button').click
- dropdown.find('button', text: text).click
- end
-
- def commit_to_current_branch(option: 'Commit to master branch', message: '')
- within '.multi-file-commit-form' do
- fill_in('commit-message', with: message) if message
-
- choose(option)
-
- click_button('Commit')
-
- wait_for_requests
- end
+ def within_web_ide(&block)
+ iframe = find('#ide iframe')
+ page.within_frame(iframe, &block)
end
end
end
diff --git a/spec/support/helpers/user_with_namespace_shim.yml b/spec/support/helpers/user_with_namespace_shim.yml
index a9cd4b4fa4a..d02bf6e861d 100644
--- a/spec/support/helpers/user_with_namespace_shim.yml
+++ b/spec/support/helpers/user_with_namespace_shim.yml
@@ -547,8 +547,6 @@
- spec/features/projects/sourcegraph_csp_spec.rb
- spec/features/projects/tags/user_views_tag_spec.rb
- spec/features/projects/tags/user_views_tags_spec.rb
-- spec/features/projects/tree/create_directory_spec.rb
-- spec/features/projects/tree/create_file_spec.rb
- spec/features/projects/tree/rss_spec.rb
- spec/features/projects/tree/tree_show_spec.rb
- spec/features/projects/user_creates_project_spec.rb
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 1c22dbf52cb..88690239c6b 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -110,7 +110,6 @@
- './ee/spec/features/groups_spec.rb'
- './ee/spec/features/groups/wikis_spec.rb'
- './ee/spec/features/groups/wiki/user_views_wiki_empty_spec.rb'
-- './ee/spec/features/ide/user_opens_ide_spec.rb'
- './ee/spec/features/integrations/jira/jira_issues_list_spec.rb'
- './ee/spec/features/issues/blocking_issues_spec.rb'
- './ee/spec/features/issues/epic_in_issue_sidebar_spec.rb'
@@ -3272,11 +3271,8 @@
- './spec/features/projects/tags/user_views_tag_spec.rb'
- './spec/features/projects/tags/user_views_tags_spec.rb'
- './spec/features/projects/terraform_spec.rb'
-- './spec/features/projects/tree/create_directory_spec.rb'
-- './spec/features/projects/tree/create_file_spec.rb'
- './spec/features/projects/tree/rss_spec.rb'
- './spec/features/projects/tree/tree_show_spec.rb'
-- './spec/features/projects/tree/upload_file_spec.rb'
- './spec/features/projects/user_changes_project_visibility_spec.rb'
- './spec/features/projects/user_creates_project_spec.rb'
- './spec/features/projects/user_sees_sidebar_spec.rb'
diff --git a/spec/tooling/ci/changed_files_spec.rb b/spec/tooling/ci/changed_files_spec.rb
index b9dc41f4161..31247772c6f 100644
--- a/spec/tooling/ci/changed_files_spec.rb
+++ b/spec/tooling/ci/changed_files_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe CI::ChangedFiles, feature_category: :tooling do
let(:instance) { described_class.new }
describe '#get_changed_files_in_merged_results_pipeline' do
- let(:git_diff_output) { "file1.js\nfile2.rb\nfile3.vue" }
+ let(:git_diff_output) { "file1.js\nfile2.rb\nfile3.vue\nfile4.graphql" }
before do
allow(instance).to receive(:`)
@@ -18,7 +18,7 @@ RSpec.describe CI::ChangedFiles, feature_category: :tooling do
context 'when git diff is run in a merged results pipeline' do
it 'returns an array when there are changed files' do
expect(instance.get_changed_files_in_merged_results_pipeline)
- .to match_array(['file1.js', 'file2.rb', 'file3.vue'])
+ .to match_array(['file1.js', 'file2.rb', 'file3.vue', 'file4.graphql'])
end
context "when there are no changed files" do
@@ -117,6 +117,175 @@ RSpec.describe CI::ChangedFiles, feature_category: :tooling do
expect(instance.run_eslint_for_changed_files).to eq(0)
end
end
+
+ context 'when a single todo file has been changed' do
+ let(:eslint_command) do
+ ['yarn', 'run', 'lint:eslint', '--no-warn-ignored', '--format', 'gitlab',
+ '.eslint_todo/vue-no-unused-properties.mjs',
+ 'app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue',
+ 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue',
+ 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_edit_note.vue',
+ 'app/assets/javascripts/admin/statistics_panel/components/app.vue',
+ 'app/assets/javascripts/badges/components/badge.vue',
+ 'app/assets/javascripts/badges/components/badge_form.vue',
+ 'app/assets/javascripts/batch_comments/components/draft_note.vue']
+ end
+
+ let(:files) { ['.eslint_todo/vue-no-unused-properties.mjs'] }
+ let(:git_diff_output) do
+ <<-DIFF
+ diff --git a/.eslint_todo/vue-no-unused-properties.mjs b/.eslint_todo/vue-no-unused-properties.mjs
+ index 81f0b2cbcf84..ef936567f2e8 100644
+ --- a/.eslint_todo/vue-no-unused-properties.mjs
+ +++ b/.eslint_todo/vue-no-unused-properties.mjs
+ @@ -3,13 +3,7 @@
+ */
+ export default {
+ files: [
+ - 'app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue',
+ - 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue',
+ - 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_edit_note.vue',
+ - 'app/assets/javascripts/admin/statistics_panel/components/app.vue',
+ - 'app/assets/javascripts/badges/components/badge.vue',
+ - 'app/assets/javascripts/badges/components/badge_form.vue',
+ - 'app/assets/javascripts/batch_comments/components/draft_note.vue',
+ + 'app/assets/javascripts/batch_comments/components/preview_item.vue',
+ 'app/assets/javascripts/behaviors/components/json_table.vue',
+ 'app/assets/javascripts/behaviors/components/sandboxed_mermaid.vue',
+ DIFF
+ end
+
+ before do
+ allow(instance).to receive(:filter_and_get_changed_files_in_mr).and_return(files)
+ allow(instance).to receive(:`)
+ .with('git diff HEAD~..HEAD -- .eslint_todo/vue-no-unused-properties.mjs')
+ .and_return(git_diff_output)
+ end
+
+ it 'runs eslint with the correct arguments and returns exit 1 on failure' do
+ expect(instance).to receive(:system).with(*eslint_command).and_return(false)
+
+ status = instance_double(Process::Status, exitstatus: 1)
+ allow(instance).to receive(:last_command_status).and_return(status)
+
+ expect(instance.run_eslint_for_changed_files).to eq(1)
+ end
+ end
+
+ context 'when several todo files have been changed' do
+ let(:eslint_command) do
+ ['yarn', 'run', 'lint:eslint', '--no-warn-ignored', '--format', 'gitlab',
+ '.eslint_todo/vue-no-unused-properties.mjs',
+ 'app/assets/javascripts/projects/project_new.js',
+ 'app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue',
+ 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue']
+ end
+
+ let(:files) { ['.eslint_todo/vue-no-unused-properties.mjs'] }
+ let(:git_diff_output) do
+ <<-DIFF
+ diff --git a/.eslint_todo/index.mjs b/.eslint_todo/index.mjs
+ index 88c73e337a50..1c27203d62cb 100644
+ --- a/.eslint_todo/index.mjs
+ +++ b/.eslint_todo/index.mjs
+ @@ -1 +1,3 @@
+ export { default as vueNoUnusedProperties } from './vue-no-unused-properties.mjs';
+ +
+ +export { default as noUnusedVars } from './no-unused-vars.mjs';
+ diff --git a/.eslint_todo/no-unused-vars.mjs b/.eslint_todo/no-unused-vars.mjs
+ index dbd4c28b65c8..f11106b14857 100644
+ --- a/.eslint_todo/no-unused-vars.mjs
+ +++ b/.eslint_todo/no-unused-vars.mjs
+ @@ -3,7 +3,6 @@
+ */
+ export default {
+ files: [
+ - 'app/assets/javascripts/projects/project_new.js',
+ 'app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_editor/available_visualizations_drawer.vue',
+ 'app/assets/javascripts/vue_shared/components/customizable_dashboard/utils.js',
+ ],
+ diff --git a/.eslint_todo/vue-no-unused-properties.mjs b/.eslint_todo/vue-no-unused-properties.mjs
+ index 81f0b2cbcf84..cf56bd55554b 100644
+ --- a/.eslint_todo/vue-no-unused-properties.mjs
+ +++ b/.eslint_todo/vue-no-unused-properties.mjs
+ @@ -3,8 +3,6 @@
+ */
+ export default {
+ files: [
+ - 'app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue',
+ - 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue',
+ 'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_edit_note.vue',
+ 'app/assets/javascripts/admin/statistics_panel/components/app.vue',
+ 'app/assets/javascripts/badges/components/badge.vue',
+
+ DIFF
+ end
+
+ before do
+ allow(instance).to receive(:filter_and_get_changed_files_in_mr).and_return(files)
+ allow(instance).to receive(:`)
+ .with('git diff HEAD~..HEAD -- .eslint_todo/vue-no-unused-properties.mjs')
+ .and_return(git_diff_output)
+ end
+
+ it 'runs eslint with the correct arguments and returns exit 1 on failure' do
+ expect(instance).to receive(:system).with(*eslint_command).and_return(false)
+
+ status = instance_double(Process::Status, exitstatus: 1)
+ allow(instance).to receive(:last_command_status).and_return(status)
+
+ expect(instance.run_eslint_for_changed_files).to eq(1)
+ end
+ end
+
+ context 'when todo files have been changed but no ignored file was removed from them' do
+ let(:eslint_command) do
+ ['yarn', 'run', 'lint:eslint', '--no-warn-ignored', '--format', 'gitlab',
+ '.eslint_todo/vue-no-unused-properties.mjs']
+ end
+
+ let(:files) { ['.eslint_todo/vue-no-unused-properties.mjs'] }
+ let(:git_diff_output) do
+ <<-DIFF
+ diff --git a/.eslint_todo/no-unused-vars.mjs b/.eslint_todo/no-unused-vars.mjs
+ new file mode 100644
+ index 000000000000..dbd4c28b65c8
+ --- /dev/null
+ +++ b/.eslint_todo/no-unused-vars.mjs
+ @@ -0,0 +1,13 @@
+ +/**
+ + * Generated by `scripts/frontend/generate_eslint_todo_list.mjs`.
+ + */
+ +export default {
+ + files: [
+ + 'app/assets/javascripts/projects/project_new.js',
+ + 'app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_editor/available_visualizations_drawer.vue',
+ + 'app/assets/javascripts/vue_shared/components/customizable_dashboard/utils.js',
+ + ],
+ + rules: {
+ + 'no-unused-vars': 'off',
+ + },
+ +};
+
+ DIFF
+ end
+
+ before do
+ allow(instance).to receive(:filter_and_get_changed_files_in_mr).and_return(files)
+ allow(instance).to receive(:`)
+ .with('git diff HEAD~..HEAD -- .eslint_todo/vue-no-unused-properties.mjs')
+ .and_return(git_diff_output)
+ end
+
+ it 'runs eslint with the correct arguments and returns exit 1 on failure' do
+ expect(instance).to receive(:system).with(*eslint_command).and_return(false)
+
+ status = instance_double(Process::Status, exitstatus: 1)
+ allow(instance).to receive(:last_command_status).and_return(status)
+
+ expect(instance.run_eslint_for_changed_files).to eq(1)
+ end
+ end
end
describe 'Run CLI commands' do
diff --git a/spec/workers/namespaces/enable_descendants_cache_cron_worker_spec.rb b/spec/workers/namespaces/enable_descendants_cache_cron_worker_spec.rb
index d299a93eea0..c6ce7b5812b 100644
--- a/spec/workers/namespaces/enable_descendants_cache_cron_worker_spec.rb
+++ b/spec/workers/namespaces/enable_descendants_cache_cron_worker_spec.rb
@@ -26,9 +26,13 @@ RSpec.describe Namespaces::EnableDescendantsCacheCronWorker, '#perform', :clean_
it 'creates the cache record for the top level group and the subgroup' do
metadata = worker.perform
- ids = Namespaces::Descendants.pluck(:namespace_id)
+ cache_records = Namespaces::Descendants.all
+ ids = cache_records.map(&:id)
expect(ids).to match_array([group.id, subgroup.id])
+ outdated_ats = cache_records.map(&:outdated_at)
+ expect(outdated_ats).to all(be_present)
+
expect(metadata).to eq({ over_time: false, last_id: nil, cache_count: 2 })
end
diff --git a/tooling/ci/changed_files.rb b/tooling/ci/changed_files.rb
index 920cd356552..4492af32b29 100755
--- a/tooling/ci/changed_files.rb
+++ b/tooling/ci/changed_files.rb
@@ -3,7 +3,10 @@
module CI
class ChangedFiles
- FRONTEND_FILES_FILTER = /\.(js|cjs|mjs|vue)$/
+ FRONTEND_EXTENSIONS = "js|cjs|mjs|vue|graphql"
+ FRONTEND_FILES_FILTER = /\.(#{FRONTEND_EXTENSIONS})$/
+ ESLINT_TODO_FILES = %r{\.eslint_todo/[a-z-]+\.mjs$}
+ ESLINT_TODO_REMOVED_FILE_PATHS = %r{- +'([a-z/_-]+\.(#{FRONTEND_EXTENSIONS}))'}
def initialize(env: ENV, args: ARGV)
@env = env
@@ -18,6 +21,15 @@ module CI
`git diff --name-only --diff-filter=d HEAD~..HEAD`.split("\n")
end
+ def get_files_removed_from_todo_files(changed_files)
+ changed_todo_files = changed_files.filter { |file| file =~ ESLINT_TODO_FILES }
+
+ return [] if changed_todo_files.empty?
+
+ diff = `git diff HEAD~..HEAD -- #{changed_todo_files.join(' ')}`
+ diff.scan(ESLINT_TODO_REMOVED_FILE_PATHS).map(&:first)
+ end
+
def filter_and_get_changed_files_in_mr(filter_pattern: //)
get_changed_files_in_merged_results_pipeline.grep(filter_pattern)
end
@@ -26,6 +38,7 @@ module CI
puts 'Running ESLint for changed files...'
files = filter_and_get_changed_files_in_mr(filter_pattern: FRONTEND_FILES_FILTER)
+ files += get_files_removed_from_todo_files(files)
if files.empty?
puts 'No files were changed. Skipping...'