Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-24 09:10:10 +00:00
parent 37ae177a1b
commit b39f7ccba1
114 changed files with 1064 additions and 843 deletions

View File

@ -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'

View File

@ -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

View File

@ -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"
- |

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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()) {

View File

@ -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)"
/>
<work-item-custom-fields
v-if="showWorkItemCustomFields"
v-if="workItemCustomFields"
:work-item-id="workItemId"
:work-item-type="selectedWorkItemTypeName"
:custom-fields="workItemCustomFields"

View File

@ -187,9 +187,6 @@ export default {
customFields() {
return this.isWidgetPresent(WIDGET_TYPE_CUSTOM_FIELDS)?.customFieldValues;
},
showWorkItemCustomFields() {
return this.glFeatures.customFieldsFeature && this.customFields;
},
showWorkItemStatus() {
return this.glFeatures.workItemStatusFeatureFlag;
},
@ -323,7 +320,7 @@ export default {
@error="$emit('error', $event)"
/>
<work-item-custom-fields
v-if="showWorkItemCustomFields"
v-if="customFields"
:work-item-id="workItem.id"
:work-item-type="workItemType"
:custom-fields="customFields"

View File

@ -4,6 +4,7 @@ import { __ } from '~/locale';
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import {
DETAIL_VIEW_QUERY_PARAM_NAME,
DETAIL_VIEW_DESIGN_VERSION_PARAM_NAME,
@ -90,6 +91,9 @@ export default {
(this.glFeatures.workItemsViewPreference && gon.current_user_use_work_items_view))
);
},
getDrawerHeight() {
return `calc(${getContentWrapperHeight()} + var(--top-bar-height))`;
},
},
watch: {
activeItem: {
@ -249,8 +253,8 @@ export default {
:open="open"
:z-index="200"
data-testid="work-item-drawer"
:header-height="getDrawerHeight"
header-sticky
header-height="calc(var(--top-bar-height) + var(--performance-bar-height))"
class="gl-w-full gl-leading-reset lg:gl-w-[480px] xl:gl-w-[768px] min-[1440px]:gl-w-[912px]"
@close="handleClose"
@opened="$emit('opened')"

View File

@ -15,7 +15,10 @@ module RapidDiffs
offset = { offset_index: params.permit(:offset)[:offset].to_i }
stream_diff_files(streaming_diff_options.merge(offset))
context = view_context
# view_context calls are not memoized, with explicit passing we are able to reuse it across renders
stream_diff_files(streaming_diff_options.merge(offset), context)
streaming_time = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - streaming_start_time).round(2)
response.stream.write "<server-timings streaming=\"#{streaming_time}\"></server-timings>"
@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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) }

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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'

View File

@ -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) }

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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') }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
d5ffdd5d041245f597dd74a033354c5302a808d8d03014a2f65e57be0a2fba16

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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?,

View File

@ -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. " \

View File

@ -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) { '<empty-state>No changes</empty-state>' }
let(:diffs_html) { '<diffs></diffs>' }
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

View File

@ -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],

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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();
});
});
});

View File

@ -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);

View File

@ -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;
});

View File

@ -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: {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

Some files were not shown because too many files have changed in this diff Show More