Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									37ae177a1b
								
							
						
					
					
						commit
						b39f7ccba1
					
				| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
        echoinfo "Not reporting test failures because \$CREATE_RAILS_TEST_FAILURE_ISSUES != 'true' or TEST_FAILURES_PROJECT_TOKEN is not set"
 | 
			
		||||
            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 TEST_FAILURES_PROJECT_TOKEN is not set"
 | 
			
		||||
      fi
 | 
			
		||||
      section_end "failed-test-issues"
 | 
			
		||||
    - |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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')"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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') }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
d5ffdd5d041245f597dd74a033354c5302a808d8d03014a2f65e57be0a2fba16
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -370,7 +370,7 @@ 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.                                                                                                                                                                                                                                                                                                                               |
 | 
			
		||||
| 
						 | 
				
			
			@ -402,6 +402,7 @@ Supported attributes:
 | 
			
		|||
| `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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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?,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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. " \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,27 +67,6 @@ 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'
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      before do
 | 
			
		||||
        stub_feature_flags(vscode_web_ide: true)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'returns hash' do
 | 
			
		||||
        expect(helper.ide_data(project: nil, fork_info: fork_info, params: params))
 | 
			
		||||
          .to include(base_data)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
| 
						 | 
				
			
			@ -118,13 +95,6 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
 | 
			
		|||
      })
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			@ -145,29 +115,11 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
 | 
			
		|||
        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
 | 
			
		||||
          ))
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
		Loading…
	
		Reference in New Issue