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
|
||||
echoerr "Skipping GCS push because one or more GLCI_FAILED_TESTS_* environment variables are not set"
|
||||
fi
|
||||
else
|
||||
echoinfo "Skipping GCS push because \$GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS != 'true'"
|
||||
fi
|
||||
|
||||
# Execute command with all arguments
|
||||
bundle exec failed-test-issues "${cmd_args[@]}"
|
||||
else
|
||||
echoinfo "Not reporting test failures because \$CREATE_RAILS_TEST_FAILURE_ISSUES != 'true' or TEST_FAILURES_PROJECT_TOKEN is not set"
|
||||
echoinfo "Not reporting test failures because TEST_FAILURES_PROJECT_TOKEN is not set"
|
||||
fi
|
||||
section_end "failed-test-issues"
|
||||
- |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -369,39 +369,40 @@ GET /projects
|
|||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------------------------|:---------|:---------|:------------|
|
||||
| `archived` | boolean | No | Limit by archived status. |
|
||||
| `id_after` | integer | No | Limit results to projects with IDs greater than the specified ID. |
|
||||
| `id_before` | integer | No | Limit results to projects with IDs less than the specified ID. |
|
||||
| `imported` | boolean | No | Limit results to projects which were imported from external systems by current user. |
|
||||
| `include_hidden` | boolean | No | Include hidden projects. _(administrators only)_ Premium and Ultimate only. |
|
||||
| `include_pending_delete` | boolean | No | Include projects pending deletion. _(administrators only)_ |
|
||||
| `last_activity_after` | datetime | No | Limit results to projects with last activity after specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
|
||||
| `last_activity_before` | datetime | No | Limit results to projects with last activity before specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
|
||||
| `membership` | boolean | No | Limit by projects that the current user is a member of. |
|
||||
| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------------------------|:---------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `archived` | boolean | No | Limit by archived status. |
|
||||
| `id_after` | integer | No | Limit results to projects with IDs greater than the specified ID. |
|
||||
| `id_before` | integer | No | Limit results to projects with IDs less than the specified ID. |
|
||||
| `imported` | boolean | No | Limit results to projects which were imported from external systems by current user. |
|
||||
| `include_hidden` | boolean | No | Include hidden projects. _(administrators only)_ Premium and Ultimate only. |
|
||||
| `include_pending_delete` | boolean | No | Include projects pending deletion. _(administrators only)_ |
|
||||
| `last_activity_after` | datetime | No | Limit results to projects with last activity after specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
|
||||
| `last_activity_before` | datetime | No | Limit results to projects with last activity before specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
|
||||
| `membership` | boolean | No | Limit by projects that the current user is a member of. |
|
||||
| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
|
||||
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, `last_activity_at`, or `similarity` fields. `repository_size`, `storage_size`, `packages_size` or `wiki_size` fields are only allowed for administrators. `similarity` is only available when searching and is limited to projects that the current user is a member of. Default is `created_at`. |
|
||||
| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
|
||||
| `repository_checksum_failed` | boolean | No | Limit projects where the repository checksum calculation has failed. Premium and Ultimate only. |
|
||||
| `repository_storage` | string | No | Limit results to projects stored on `repository_storage`. _(administrators only)_ |
|
||||
| `search_namespaces` | boolean | No | Include ancestor namespaces when matching search criteria. Default is `false`. |
|
||||
| `search` | string | No | Return list of projects with a `path`, `name`, or `description` matching the search criteria (case-insensitive, substring match). Multiple terms can be provided, separated by an escaped space, either `+` or `%20`, and will be ANDed together. Example: `one+two` will match substrings `one` and `two` (in any order). |
|
||||
| `simple` | boolean | No | Return only limited fields for each project. This operation is a no-op without authentication where only simple fields are returned. |
|
||||
| `sort` | string | No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
|
||||
| `starred` | boolean | No | Limit by projects starred by the current user. |
|
||||
| `statistics` | boolean | No | Include project statistics. Available only to users with at least the Reporter role. |
|
||||
| `topic_id` | integer | No | Limit results to projects with the assigned topic given by the topic ID. |
|
||||
| `topic` | string | No | Comma-separated topic names. Limit results to projects that match all of given topics. See `topics` attribute. |
|
||||
| `updated_after` | datetime | No | Limit results to projects last updated after the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
|
||||
| `updated_before` | datetime | No | Limit results to projects last updated before the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
|
||||
| `visibility` | string | No | Limit by visibility `public`, `internal`, or `private`. |
|
||||
| `wiki_checksum_failed` | boolean | No | Limit projects where the wiki checksum calculation has failed. Premium and Ultimate only. |
|
||||
| `with_custom_attributes` | boolean | No | Include [custom attributes](custom_attributes.md) in response. _(administrator only)_ |
|
||||
| `with_issues_enabled` | boolean | No | Limit by enabled issues feature. |
|
||||
| `with_merge_requests_enabled` | boolean | No | Limit by enabled merge requests feature. |
|
||||
| `with_programming_language` | string | No | Limit by projects which use the given programming language. |
|
||||
| `marked_for_deletion_on` | date | No | Filter by date when project was marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463939) in GitLab 17.1. Premium and Ultimate only. |
|
||||
| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
|
||||
| `repository_checksum_failed` | boolean | No | Limit projects where the repository checksum calculation has failed. Premium and Ultimate only. |
|
||||
| `repository_storage` | string | No | Limit results to projects stored on `repository_storage`. _(administrators only)_ |
|
||||
| `search_namespaces` | boolean | No | Include ancestor namespaces when matching search criteria. Default is `false`. |
|
||||
| `search` | string | No | Return list of projects with a `path`, `name`, or `description` matching the search criteria (case-insensitive, substring match). Multiple terms can be provided, separated by an escaped space, either `+` or `%20`, and will be ANDed together. Example: `one+two` will match substrings `one` and `two` (in any order). |
|
||||
| `simple` | boolean | No | Return only limited fields for each project. This operation is a no-op without authentication where only simple fields are returned. |
|
||||
| `sort` | string | No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
|
||||
| `starred` | boolean | No | Limit by projects starred by the current user. |
|
||||
| `statistics` | boolean | No | Include project statistics. Available only to users with at least the Reporter role. |
|
||||
| `topic_id` | integer | No | Limit results to projects with the assigned topic given by the topic ID. |
|
||||
| `topic` | string | No | Comma-separated topic names. Limit results to projects that match all of given topics. See `topics` attribute. |
|
||||
| `updated_after` | datetime | No | Limit results to projects last updated after the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
|
||||
| `updated_before` | datetime | No | Limit results to projects last updated before the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393979) in GitLab 15.10. For this filter to work, you must also provide `updated_at` as the `order_by` attribute. |
|
||||
| `visibility` | string | No | Limit by visibility `public`, `internal`, or `private`. |
|
||||
| `wiki_checksum_failed` | boolean | No | Limit projects where the wiki checksum calculation has failed. Premium and Ultimate only. |
|
||||
| `with_custom_attributes` | boolean | No | Include [custom attributes](custom_attributes.md) in response. _(administrator only)_ |
|
||||
| `with_issues_enabled` | boolean | No | Limit by enabled issues feature. |
|
||||
| `with_merge_requests_enabled` | boolean | No | Limit by enabled merge requests feature. |
|
||||
| `with_programming_language` | string | No | Limit by projects which use the given programming language. |
|
||||
| `marked_for_deletion_on` | date | No | Filter by date when project was marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463939) in GitLab 17.1. Premium and Ultimate only. |
|
||||
| `active` | boolean | No | Limit by projects that are not archived and not marked for deletion. |
|
||||
|
||||
This endpoint supports [keyset pagination](rest/_index.md#keyset-based-pagination) for selected `order_by` options.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,93 +67,51 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with vscode_web_ide=true' do
|
||||
let(:base_data) do
|
||||
{
|
||||
'use-new-web-ide' => 'true',
|
||||
'user-preferences-path' => profile_preferences_path,
|
||||
'sign-in-path' => 'test-sign-in-path',
|
||||
'new-web-ide-help-page-path' =>
|
||||
help_page_path('user/project/web_ide/_index.md'),
|
||||
'csp-nonce' => 'test-csp-nonce'
|
||||
}
|
||||
it 'includes editor font configuration' do
|
||||
ide_data = helper.ide_data(project: nil, fork_info: fork_info, params: params)
|
||||
editor_font = ::Gitlab::Json.parse(ide_data.fetch('editor-font'), symbolize_names: true)
|
||||
|
||||
expect(editor_font).to include({
|
||||
fallback_font_family: 'monospace',
|
||||
font_faces: [
|
||||
{
|
||||
family: 'GitLab Mono',
|
||||
display: 'block',
|
||||
src: [{
|
||||
url: a_string_matching(%r{gitlab-mono/GitLabMono-[^I]}),
|
||||
format: 'woff2'
|
||||
}]
|
||||
},
|
||||
{
|
||||
family: 'GitLab Mono',
|
||||
display: 'block',
|
||||
style: 'italic',
|
||||
src: [{
|
||||
url: a_string_matching(%r{gitlab-mono/GitLabMono-Italic}),
|
||||
format: 'woff2'
|
||||
}]
|
||||
}
|
||||
]
|
||||
})
|
||||
end
|
||||
|
||||
context 'for extension marketplace data' do
|
||||
where(:settings, :expected_settings_hash) do
|
||||
ref(:disabled_vscode_settings) | nil
|
||||
ref(:enabled_vscode_settings) | 'c6620244fe72864fa8d8'
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(vscode_web_ide: true)
|
||||
end
|
||||
with_them do
|
||||
it 'includes extension marketplace settings and settings context hash' do
|
||||
expect(WebIde::ExtensionMarketplace).to receive(:webide_extension_marketplace_settings)
|
||||
.with(user: user).and_return(settings)
|
||||
|
||||
it 'returns hash' do
|
||||
expect(helper.ide_data(project: nil, fork_info: fork_info, params: params))
|
||||
.to include(base_data)
|
||||
end
|
||||
actual = helper.ide_data(project: nil, fork_info: fork_info, params: params)
|
||||
|
||||
it 'includes editor font configuration' do
|
||||
ide_data = helper.ide_data(project: nil, fork_info: fork_info, params: params)
|
||||
editor_font = ::Gitlab::Json.parse(ide_data.fetch('editor-font'), symbolize_names: true)
|
||||
|
||||
expect(editor_font).to include({
|
||||
fallback_font_family: 'monospace',
|
||||
font_faces: [
|
||||
{
|
||||
family: 'GitLab Mono',
|
||||
display: 'block',
|
||||
src: [{
|
||||
url: a_string_matching(%r{gitlab-mono/GitLabMono-[^I]}),
|
||||
format: 'woff2'
|
||||
}]
|
||||
},
|
||||
{
|
||||
family: 'GitLab Mono',
|
||||
display: 'block',
|
||||
style: 'italic',
|
||||
src: [{
|
||||
url: a_string_matching(%r{gitlab-mono/GitLabMono-Italic}),
|
||||
format: 'woff2'
|
||||
}]
|
||||
}
|
||||
]
|
||||
})
|
||||
end
|
||||
|
||||
it 'does not use new web ide if feature flag is disabled' do
|
||||
stub_feature_flags(vscode_web_ide: false)
|
||||
|
||||
expect(helper.ide_data(project: nil, fork_info: fork_info, params: params))
|
||||
.to include('use-new-web-ide' => 'false')
|
||||
end
|
||||
|
||||
context 'for extension marketplace data' do
|
||||
where(:settings, :expected_settings_hash) do
|
||||
ref(:disabled_vscode_settings) | nil
|
||||
ref(:enabled_vscode_settings) | 'c6620244fe72864fa8d8'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'includes extension marketplace settings and settings context hash' do
|
||||
expect(WebIde::ExtensionMarketplace).to receive(:webide_extension_marketplace_settings)
|
||||
.with(user: user).and_return(settings)
|
||||
|
||||
actual = helper.ide_data(project: nil, fork_info: fork_info, params: params)
|
||||
|
||||
expect(actual).to include({
|
||||
'extension-marketplace-settings' => settings.to_json,
|
||||
'settings-context-hash' => expected_settings_hash
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project' do
|
||||
it 'returns hash with parameters' do
|
||||
expect(
|
||||
helper.ide_data(project: project, fork_info: nil, params: params)
|
||||
).to include(base_data.merge(
|
||||
'branch-name' => params[:branch],
|
||||
'file-path' => params[:path],
|
||||
'merge-request' => params[:merge_request_id],
|
||||
'fork-info' => nil
|
||||
))
|
||||
expect(actual).to include({
|
||||
'extension-marketplace-settings' => settings.to_json,
|
||||
'settings-context-hash' => expected_settings_hash
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -164,10 +120,6 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
|
|||
describe '#show_web_ide_oauth_callback_mismatch_callout?' do
|
||||
let_it_be(:oauth_application) { create(:oauth_application, owner: nil) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vscode_web_ide: true)
|
||||
end
|
||||
|
||||
it 'returns false if no Web IDE OAuth application found' do
|
||||
expect(helper.show_web_ide_oauth_callback_mismatch_callout?).to be false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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