Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-05-06 09:11:03 +00:00
parent 686f1c3361
commit dcaa8f80fb
116 changed files with 898 additions and 2244 deletions

View File

@ -123,17 +123,17 @@ config/bounded_contexts.yml @fabiopitino @grzesiek @stanhu @cwoolley-gitlab @tku
/.gitlab/ci/test-on-gdk/ @gl-dx/pipeline-maintainers @gl-dx/maintainers
/gems/gem.gitlab-ci.yml
[Tooling] @gl-dx/eng-prod
[Tooling] @gl-dx/tooling-maintainers
Dangerfile
/danger/
/tooling/
/spec/tooling/
/scripts/
/scripts/**/*.rb @gl-dx/eng-prod @gitlab-org/maintainers/rails-backend
/scripts/**/*.js @gl-dx/eng-prod @gitlab-org/maintainers/frontend
/scripts/frontend/ @gl-dx/eng-prod @gitlab-org/maintainers/frontend
/scripts/remote_development/ @gl-dx/eng-prod @gitlab-org/maintainers/workspaces/backend @gitlab-org/maintainers/workspaces/frontend
/scripts/review_apps/seed-dast-test-data.sh @gl-dx/eng-prod @dappelt @ngeorge1
/scripts/**/*.rb @gl-dx/tooling-maintainers @gitlab-org/maintainers/rails-backend
/scripts/**/*.js @gl-dx/tooling-maintainers @gitlab-org/maintainers/frontend
/scripts/frontend/ @gl-dx/tooling-maintainers @gitlab-org/maintainers/frontend
/scripts/remote_development/ @gl-dx/tooling-maintainers @gitlab-org/maintainers/workspaces/backend @gitlab-org/maintainers/workspaces/frontend
/scripts/review_apps/seed-dast-test-data.sh @gl-dx/tooling-maintainers @dappelt @ngeorge1
/.dockerignore
/.editorconfig
/.gitpod.yml
@ -145,7 +145,7 @@ Dangerfile
/lefthook.yml
/tests.yml
^[Backend Static Code Analysis] @gl-dx/eng-prod @dstull
^[Backend Static Code Analysis] @gl-dx/tooling-maintainers @dstull
.rubocop*.yml
/gems/config/rubocop.yml
/rubocop/
@ -990,7 +990,7 @@ lib/gitlab/checks/**
/doc/development/fe_guide/keyboard_shortcuts.md @gitlab-org/foundations/personal-productivity/engineering
/doc/development/git_object_deduplication.md @proglottis @toon
/doc/development/gitaly.md @proglottis @toon
/doc/development/gitpod_internals.md @gl-dx/eng-prod
/doc/development/gitpod_internals.md @gl-dx/tooling-maintainers
/doc/development/identity_verification.md @gitlab-org/software-supply-chain-security/authorization/approvers
/doc/development/image_scaling.md @abdwdd @alexpooley
/doc/development/internal_analytics/ @gitlab-org/analytics-section/product-analytics/engineers/frontend @gitlab-org/analytics-section/analytics-instrumentation/engineers
@ -1004,12 +1004,12 @@ lib/gitlab/checks/**
/doc/development/organization/ @abdwdd @alexpooley
/doc/development/permissions.md @gitlab-org/software-supply-chain-security/authorization/approvers
/doc/development/permissions/ @gitlab-org/software-supply-chain-security/authorization/approvers
/doc/development/pipelines/ @gl-dx/eng-prod
/doc/development/pipelines/ @gl-dx/pipeline-maintainers
/doc/development/policies.md @gitlab-org/software-supply-chain-security/authentication/approvers
/doc/development/prometheus_metrics.md @gitlab-org/analytics-section/product-analytics/engineers/frontend
/doc/development/search/ @gitlab-org/search-team/migration-maintainers
/doc/development/sec/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/static-analysis
/doc/development/software_design.md @gl-dx/eng-prod
/doc/development/software_design.md @gl-dx/pipeline-maintainers
/doc/development/spam_protection_and_captcha/ @gitlab-org/software-supply-chain-security/authorization/approvers
/doc/development/stage_group_observability/ @gitlab-org/analytics-section/product-analytics/engineers/frontend
/doc/development/tracing.md @gitlab-org/analytics-section/product-analytics/engineers/frontend
@ -1252,7 +1252,7 @@ lib/gitlab/checks/**
[Localization Team] @gitlab-com/localization/maintainers
/doc-locale/**
/doc/development/i18n/proofreader.md
/argo_translation.yml
/argo_translation.yml
[Authorization] @gitlab-org/software-supply-chain-security/authorization/approvers
/config/initializers/declarative_policy.rb

View File

@ -112,8 +112,22 @@ docs hugo_build:
stage: lint
needs: []
dependencies: []
variables:
DOCS_BRANCH: "main"
before_script:
- git clone --depth 1 --filter=tree:0 https://gitlab.com/gitlab-org/technical-writing/docs-gitlab-com.git
# Check if this a release branch, which would be the case for a backport.
# If this is a backport MR, we need to checkout the appropriate version
# of the Docs website.
- |
if [[ $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ [0-9]+-[0-9]+-stable ]]; then
MAJOR=$(echo $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | cut -d '-' -f 1)
MINOR=$(echo $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | cut -d '-' -f 2)
# Convert GitLab style (17-9-stable-ee) to Docs style (17.9)
DOCS_BRANCH="$MAJOR.$MINOR"
echo "Using docs-gitlab-com branch $DOCS_BRANCH for release branch"
fi
# Clone the Docs website project
- git clone --depth 1 --filter=tree:0 --branch $DOCS_BRANCH https://gitlab.com/gitlab-org/technical-writing/docs-gitlab-com.git
- cd docs-gitlab-com
- make add-latest-icons
# Copy the current project's docs to the appropriate location in the docs website

View File

@ -40,12 +40,8 @@
# - tooling/lib/tooling/glci/failure_analyzer.rb
.failure_category_after_script:
after_script:
# We need this at the very top, because the section_start/section_end logic is defined there.
- source scripts/utils.sh
- |
section_start "failure-analyzer" "Report failure category"
tooling/lib/tooling/glci/failure_analyzer.rb $CI_JOB_ID || true
section_end "failure-analyzer"
- execute_failure_analyzer
.repo-from-artifacts:
variables:

View File

@ -48,12 +48,8 @@ stages:
# Taken from .gitlab/ci/global.gitlab-ci.yml, but adapted the paths
.failure_category_after_script:
after_script:
# We need this at the very top, because the section_start/section_end logic is defined there.
- source $CI_PROJECT_DIR/scripts/utils.sh
- |
section_start "failure-analyzer" "Report failure category"
$CI_PROJECT_DIR/tooling/lib/tooling/glci/failure_analyzer.rb $CI_JOB_ID || true
section_end "failure-analyzer"
- execute_failure_analyzer
.qa-install:
extends:

View File

@ -6,7 +6,7 @@ include:
inputs:
cng_path: 'charts/components/images'
- project: 'gitlab-org/quality/pipeline-common'
ref: '10.0.0'
ref: '10.1.0'
file: ci/base.gitlab-ci.yml
stages:

View File

@ -3,7 +3,7 @@
include:
- project: gitlab-org/quality/pipeline-common
ref: 10.0.0
ref: 10.1.0
file:
- /ci/base.gitlab-ci.yml

View File

@ -1595,14 +1595,12 @@ Gitlab/BoundedContexts:
- 'app/services/issues/after_create_service.rb'
- 'app/services/issues/base_service.rb'
- 'app/services/issues/build_service.rb'
- 'app/services/issues/clone_service.rb'
- 'app/services/issues/close_service.rb'
- 'app/services/issues/convert_to_ticket_service.rb'
- 'app/services/issues/create_service.rb'
- 'app/services/issues/duplicate_service.rb'
- 'app/services/issues/export_csv_service.rb'
- 'app/services/issues/import_csv_service.rb'
- 'app/services/issues/move_service.rb'
- 'app/services/issues/prepare_import_csv_service.rb'
- 'app/services/issues/referenced_merge_requests_service.rb'
- 'app/services/issues/related_branches_service.rb'
@ -3132,12 +3130,10 @@ Gitlab/BoundedContexts:
- 'ee/app/services/ee/issues/after_create_service.rb'
- 'ee/app/services/ee/issues/base_service.rb'
- 'ee/app/services/ee/issues/build_service.rb'
- 'ee/app/services/ee/issues/clone_service.rb'
- 'ee/app/services/ee/issues/close_service.rb'
- 'ee/app/services/ee/issues/create_service.rb'
- 'ee/app/services/ee/issues/export_csv_service.rb'
- 'ee/app/services/ee/issues/import_csv_service.rb'
- 'ee/app/services/ee/issues/move_service.rb'
- 'ee/app/services/ee/issues/reopen_service.rb'
- 'ee/app/services/ee/issues/update_service.rb'
- 'ee/app/services/ee/jira_connect/sync_service.rb'

View File

@ -540,7 +540,6 @@ Layout/EmptyLineAfterMagicComment:
- 'spec/services/dependency_proxy/head_manifest_service_spec.rb'
- 'spec/services/dependency_proxy/request_token_service_spec.rb'
- 'spec/services/design_management/copy_design_collection/copy_service_spec.rb'
- 'spec/services/design_management/copy_design_collection/queue_service_spec.rb'
- 'spec/services/design_management/delete_designs_service_spec.rb'
- 'spec/services/design_management/move_designs_service_spec.rb'
- 'spec/services/design_management/save_designs_service_spec.rb'

View File

@ -236,7 +236,6 @@ Layout/LineLength:
- 'app/services/dependency_proxy/group_settings/update_service.rb'
- 'app/services/dependency_proxy/image_ttl_group_policies/update_service.rb'
- 'app/services/design_management/copy_design_collection/copy_service.rb'
- 'app/services/design_management/copy_design_collection/queue_service.rb'
- 'app/services/design_management/generate_image_versions_service.rb'
- 'app/services/design_management/save_designs_service.rb'
- 'app/services/discussions/resolve_service.rb'
@ -253,10 +252,8 @@ Layout/LineLength:
- 'app/services/import/bitbucket_server_service.rb'
- 'app/services/issuable/process_assignees.rb'
- 'app/services/issuable_base_service.rb'
- 'app/services/issues/clone_service.rb'
- 'app/services/issues/close_service.rb'
- 'app/services/issues/duplicate_service.rb'
- 'app/services/issues/move_service.rb'
- 'app/services/issues/relative_position_rebalancing_service.rb'
- 'app/services/issues/set_crm_contacts_service.rb'
- 'app/services/issues/update_service.rb'
@ -668,7 +665,6 @@ Layout/LineLength:
- 'ee/app/services/ee/groups/update_service.rb'
- 'ee/app/services/ee/ip_restrictions/update_service.rb'
- 'ee/app/services/ee/issues/base_service.rb'
- 'ee/app/services/ee/issues/clone_service.rb'
- 'ee/app/services/ee/merge_requests/merge_base_service.rb'
- 'ee/app/services/ee/merge_requests/refresh_service.rb'
- 'ee/app/services/ee/personal_access_tokens/revoke_service.rb'
@ -1647,7 +1643,6 @@ Layout/LineLength:
- 'ee/spec/services/ee/ip_restrictions/update_service_spec.rb'
- 'ee/spec/services/ee/issuable/common_system_notes_service_spec.rb'
- 'ee/spec/services/ee/issue_links/create_service_spec.rb'
- 'ee/spec/services/ee/issues/clone_service_spec.rb'
- 'ee/spec/services/ee/issues/create_service_spec.rb'
- 'ee/spec/services/ee/issues/update_service_spec.rb'
- 'ee/spec/services/ee/members/destroy_service_spec.rb'
@ -3796,12 +3791,10 @@ Layout/LineLength:
- 'spec/services/issue_links/create_service_spec.rb'
- 'spec/services/issues/after_create_service_spec.rb'
- 'spec/services/issues/build_service_spec.rb'
- 'spec/services/issues/clone_service_spec.rb'
- 'spec/services/issues/close_service_spec.rb'
- 'spec/services/issues/create_service_spec.rb'
- 'spec/services/issues/duplicate_service_spec.rb'
- 'spec/services/issues/export_csv_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/referenced_merge_requests_service_spec.rb'
- 'spec/services/issues/relative_position_rebalancing_service_spec.rb'
- 'spec/services/issues/resolve_discussions_spec.rb'

View File

@ -102,8 +102,6 @@ Lint/AssignmentInCondition:
- 'ee/app/services/deployments/approval_service.rb'
- 'ee/app/services/dora/aggregate_metrics_service.rb'
- 'ee/app/services/ee/application_settings/update_service.rb'
- 'ee/app/services/ee/issues/clone_service.rb'
- 'ee/app/services/ee/issues/move_service.rb'
- 'ee/app/services/ee/projects/operations/update_service.rb'
- 'ee/app/services/gitlab_subscriptions/fetch_subscription_plans_service.rb'
- 'ee/app/services/incident_management/issuable_resource_links/zoom_link_service.rb'

View File

@ -140,7 +140,6 @@ Lint/UnusedMethodArgument:
- 'app/services/groups/update_service.rb'
- 'app/services/import/gitlab_projects/file_acquisition_strategies/file_upload.rb'
- 'app/services/issues/export_csv_service.rb'
- 'app/services/issues/move_service.rb'
- 'app/services/jira/requests/base.rb'
- 'app/services/keys/destroy_service.rb'
- 'app/services/merge_requests/close_service.rb'

View File

@ -202,7 +202,6 @@ Rails/Date:
- 'spec/services/import/reassign_placeholder_user_records_service_spec.rb'
- 'spec/services/issuable/callbacks/time_tracking_spec.rb'
- 'spec/services/issuable/common_system_notes_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/members/invitation_reminder_email_service_spec.rb'
- 'spec/services/members/update_service_spec.rb'
- 'spec/services/milestones/find_or_create_service_spec.rb'

View File

@ -1310,10 +1310,8 @@ RSpec/BeEq:
- 'spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb'
- 'spec/services/incident_management/timeline_events/create_service_spec.rb'
- 'spec/services/issuable/callbacks/time_tracking_spec.rb'
- 'spec/services/issues/clone_service_spec.rb'
- 'spec/services/issues/create_service_spec.rb'
- 'spec/services/issues/export_csv_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/update_service_spec.rb'
- 'spec/services/issues/zoom_link_service_spec.rb'
- 'spec/services/jira_connect_installations/update_service_spec.rb'

View File

@ -534,7 +534,6 @@ RSpec/BeforeAllRoleAssignment:
- 'ee/spec/services/ee/groups/deploy_tokens/revoke_service_spec.rb'
- 'ee/spec/services/ee/groups/group_links/create_service_spec.rb'
- 'ee/spec/services/ee/issuable/bulk_update_service_spec.rb'
- 'ee/spec/services/ee/issues/clone_service_spec.rb'
- 'ee/spec/services/ee/issues/close_service_spec.rb'
- 'ee/spec/services/ee/issues/create_service_spec.rb'
- 'ee/spec/services/ee/issues/reopen_service_spec.rb'
@ -1210,9 +1209,7 @@ RSpec/BeforeAllRoleAssignment:
- 'spec/services/integrations/slack_options/user_search_handler_spec.rb'
- 'spec/services/issuable/bulk_update_service_spec.rb'
- 'spec/services/issue_links/create_service_spec.rb'
- 'spec/services/issues/clone_service_spec.rb'
- 'spec/services/issues/create_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/reorder_service_spec.rb'
- 'spec/services/issues/set_crm_contacts_service_spec.rb'
- 'spec/services/issues/update_service_spec.rb'

View File

@ -616,9 +616,7 @@ RSpec/ContextWording:
- 'ee/spec/services/ee/integrations/test/project_service_spec.rb'
- 'ee/spec/services/ee/ip_restrictions/update_service_spec.rb'
- 'ee/spec/services/ee/issuable/bulk_update_service_spec.rb'
- 'ee/spec/services/ee/issues/clone_service_spec.rb'
- 'ee/spec/services/ee/issues/create_service_spec.rb'
- 'ee/spec/services/ee/issues/move_service_spec.rb'
- 'ee/spec/services/ee/issues/update_service_spec.rb'
- 'ee/spec/services/ee/keys/destroy_service_spec.rb'
- 'ee/spec/services/ee/members/create_service_spec.rb'
@ -2387,11 +2385,9 @@ RSpec/ContextWording:
- 'spec/services/integrations/test/project_service_spec.rb'
- 'spec/services/issue_links/list_service_spec.rb'
- 'spec/services/issues/build_service_spec.rb'
- 'spec/services/issues/clone_service_spec.rb'
- 'spec/services/issues/close_service_spec.rb'
- 'spec/services/issues/create_service_spec.rb'
- 'spec/services/issues/export_csv_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/referenced_merge_requests_service_spec.rb'
- 'spec/services/issues/related_branches_service_spec.rb'
- 'spec/services/issues/relative_position_rebalancing_service_spec.rb'

View File

@ -270,7 +270,6 @@ RSpec/ExpectChange:
- 'spec/services/clusters/agents/create_activity_event_service_spec.rb'
- 'spec/services/clusters/agents/delete_expired_events_service_spec.rb'
- 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb'
- 'spec/services/design_management/copy_design_collection/queue_service_spec.rb'
- 'spec/services/design_management/save_designs_service_spec.rb'
- 'spec/services/event_create_service_spec.rb'
- 'spec/services/events/destroy_service_spec.rb'
@ -285,7 +284,6 @@ RSpec/ExpectChange:
- 'spec/services/issuable/bulk_update_service_spec.rb'
- 'spec/services/issues/create_service_spec.rb'
- 'spec/services/issues/export_csv_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/update_service_spec.rb'
- 'spec/services/jira_connect_installations/destroy_service_spec.rb'
- 'spec/services/keys/destroy_service_spec.rb'

View File

@ -854,7 +854,6 @@ RSpec/NamedSubject:
- 'ee/spec/services/ee/issuable/common_system_notes_service_spec.rb'
- 'ee/spec/services/ee/issuable/destroy_service_spec.rb'
- 'ee/spec/services/ee/issue_links/create_service_spec.rb'
- 'ee/spec/services/ee/issues/clone_service_spec.rb'
- 'ee/spec/services/ee/issues/update_service_spec.rb'
- 'ee/spec/services/ee/keys/destroy_service_spec.rb'
- 'ee/spec/services/ee/members/create_service_spec.rb'
@ -2884,7 +2883,6 @@ RSpec/NamedSubject:
- 'spec/services/deployments/archive_in_project_service_spec.rb'
- 'spec/services/deployments/update_environment_service_spec.rb'
- 'spec/services/design_management/copy_design_collection/copy_service_spec.rb'
- 'spec/services/design_management/copy_design_collection/queue_service_spec.rb'
- 'spec/services/design_management/design_user_notes_count_service_spec.rb'
- 'spec/services/design_management/move_designs_service_spec.rb'
- 'spec/services/discussions/capture_diff_note_position_service_spec.rb'
@ -2941,12 +2939,10 @@ RSpec/NamedSubject:
- 'spec/services/issuable/import_csv/base_service_spec.rb'
- 'spec/services/issue_links/list_service_spec.rb'
- 'spec/services/issues/build_service_spec.rb'
- 'spec/services/issues/clone_service_spec.rb'
- 'spec/services/issues/create_service_spec.rb'
- 'spec/services/issues/duplicate_service_spec.rb'
- 'spec/services/issues/export_csv_service_spec.rb'
- 'spec/services/issues/import_csv_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/prepare_import_csv_service_spec.rb'
- 'spec/services/issues/relative_position_rebalancing_service_spec.rb'
- 'spec/services/issues/reorder_service_spec.rb'

View File

@ -136,7 +136,6 @@ RSpec/ReceiveMessages:
- 'ee/spec/services/compliance_management/frameworks/update_project_service_spec.rb'
- 'ee/spec/services/ee/ci/job_token_scope/remove_group_service_spec.rb'
- 'ee/spec/services/ee/ci/job_token_scope/remove_project_service_spec.rb'
- 'ee/spec/services/ee/issues/move_service_spec.rb'
- 'ee/spec/services/ee/post_receive_service_spec.rb'
- 'ee/spec/services/ee/spam/spam_verdict_service_spec.rb'
- 'ee/spec/services/ee/work_items/related_work_item_links/create_service_spec.rb'

View File

@ -85,8 +85,6 @@ RSpec/SubjectDeclaration:
- 'spec/serializers/pipeline_details_entity_spec.rb'
- 'spec/services/concerns/exclusive_lease_guard_spec.rb'
- 'spec/services/concerns/rate_limited_service_spec.rb'
- 'spec/services/issues/clone_service_spec.rb'
- 'spec/services/issues/move_service_spec.rb'
- 'spec/services/issues/prepare_import_csv_service_spec.rb'
- 'spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb'
- 'spec/services/merge_requests/reload_diffs_service_spec.rb'

View File

@ -19,7 +19,6 @@ Style/AccessorGrouping:
- 'app/models/integrations/chat_message/wiki_page_message.rb'
- 'app/models/project.rb'
- 'app/services/deployments/update_environment_service.rb'
- 'app/services/issues/clone_service.rb'
- 'app/services/note_summary.rb'
- 'app/services/notification_recipients/builder/default.rb'
- 'app/services/task_list_toggle_service.rb'

View File

@ -58,9 +58,7 @@ Style/FormatString:
- 'app/services/import/bitbucket_server_service.rb'
- 'app/services/import/fogbugz_service.rb'
- 'app/services/issuable_links/create_service.rb'
- 'app/services/issues/clone_service.rb'
- 'app/services/issues/close_service.rb'
- 'app/services/issues/move_service.rb'
- 'app/services/issues/set_crm_contacts_service.rb'
- 'app/services/jira/requests/base.rb'
- 'app/services/milestones/promote_service.rb'

View File

@ -130,8 +130,6 @@ Style/GuardClause:
- 'app/services/issuable/bulk_update_service.rb'
- 'app/services/issuable/common_system_notes_service.rb'
- 'app/services/issuable_base_service.rb'
- 'app/services/issues/clone_service.rb'
- 'app/services/issues/move_service.rb'
- 'app/services/issues/update_service.rb'
- 'app/services/markdown_content_rewriter_service.rb'
- 'app/services/merge_requests/add_spent_time_service.rb'
@ -263,7 +261,6 @@ Style/GuardClause:
- 'ee/app/services/ee/groups/update_service.rb'
- 'ee/app/services/ee/issuable/common_system_notes_service.rb'
- 'ee/app/services/ee/issues/base_service.rb'
- 'ee/app/services/ee/issues/clone_service.rb'
- 'ee/app/services/ee/merge_requests/merge_base_service.rb'
- 'ee/app/services/ee/merge_requests/refresh_service.rb'
- 'ee/app/services/ee/projects/create_service.rb'

View File

@ -93,7 +93,6 @@ Style/IfUnlessModifier:
- 'app/services/concerns/merge_requests/assigns_merge_params.rb'
- 'app/services/dependency_proxy/group_settings/update_service.rb'
- 'app/services/dependency_proxy/image_ttl_group_policies/update_service.rb'
- 'app/services/design_management/copy_design_collection/queue_service.rb'
- 'app/services/discussions/resolve_service.rb'
- 'app/services/discussions/update_diff_position_service.rb'
- 'app/services/draft_notes/create_service.rb'
@ -108,7 +107,6 @@ Style/IfUnlessModifier:
- 'app/services/issuable/bulk_update_service.rb'
- 'app/services/issuable_base_service.rb'
- 'app/services/issuable_links/create_service.rb'
- 'app/services/issues/move_service.rb'
- 'app/services/issues/relative_position_rebalancing_service.rb'
- 'app/services/issues/update_service.rb'
- 'app/services/lfs/lock_file_service.rb'

View File

@ -1 +1 @@
db0830c3c2751e55a8a4702cf08ff756f09e7dc8
1be1a77766ed02eefd5fc8e64e99e7b69e5ed91b

View File

@ -0,0 +1,47 @@
/* eslint-disable no-underscore-dangle,@gitlab/require-i18n-strings,no-console */
function getPersistedCoverage() {
const storedPaths = localStorage.getItem('__coverage_paths__');
if (storedPaths) {
return JSON.parse(storedPaths);
}
return [];
}
function getCoverage() {
if (!window.__coverage__) {
console.warn(
'Coverage object is missing on the page. Did you install Istanbul babel plugin and enable Webpack?',
);
}
const filePaths = Object.keys(window.__coverage__);
const existingPaths = getPersistedCoverage();
return [...new Set([...existingPaths, ...filePaths])];
}
function persistCoverage(coverage = getCoverage()) {
localStorage.setItem('__coverage_paths__', JSON.stringify(coverage));
console.log(`Coverage paths saved: ${coverage.length} files tracked`);
}
function updateCoverage() {
const coverage = getCoverage();
persistCoverage(coverage);
window.__coverageFilePaths = coverage;
}
window.addEventListener('beforeunload', () => {
updateCoverage();
});
window.__coveragePathsPersistence = {
update: updateCoverage,
getPaths() {
return window.__coverageFilePaths || [];
},
reset() {
localStorage.removeItem('__coverage_paths__');
window.__coverageFilePaths = [];
console.log('Coverage paths reset.');
},
};

View File

@ -35,22 +35,11 @@ const i18n = {
helpString: __('Why am I seeing this warning?'),
};
const redactString = (inputString) => {
if (inputString.length <= 9) return inputString;
const prefix = inputString.substring(0, 5); // Keep the first 5 characters
const suffix = inputString.substring(inputString.length - 4); // Keep the last 4 characters
const redactLength = Math.min(inputString.length - prefix.length - suffix.length, 22);
return `${prefix}${'*'.repeat(redactLength)}${suffix}`;
};
const formatMessage = (findings, contentType) => {
const header = sprintf(i18n.promptMessage(findings.length), { contentType });
const matchedPatterns = findings.map(({ patternName, matchedString }) => {
const redactedString = redactString(matchedString);
return `<li>${escape(patternName)}: ${escape(redactedString)}</li>`;
return `<li>${escape(patternName)}: ${escape(matchedString)}</li>`;
});
const message = `
@ -74,10 +63,10 @@ const containsSensitiveToken = (message) => {
for (const rule of sensitiveDataPatterns()) {
const regex = new RegExp(rule.regex, 'gi');
const matches = message.match(regex);
const uniqueMatches = new Set(message.match(regex));
if (matches) {
matches.forEach((match) => {
if (uniqueMatches) {
uniqueMatches.forEach((match) => {
findings.push({
patternName: rule.name,
matchedString: match,

View File

@ -16,10 +16,6 @@
}
}
.oneline {
line-height: 35px;
}
.row-content-block {
margin-top: 0;
@apply gl-bg-subtle;

View File

@ -199,13 +199,9 @@ class Projects::IssuesController < Projects::ApplicationController
new_project = Project.find(params[:move_to_project_id])
return render_404 unless issue.can_move?(current_user, new_project)
@issue = if project.work_item_move_and_clone_flag_enabled?
::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user, target_namespace: new_project.project_namespace
).execute[:work_item]
else
::Issues::MoveService.new(container: project, current_user: current_user).execute(issue, new_project)
end
@issue = ::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user, target_namespace: new_project.project_namespace
).execute[:work_item]
end
respond_to do |format|

View File

@ -14,25 +14,16 @@ module Mutations
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20816')
issue = authorized_find!(project_path: project_path, iid: iid)
source_project = issue.project
target_project = resolve_project(full_path: target_project_path).sync
begin
moved_issue = if source_project.work_item_move_and_clone_flag_enabled?
response = ::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user,
target_namespace: target_project.project_namespace
).execute
response = ::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user,
target_namespace: target_project.project_namespace
).execute
errors = response.message if response.error?
response.payload[:work_item]
else
::Issues::MoveService.new(
container: source_project, current_user: current_user
).execute(issue, target_project)
end
rescue ::Issues::MoveService::MoveError => e
errors = e.message
errors = response.message if response.error?
moved_issue = response.payload[:work_item]
end
{

View File

@ -14,17 +14,17 @@ module Resolvers
type ::Types::WorkItems::LinkedItemType.connection_type, null: true
def resolve_with_lookahead(**args)
apply_lookahead(related_work_items(args))
if Feature.enabled?(:batch_load_linked_items, work_item.resource_parent, type: :wip)
bulk_load_linked_items(args[:filter])
else
offset_pagination(
work_item.linked_work_items(authorize: false, link_type: args[:filter])
)
end
end
private
def related_work_items(args)
offset_pagination(
work_item.linked_work_items(authorize: false, link_type: args[:filter])
)
end
def work_item
object.is_a?(Issue) ? WorkItem.find_by_id(object.id) : object.work_item
end
@ -33,6 +33,34 @@ module Resolvers
def node_selection(selection = lookahead)
super.selection(:work_item)
end
def bulk_load_linked_items(link_type)
# Calculate the current nesting level of linked items in the context path
nesting_level = context[:current_path].count('linkedItems')
batch_key = "linked_items_level_#{nesting_level}"
BatchLoader::GraphQL.for(work_item.id).batch(key: batch_key, cache: false) do |item_ids, loader, _args|
preloads = [:author, :work_item_type, { project: [:route, { namespace: :route }] }]
linked_items = apply_lookahead(WorkItem.linked_items_for(item_ids, preload: preloads, link_type: link_type))
grouped_by_source = linked_items_grouped_by_source(linked_items, item_ids)
# Assign the grouped items to each work item ID in the batch loader
item_ids.each do |id|
loader.call(id, grouped_by_source[id] || [])
end
end
end
def linked_items_grouped_by_source(linked_items, item_ids)
linked_items.each_with_object({}) do |item, result|
# Find the ID of the item that this item links to
target_id = [item.issue_link_source_id, item.issue_link_target_id].find { |id| id != item.id }
next unless item_ids.include?(target_id)
result[target_id] ||= []
result[target_id] << item
end
end
end
end
end

View File

@ -7,13 +7,10 @@ module Types
value 'IDLE',
description: "Runner is idle.",
value: :idle,
experiment: { milestone: '15.7' }
value: :idle
value 'ACTIVE',
description: 'Runner is busy.',
value: :active,
experiment: { milestone: '17.2' }
value: :active
end
end
end

View File

@ -27,8 +27,7 @@ module Types
field :job_execution_status,
Types::Ci::RunnerJobExecutionStatusEnum,
null: true,
description: 'Job execution status of the runner manager.',
experiment: { milestone: '16.3' }
description: 'Job execution status of the runner manager.'
field :platform_name, GraphQL::Types::String, null: true,
description: 'Platform provided by the runner manager.',
method: :platform

View File

@ -61,8 +61,7 @@ module Types
field :job_execution_status,
Types::Ci::RunnerJobExecutionStatusEnum,
null: true,
description: 'Job execution status of the runner.',
experiment: { milestone: '15.7' }
description: 'Job execution status of the runner.'
field :jobs, ::Types::Ci::JobInterface.connection_type, null: true,
description: 'Jobs assigned to the runner. This field can only be resolved for one runner in any single request.',
authorize: :read_builds,

View File

@ -41,10 +41,6 @@ module TreeHelper
# `username-branchname-patch-epoch`
# where `epoch` is the last 5 digits of the time since epoch (in
# milliseconds)
#
# Note: this correlates with how the WebIDE formats the branch name
# and if this implementation changes, so should the `placeholderBranchName`
# definition in app/assets/javascripts/ide/stores/modules/commit/getters.js
def patch_branch_name(ref)
return unless current_user

View File

@ -1048,10 +1048,6 @@ class Group < Namespace
].compact.min
end
def work_item_move_and_clone_flag_enabled?
feature_flag_enabled_for_self_or_ancestor?(:work_item_move_and_clone, type: :beta)
end
def work_items_feature_flag_enabled?
feature_flag_enabled_for_self_or_ancestor?(:work_items)
end

View File

@ -71,10 +71,6 @@ module Namespaces
assign_attributes(attributes_to_sync)
end
def work_item_move_and_clone_flag_enabled?
project.work_item_move_and_clone_flag_enabled?
end
# It's always 1 project but it has to be an AR relation
def all_projects
Project.where(id: project.id)

View File

@ -3367,10 +3367,6 @@ class Project < ApplicationRecord
pending_delete? || hidden?
end
def work_item_move_and_clone_flag_enabled?
Feature.enabled?(:work_item_move_and_clone, self, type: :beta) || group&.work_item_move_and_clone_flag_enabled?
end
def work_items_feature_flag_enabled?
group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self)
end

View File

@ -130,6 +130,19 @@ class WorkItem < Issue
])
end
def linked_items_for(target_ids, preload: nil, link_type: nil)
select_query =
select('issues.*,
issue_links.id AS issue_link_id,
issue_links.link_type AS issue_link_type_value,
issue_links.target_id AS issue_link_source_id,
issue_links.source_id AS issue_link_target_id,
issue_links.created_at AS issue_link_created_at,
issue_links.updated_at AS issue_link_updated_at')
ordered_linked_items(select_query, ids: target_ids, link_type: link_type, preload: preload)
end
override :related_link_class
def related_link_class
WorkItems::RelatedWorkItemLink
@ -144,6 +157,25 @@ class WorkItem < Issue
def non_widgets
[:pending_escalations]
end
def ordered_linked_items(select_query, ids: [], link_type: nil, preload: nil)
type_condition =
if link_type == WorkItems::RelatedWorkItemLink::TYPE_RELATES_TO
" AND issue_links.link_type = #{WorkItems::RelatedWorkItemLink.link_types[link_type]}"
else
""
end
query_ids = sanitize_sql_array(['?', Array.wrap(ids)])
select_query
.joins("INNER JOIN issue_links ON
(issue_links.source_id = issues.id AND issue_links.target_id IN (#{query_ids})#{type_condition})
OR
(issue_links.target_id = issues.id AND issue_links.source_id IN (#{query_ids})#{type_condition})")
.preload(preload)
.reorder(linked_items_keyset_order)
end
end
def create_dates_source_from_current_dates
@ -254,14 +286,14 @@ class WorkItem < Issue
def linked_work_items(current_user = nil, authorize: true, preload: nil, link_type: nil)
return [] if new_record?
linked_work_items = linked_work_items_query(link_type)
.preload(preload)
.reorder(self.class.linked_items_keyset_order)
return linked_work_items unless authorize
linked_items =
self.class.ordered_linked_items(linked_issues_select, ids: id, link_type: link_type, preload: preload)
return linked_items unless authorize
cross_project_filter = ->(work_items) { work_items.where(project: project) }
Ability.work_items_readable_by_user(
linked_work_items,
linked_items,
current_user,
filters: { read_cross_project: cross_project_filter }
)
@ -400,21 +432,6 @@ class WorkItem < Issue
end
end
def linked_work_items_query(link_type)
type_condition =
if link_type == WorkItems::RelatedWorkItemLink::TYPE_RELATES_TO
" AND issue_links.link_type = #{WorkItems::RelatedWorkItemLink.link_types[link_type]}"
else
""
end
linked_issues_select
.joins("INNER JOIN issue_links ON
(issue_links.source_id = issues.id AND issue_links.target_id = #{id}#{type_condition})
OR
(issue_links.target_id = issues.id AND issue_links.source_id = #{id}#{type_condition})")
end
def hierarchy_supports_parent?
::WorkItems::HierarchyRestriction.find_by_child_type_id(work_item_type_id).present?
end

View File

@ -1,42 +0,0 @@
# frozen_string_literal: true
# Service for setting the initial copy_state on the target DesignCollection
# and queuing a CopyDesignCollectionWorker.
module DesignManagement
module CopyDesignCollection
class QueueService
def initialize(current_user, issue, target_issue)
@current_user = current_user
@issue = issue
@target_issue = target_issue
@target_design_collection = target_issue.design_collection
end
def execute
return error('User cannot copy designs to issue') unless user_can_copy?
return error('Target design collection copy state must be `ready`') unless target_design_collection.can_start_copy?
target_design_collection.start_copy!
DesignManagement::CopyDesignCollectionWorker.perform_async(current_user.id, issue.id, target_issue.id)
ServiceResponse.success
end
private
delegate :design_collection, to: :issue
attr_reader :current_user, :issue, :target_design_collection, :target_issue
def error(message)
ServiceResponse.error(message: message)
end
def user_can_copy?
current_user.can?(:read_design, issue) &&
current_user.can?(:admin_issue, target_issue)
end
end
end
end

View File

@ -1,124 +0,0 @@
# frozen_string_literal: true
module Issues
class CloneService < Issuable::Clone::BaseService
CloneError = Class.new(StandardError)
def execute(issue, target_project, with_notes: false)
@target_project = target_project
@with_notes = with_notes
verify_can_clone_issue!(issue, target_project)
super(issue, target_project)
notify_participants
queue_copy_designs
new_entity
end
private
attr_reader :target_project
attr_reader :with_notes
def verify_can_clone_issue!(issue, target_project)
unless issue.supports_move_and_clone?
raise CloneError, s_('CloneIssue|Cannot clone issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type }
end
unless issue.can_clone?(current_user, target_project)
raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions.')
end
if target_project.pending_delete?
raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.')
end
end
def update_new_entity
# we don't call `super` because we want to be able to decide whether or not to copy all comments over.
update_new_entity_description
if with_notes
copy_notes
copy_resource_events
end
end
def update_old_entity
# no-op
# The base_service closes the old issue, we don't want that, so we override here so nothing happens.
end
def create_new_entity
new_params = {
id: nil,
iid: nil,
relative_position: relative_position,
project: target_project,
author: current_user,
assignee_ids: original_entity.assignee_ids
}
new_params = original_entity.serializable_hash.symbolize_keys.except(:project_id, :author_id).merge(new_params)
new_params = new_params.merge(rewritten_old_entity_attributes)
new_params.delete(:imported_from)
new_params.delete(:created_at)
new_params.delete(:updated_at)
# spam checking is not necessary, as no new content is being created.
# Skip creation of system notes for existing attributes of the issue when cloning with notes.
# The system notes of the old issue are copied over so we don't want to end up with duplicate notes.
# When cloning without notes, we want to generate system notes for the attributes that were copied.
create_result = CreateService.new(
container: target_project,
current_user: current_user,
params: new_params,
perform_spam_check: false
).execute(skip_system_notes: with_notes)
raise CloneError, create_result.errors.join(', ') if create_result.error? && create_result[:issue].blank?
create_result[:issue]
end
def queue_copy_designs
return unless original_entity.designs.present?
response = DesignManagement::CopyDesignCollection::QueueService.new(
current_user,
original_entity,
new_entity
).execute
log_error(response.message) if response.error?
end
def notify_participants
notification_service.async.issue_cloned(original_entity, new_entity, current_user)
end
def add_note_from
SystemNoteService.noteable_cloned(
new_entity,
target_project,
original_entity,
current_user,
direction: :from,
created_at: new_entity.created_at
)
end
def add_note_to
SystemNoteService.noteable_cloned(original_entity, old_project,
new_entity, current_user,
direction: :to)
end
end
end
Issues::CloneService.prepend_mod_with('Issues::CloneService')

View File

@ -1,224 +0,0 @@
# frozen_string_literal: true
module Issues
class MoveService < Issuable::Clone::BaseService
extend ::Gitlab::Utils::Override
BATCH_SIZE = 100
MoveError = Class.new(StandardError)
def execute(issue, target_project, move_any_issue_type = false)
@move_any_issue_type = move_any_issue_type
@target_project = target_project
verify_can_move_issue!(issue, target_project)
super(issue, target_project)
notify_participants
# Updates old issue sent notifications allowing
# to receive service desk emails on the new moved issue.
update_service_desk_sent_notifications
copy_email_participants
queue_copy_designs
copy_timelogs
new_entity
end
private
attr_reader :target_project, :move_any_issue_type
override :after_clone_actions
def after_clone_actions
move_children
end
def move_children
WorkItems::ParentLink.for_parents(original_entity).each do |link|
new_child = self.class.new(
container: container,
current_user: current_user
).execute(
::Issue.find(link.work_item_id),
target_project,
true
)
WorkItems::ParentLink.create!(work_item_id: new_child.id, work_item_parent_id: new_entity.id)
end
end
def verify_can_move_issue!(issue, target_project)
unless issue.supports_move_and_clone? || move_any_issue_type
raise MoveError, s_('MoveIssue|Cannot move issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type }
end
unless issue.can_move?(current_user, @target_project)
raise MoveError, s_('MoveIssue|Cannot move issue due to insufficient permissions.')
end
if @project == @target_project
raise MoveError, s_('MoveIssue|Cannot move issue to project it originates from.')
end
end
def update_service_desk_sent_notifications
context = { project_id: new_entity.project_id, noteable_id: new_entity.id }
original_entity.run_after_commit_or_now do
next unless from_service_desk?
sent_notifications.update_all(**context)
end
end
def copy_email_participants
new_attributes = { id: nil, issue_id: new_entity.id }
new_participants = original_entity.issue_email_participants.dup
new_participants.each do |participant|
participant.assign_attributes(new_attributes)
end
IssueEmailParticipant.bulk_insert!(new_participants)
end
override :update_old_entity
def update_old_entity
super
recreate_related_issues
mark_as_moved
end
override :update_new_entity
def update_new_entity
super
copy_contacts
end
def create_new_entity
new_params = {
id: nil,
iid: nil,
relative_position: relative_position,
project: target_project,
author: original_entity.author,
assignee_ids: original_entity.assignee_ids,
moved_issue: true,
imported_from: :none
}
new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
new_params = new_params.merge(rewritten_old_entity_attributes)
# spam checking is not necessary, as no new content is being created.
# Skip creation of system notes for existing attributes of the issue. The system notes of the old
# issue are copied over so we don't want to end up with duplicate notes.
create_result = CreateService.new(
container: @target_project,
current_user: @current_user,
params: new_params,
perform_spam_check: false
).execute(skip_system_notes: true)
raise MoveError, create_result.errors.join(', ') if create_result.error? && create_result[:issue].blank?
create_result[:issue]
end
def queue_copy_designs
return unless original_entity.designs.present?
response = DesignManagement::CopyDesignCollection::QueueService.new(
current_user,
original_entity,
new_entity
).execute
log_error(response.message) if response.error?
end
def copy_timelogs
return if original_entity.timelogs.empty?
WorkItems::CopyTimelogsWorker.perform_async(original_entity.id, new_entity.id)
end
def mark_as_moved
original_entity.update(moved_to: new_entity)
end
def recreate_related_issues
source_issue_links = IssueLink.for_source(original_entity)
target_issue_links = IssueLink.for_target(original_entity)
source_issue_links.each_batch(of: BATCH_SIZE) do |links_batch|
new_links = new_links(links_batch, reference_attribute: 'source_id')
::IssueLink.insert_all!(new_links) if new_links.any?
end
target_issue_links.each_batch(of: BATCH_SIZE) do |links_batch|
new_links = new_links(links_batch, reference_attribute: 'target_id')
::IssueLink.insert_all!(new_links) if new_links.any?
end
source_issue_links.each_batch(of: BATCH_SIZE) do |links_batch|
links_batch.delete_all
end
target_issue_links.each_batch(of: BATCH_SIZE) do |links_batch|
links_batch.delete_all
end
end
def new_links(links_batch, reference_attribute:)
links_batch.map do |link|
link.attributes.except('id', 'namespace_id').merge(reference_attribute => new_entity.id)
end
end
def copy_contacts
return unless original_entity.project.root_ancestor == new_entity.project.root_ancestor
new_entity.customer_relations_contacts = original_entity.customer_relations_contacts
end
def notify_participants
context = { original: original_entity, new: new_entity, user: @current_user, service: notification_service }
original_entity.run_after_commit_or_now do
context[:service].async.issue_moved(context[:original], context[:new], context[:user])
end
end
def add_note_from
SystemNoteService.noteable_moved(
new_entity,
target_project,
original_entity,
current_user,
direction: :from
)
end
def add_note_to
SystemNoteService.noteable_moved(
original_entity,
old_project,
new_entity,
current_user,
direction: :to
)
end
end
end
Issues::MoveService.prepend_mod_with('Issues::MoveService')

View File

@ -103,16 +103,11 @@ module Issues
update(issue)
if container.work_item_move_and_clone_flag_enabled?
move_service_container = target_container.is_a?(Project) ? target_container.project_namespace : target_container
::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user, target_namespace: move_service_container
).execute[:work_item]
elsif target_container.is_a?(Project) || target_container.is_a?(Namespaces::ProjectNamespace)
::Issues::MoveService.new(
container: project, current_user: current_user
).execute(issue, target_container)
end
move_service_container = target_container.is_a?(Project) ? target_container.project_namespace : target_container
::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user, target_namespace: move_service_container
).execute[:work_item]
end
private
@ -157,17 +152,12 @@ module Issues
# we've pre-empted this from running in #execute, so let's go ahead and update the Issue now.
update(issue)
if container.work_item_move_and_clone_flag_enabled?
clone_service_container = target_container.is_a?(Project) ? target_container.project_namespace : target_container
::WorkItems::DataSync::CloneService.new(
work_item: issue, current_user: current_user, target_namespace: clone_service_container,
params: { clone_with_notes: with_notes }
).execute[:work_item]
elsif target_container.is_a?(Project) || target_container.is_a?(Namespaces::ProjectNamespace)
Issues::CloneService.new(container: project, current_user: current_user).execute(
issue, target_container, with_notes: with_notes
)
end
clone_service_container = target_container.is_a?(Project) ? target_container.project_namespace : target_container
::WorkItems::DataSync::CloneService.new(
work_item: issue, current_user: current_user, target_namespace: clone_service_container,
params: { clone_with_notes: with_notes }
).execute[:work_item]
end
def create_merge_request_from_quick_action

View File

@ -65,6 +65,9 @@
= webpack_bundle_tag 'super_sidebar'
- if ENV['BABEL_ENV'] == 'istanbul'
= webpack_bundle_tag 'coverage_persistence'
- if vite_enabled?
= render 'layouts/vite_main'
- else

View File

@ -27,6 +27,7 @@ const plugins = [
'@babel/plugin-transform-class-static-block',
];
const env = {};
// Jest is running in node environment
const isJest = Boolean(process.env.JEST_WORKER_ID);
if (isJest) {
@ -40,6 +41,22 @@ if (isJest) {
},
],
];
} else {
env.istanbul = {
plugins: [
[
'istanbul',
{
extension: ['.js', '.vue', '.mjs', '.cjs'],
},
],
],
};
}
module.exports = { presets, plugins, sourceType: 'unambiguous' };
module.exports = {
presets,
plugins,
sourceType: 'unambiguous',
env,
};

View File

@ -1,9 +0,0 @@
---
name: work_item_move_and_clone
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/15470
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179494
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/525524
milestone: '17.10'
group: group::project management
type: beta
default_enabled: true

View File

@ -1,8 +1,9 @@
---
name: allow_organization_creation
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/441531
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147930
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/452062
milestone: '16.11'
type: development
type: wip
group: group::organizations
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: batch_load_linked_items
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/512056
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189332
rollout_issue_url:
milestone: '18.0'
group: group::product planning
type: wip
default_enabled: false

View File

@ -1,8 +1,9 @@
---
name: edit_user_profile_vue
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389918
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122402
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414552
milestone: '16.1'
type: development
type: wip
group: group::organizations
default_enabled: false

View File

@ -1,8 +1,9 @@
---
name: explore_topics_cleaned_path
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/393166
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122970
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414793
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414892
milestone: '16.1'
type: development
type: wip
group: group::organizations
default_enabled: false

View File

@ -1,8 +1,9 @@
---
name: optional_personal_namespace
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427730
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137713
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431978
milestone: '16.8'
type: development
type: wip
group: group::organizations
default_enabled: false

View File

@ -1,8 +1,9 @@
---
name: profile_tabs_vue
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/9056
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109422
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388708
milestone: '15.9'
type: development
type: wip
group: group::organizations
default_enabled: false

View File

@ -1,8 +1,9 @@
---
name: ui_for_organizations
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/17432
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122866
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414592
milestone: '16.1'
type: development
type: wip
group: group::organizations
default_enabled: false

View File

@ -278,6 +278,7 @@ module.exports = {
return {
default: defaultEntries,
sentry: './sentry/index.js',
coverage_persistence: './entrypoints/coverage_persistence.js',
performance_bar: './entrypoints/performance_bar.js',
jira_connect_app: './jira_connect/subscriptions/index.js',
sandboxed_mermaid: './lib/mermaid.js',

View File

@ -0,0 +1,8 @@
---
migration_job_name: MarkAdminBotRunnersAsHosted
description: Mark runners created by admin bot as hosted on GitLab Dedicated
feature_category: hosted_runners
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183329
milestone: '18.0'
queued_migration_version: 20250505095336
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class QueueMarkAdminBotRunnersAsHosted < Gitlab::Database::Migration[2.3]
milestone '18.0'
# Select the applicable gitlab schema for your batched background migration
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = "MarkAdminBotRunnersAsHosted"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:ci_runners,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :ci_runners, :id, [])
end
end

View File

@ -0,0 +1 @@
75166bbf5d04065b48b810200cada005c73109b8da8ccebc6ed9f3195951928a

View File

@ -200,9 +200,9 @@ gitlab:
memory: 32Mi
```
#### Configure concurrency rate limiting
#### Configure concurrency limiting
As well as using cgroups, you can use concurrency limits to further help protect the service from abnormal traffic patterns. For more information, see
You can use concurrency limits to help protect the service from abnormal traffic patterns. For more information, see
[concurrency configuration documentation](concurrency_limiting.md) and [how to monitor limits](monitoring.md#monitor-gitaly-concurrency-limiting).
#### Isolate Gitaly pods

View File

@ -16,22 +16,19 @@ Metric definitions are available:
<!--- start_remove The following content will be removed on remove_date: '2025-08-01' -->
## Monitor Gitaly rate limiting (deprecated)
## Monitor Gitaly rate limiting (removed)
{{< alert type="warning" >}}
This feature was [deprecated](https://gitlab.com/gitlab-org/gitaly/-/issues/5011) in GitLab 17.7
and is planned for removal in 18.0. Use [concurrency limiting](concurrency_limiting.md) instead.
and was removed in 18.0. Use [concurrency limiting](concurrency_limiting.md) instead.
{{< /alert >}}
Gitaly can be configured to limit requests based on:
- Concurrency of requests.
- A rate limit.
<!--- end_remove -->
Gitaly can be configured to limit requests based on concurrency of requests (adaptive or non-adaptive).
## Monitor Gitaly concurrency limiting
You can observe specific behavior of [concurrency-queued requests](concurrency_limiting.md#limit-rpc-concurrency) using Gitaly logs and Prometheus.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Get started with GitLab Duo Self-Hosted.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Get started with GitLab Duo Self-Hosted.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Configure your GitLab instance to use GitLab Duo Self-Hosted.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Enable logging for self-hosted models.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Supported LLM Serving Platforms.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Supported models and hardware requirements.

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Troubleshooting tips for deploying GitLab Duo Self-Hosted

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Custom Models
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Self-Hosted model setup Related topics

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: Duo Chat
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Documentation for the REST API for Duo Chat.

View File

@ -23156,7 +23156,7 @@ CI/CD variables for a project.
| <a id="cirunnerephemeralregisterurl"></a>`ephemeralRegisterUrl` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 15.11. **Status**: Experiment. URL of the registration page of the runner manager. Only available for the creator of the runner for a limited time during registration. |
| <a id="cirunnergroups"></a>`groups` | [`GroupInterfaceConnection`](#groupinterfaceconnection) | Groups the runner is associated with. For group runners only. (see [Connections](#connections)) |
| <a id="cirunnerid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner. |
| <a id="cirunnerjobexecutionstatus"></a>`jobExecutionStatus` {{< icon name="warning-solid" >}} | [`CiRunnerJobExecutionStatus`](#cirunnerjobexecutionstatus) | **Introduced** in GitLab 15.7. **Status**: Experiment. Job execution status of the runner. |
| <a id="cirunnerjobexecutionstatus"></a>`jobExecutionStatus` | [`CiRunnerJobExecutionStatus`](#cirunnerjobexecutionstatus) | Job execution status of the runner. |
| <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
| <a id="cirunnermaintenancenote"></a>`maintenanceNote` | [`String`](#string) | Runner's maintenance notes. |
| <a id="cirunnermaintenancenotehtml"></a>`maintenanceNoteHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `maintenance_note`. |
@ -23335,7 +23335,7 @@ Returns [`[CiRunnerCloudProvisioningStep!]`](#cirunnercloudprovisioningstep).
| <a id="cirunnermanagerexecutorname"></a>`executorName` | [`String`](#string) | Executor last advertised by the runner. |
| <a id="cirunnermanagerid"></a>`id` | [`CiRunnerManagerID!`](#cirunnermanagerid) | ID of the runner manager. |
| <a id="cirunnermanageripaddress"></a>`ipAddress` | [`String`](#string) | IP address of the runner manager. |
| <a id="cirunnermanagerjobexecutionstatus"></a>`jobExecutionStatus` {{< icon name="warning-solid" >}} | [`CiRunnerJobExecutionStatus`](#cirunnerjobexecutionstatus) | **Introduced** in GitLab 16.3. **Status**: Experiment. Job execution status of the runner manager. |
| <a id="cirunnermanagerjobexecutionstatus"></a>`jobExecutionStatus` | [`CiRunnerJobExecutionStatus`](#cirunnerjobexecutionstatus) | Job execution status of the runner manager. |
| <a id="cirunnermanagerplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner manager. |
| <a id="cirunnermanagerrevision"></a>`revision` | [`String`](#string) | Revision of the runner. |
| <a id="cirunnermanagerrunner"></a>`runner` | [`CiRunner`](#cirunner) | Runner configuration for the runner manager. |
@ -42827,8 +42827,8 @@ Runner cloud provider.
| Value | Description |
| ----- | ----------- |
| <a id="cirunnerjobexecutionstatusactive"></a>`ACTIVE` {{< icon name="warning-solid" >}} | **Introduced** in GitLab 17.2. **Status**: Experiment. Runner is busy. |
| <a id="cirunnerjobexecutionstatusidle"></a>`IDLE` {{< icon name="warning-solid" >}} | **Introduced** in GitLab 15.7. **Status**: Experiment. Runner is idle. |
| <a id="cirunnerjobexecutionstatusactive"></a>`ACTIVE` | Runner is busy. |
| <a id="cirunnerjobexecutionstatusidle"></a>`IDLE` | Runner is idle. |
### `CiRunnerMembershipFilter`
@ -48896,6 +48896,7 @@ Field that are available while modifying the custom mapping attributes for an HT
| <a id="boardissueinputor"></a>`or` | [`UnionedIssueFilterInput`](#unionedissuefilterinput) | List of arguments with inclusive OR. |
| <a id="boardissueinputreleasetag"></a>`releaseTag` | [`String`](#string) | Filter by release tag. |
| <a id="boardissueinputsearch"></a>`search` | [`String`](#string) | Search query for issue title or description. |
| <a id="boardissueinputstatus"></a>`status` {{< icon name="warning-solid" >}} | [`WorkItemWidgetStatusFilterInput`](#workitemwidgetstatusfilterinput) | **Deprecated:** **Status**: Experiment. Introduced in GitLab 18.0. |
| <a id="boardissueinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. |
| <a id="boardissueinputweight"></a>`weight` | [`String`](#string) | Filter by weight. |
| <a id="boardissueinputweightwildcardid"></a>`weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. |

View File

@ -941,6 +941,44 @@ path.Clean("../../etc/passwd")
// renders the path to "../../etc/passwd"; the file path will look back up to two parent directories!
```
#### Safe File Operations in Go
The Go standard library provides basic file operations like `os.Open`, `os.ReadFile`, `os.WriteFile`, and `os.Readlink`. However, these functions do not prevent path traversal attacks, where user-supplied paths can escape the intended directory and access sensitive system files.
Example of unsafe usage:
```go
// Vulnerable: user input is directly used in the path
os.Open(filepath.Join("/app/data", userInput))
os.ReadFile(filepath.Join("/app/data", userInput))
os.WriteFile(filepath.Join("/app/data", userInput), []byte("data"), 0644)
os.Readlink(filepath.Join("/app/data", userInput))
```
To mitigate these risks, use the [`safeopen`](https://pkg.go.dev/github.com/google/safeopen) library functions. These functions enforce a secure root directory and sanitize file paths:
Example of safe usage:
```go
safeopen.OpenBeneath("/app/data", userInput)
safeopen.ReadFileBeneath("/app/data", userInput)
safeopen.WriteFileBeneath("/app/data", []byte("data"), 0644)
safeopen.ReadlinkBeneath("/app/data", userInput)
```
Benefits:
- Prevents path traversal attacks (`../` sequences).
- Restricts file operations to trusted root directories.
- Secures against unauthorized file reads, writes, and symlink resolutions.
- Provides simple, developer-friendly replacements.
References:
- [Go Standard Library os Package](https://pkg.go.dev/os)
- [Safe Go Libraries Announcement](https://bughunters.google.com/blog/4925068200771584/the-family-of-safe-golang-libraries-is-growing)
- [OWASP Path Traversal Cheat Sheet](https://owasp.org/www-community/attacks/Path_Traversal)
## OS command injection guidelines
Command injection is an issue in which an attacker is able to execute arbitrary commands on the host

View File

@ -1,5 +1,5 @@
---
stage: AI-Powered
stage: AI-powered
group: AI Framework
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Set up your self-hosted model GitLab AI gateway

View File

@ -71,7 +71,7 @@ This table shows the features available with the Jira issues integration and the
| [View a list of Jira issues](configure.md#view-jira-issues). | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No |
| [Create a Jira issue for a vulnerability](configure.md#create-a-jira-issue-for-a-vulnerability). | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No |
| Create a GitLab branch from a Jira issue. | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes, in the Jira issue's development panel. |
| Mention a Jira issue ID in a GitLab merge request, branch name, or any of the last 5,000 commits to the branch after the last successful deployment to the environment to sync a GitLab deployment to a Jira issue. | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes, in the Jira issue's development panel. |
| Mention a Jira issue ID in a GitLab merge request, branch name, or any of the last 2,000 commits to the branch after the last successful deployment to the environment to sync a GitLab deployment to a Jira issue. | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes, in the Jira issue's development panel. |
## Privacy considerations

View File

@ -66,7 +66,7 @@ For the [GitLab for Jira Cloud app](connect-app.md), the following information i
|---------------------------------------------|-------------------------------------------------------|
| Merge request title or description | Link to the merge request<br>Link to the deployment<br>Link to the pipeline through the merge request title<br>Link to the pipeline through the merge request description ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/390888) in GitLab 15.10)<br>Link to the branch ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354373) in GitLab 15.11)<br>Reviewer information and approval status ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/364273) in GitLab 16.5) |
| Branch name | Link to the branch<br>Link to the deployment |
| Commit message | Link to the commit<br>Link to the deployment from up to 5,000 commits after the last successful deployment to the environment <sup>1</sup> <sup>2</sup> |
| Commit message | Link to the commit<br>Link to the deployment from up to 2,000 commits after the last successful deployment to the environment <sup>1</sup> <sup>2</sup> |
| [Jira Smart Commit](#jira-smart-commits) | Custom comment, logged time, or workflow transition |
**Footnotes:**

View File

@ -79,7 +79,7 @@ Details of each dependency are listed, sorted by decreasing severity of vulnerab
| Packager | The packager used to install the dependency. |
| Location | For system dependencies, this field lists the image that was scanned. For application dependencies, this field shows a link to the packager-specific lock file in your project that declared the dependency. It also shows the direct [dependents](#dependency-paths), if any. If there are transitive dependencies, selecting **View dependency paths** shows the full path of all dependents. Transitive dependencies are indirect dependents that have a direct dependent as an ancestor. |
| License (for projects only) | Links to dependency's software licenses. A warning badge that includes the number of vulnerabilities detected in the dependency. |
| Projects (for groups only) | Links to the project with the dependency. If multiple projects have the same dependency, the total number of these projects is shown. To go to a project with this dependency, select the **Projects** number, then search for and select its name. The project search feature is supported only on groups that have up to 600 occurrences in their group hierarchy. |
| Projects (for groups only) | Links to the project with the dependency. If multiple projects have the same dependency, the total number of these projects is shown. To go to a project with this dependency, select the **Projects** number, then search for and select its name. |
## Filter dependency list

View File

@ -25,6 +25,7 @@ If this does not work, you can also check the following troubleshooting document
- [Microsoft Visual Studio](../../editor_extensions/visual_studio/visual_studio_troubleshooting.md).
- [JetBrains IDEs](../../editor_extensions/jetbrains_ide/jetbrains_troubleshooting.md).
- [Neovim](../../editor_extensions/neovim/neovim_troubleshooting.md).
- [Eclipse](../../editor_extensions/eclipse/troubleshooting.md).
- [Troubleshooting GitLab Duo](../gitlab_duo/troubleshooting.md).
- [Troubleshooting GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/troubleshooting.md).

View File

@ -53,12 +53,8 @@ default:
- bundle config # Show bundler configuration
- bundle install --jobs=$(nproc) --retry=3
after_script:
# We need this at the very top, because the section_start/section_end logic is defined there.
- source $CI_PROJECT_DIR/scripts/utils.sh
- |
section_start "failure-analyzer" "Report failure category"
$CI_PROJECT_DIR/tooling/lib/tooling/glci/failure_analyzer.rb $CI_JOB_ID || true
section_end "failure-analyzer"
- execute_failure_analyzer
.ruby_matrix:
parallel:

View File

@ -53,12 +53,8 @@ include:
- bundle config # Show bundler configuration
- bundle install --jobs=$(nproc) --retry=3
after_script:
# We need this at the very top, because the section_start/section_end logic is defined there.
- source $CI_PROJECT_DIR/scripts/utils.sh
- |
section_start "failure-analyzer" "Report failure category"
$CI_PROJECT_DIR/tooling/lib/tooling/glci/failure_analyzer.rb $CI_JOB_ID || true
section_end "failure-analyzer"
- execute_failure_analyzer
default:
<<: *default

View File

@ -381,23 +381,15 @@ module API
not_found!('Project') unless new_project
begin
issue = if user_project.work_item_move_and_clone_flag_enabled?
response = ::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user, target_namespace: new_project.project_namespace
).execute
response = ::WorkItems::DataSync::MoveService.new(
work_item: issue, current_user: current_user, target_namespace: new_project.project_namespace
).execute
render_api_error!(response.message, 400) if response.error?
render_api_error!(response.message, 400) if response.error?
response.payload[:work_item]
else
::Issues::MoveService.new(
container: user_project, current_user: current_user
).execute(issue, new_project)
end
issue = response.payload[:work_item]
present issue, with: Entities::Issue, current_user: current_user, project: user_project
rescue ::Issues::MoveService::MoveError => error
render_api_error!(error.message, 400)
end
end
# rubocop: enable CodeReuse/ActiveRecord
@ -421,23 +413,16 @@ module API
not_found!('Project') unless target_project
begin
issue = if user_project.work_item_move_and_clone_flag_enabled?
response = ::WorkItems::DataSync::CloneService.new(
work_item: issue, current_user: current_user, target_namespace: target_project.project_namespace,
params: { clone_with_notes: params[:with_notes] }
).execute
response = ::WorkItems::DataSync::CloneService.new(
work_item: issue, current_user: current_user, target_namespace: target_project.project_namespace,
params: { clone_with_notes: params[:with_notes] }
).execute
render_api_error!(response.message, 400) if response.error?
render_api_error!(response.message, 400) if response.error?
response.payload[:work_item]
else
::Issues::CloneService.new(container: user_project, current_user: current_user)
.execute(issue, target_project, with_notes: params[:with_notes])
end
issue = response.payload[:work_item]
present issue, with: Entities::Issue, current_user: current_user, project: target_project
rescue ::Issues::CloneService::CloneError => error
render_api_error!(error.message, 400)
end
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -7,7 +7,8 @@ module Atlassian
include Gitlab::Routing
include Gitlab::Utils::StrongMemoize
COMMITS_LIMIT = 5_000
COMMITS_LIMIT = 2000
ISSUE_KEY_LIMIT = 500
format_with(:iso8601, &:iso8601)
@ -26,7 +27,8 @@ module Atlassian
expose :generate_deployment_commands_from_integration_configuration, as: :commands
def issue_keys
@issue_keys ||= (issue_keys_from_pipeline + issue_keys_from_commits_since_last_deploy).uniq
@issue_keys ||= (issue_keys_from_pipeline + issue_keys_from_commits_since_last_deploy)
.uniq.first(ISSUE_KEY_LIMIT)
end
def associations
@ -109,20 +111,21 @@ module Atlassian
.successful_deployments
.id_not_in(deployment.id)
.ordered
.find_by_ref(deployment.ref)
.first
&.commit
commit_range = if last_deployed_commit
"#{last_deployed_commit.id}..#{deployment.commit.id}"
else
deployment.commit.id
end
commits = project.repository.commits(
deployment.ref,
before: deployment.commit.created_at,
after: last_deployed_commit&.created_at,
commit_range,
skip_merges: true,
limit: COMMITS_LIMIT
)
# Include this deploy's commit, as the `before:` param in `Repository#list_commits_by` excluded it.
commits << deployment.commit
commits.flat_map do |commit|
JiraIssueKeyExtractor.new(commit.message).issue_keys
end.compact

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This class doesn't create SecuritySetting
# as this feature exists only in EE
class MarkAdminBotRunnersAsHosted < BatchedMigrationJob
feature_category :hosted_runners
def perform; end
end
end
end
# rubocop:disable Layout/LineLength -- If I do multiline, another cop complains about prepend should be last line
Gitlab::BackgroundMigration::MarkAdminBotRunnersAsHosted.prepend_mod_with('Gitlab::BackgroundMigration::MarkAdminBotRunnersAsHosted')
# rubocop:enable Layout/LineLength

View File

@ -109,8 +109,7 @@ module Gitlab
types Issue, WorkItem
condition do
quick_action_target.persisted? &&
current_user.can?(:"clone_#{quick_action_target.to_ability_name}", quick_action_target) &&
can_be_moved_or_cloned?
current_user.can?(:"clone_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :clone do |params = ''|
params = params.split(' ')
@ -147,8 +146,7 @@ module Gitlab
types Issue, WorkItem
condition do
quick_action_target.persisted? &&
current_user.can?(:"move_#{quick_action_target.to_ability_name}", quick_action_target) &&
can_be_moved_or_cloned?
current_user.can?(:"move_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :move do |target_container_path|
target_container = fetch_target_container(target_container_path)
@ -449,12 +447,6 @@ module Gitlab
Group.find_by_full_path(target_container_path)
end
end
def can_be_moved_or_cloned?
return true unless quick_action_target.is_a?(WorkItem) && quick_action_target.work_item_type.epic?
container.work_item_move_and_clone_flag_enabled?
end
end
end
end

View File

@ -304,8 +304,20 @@ module Gitlab
if reviewers.blank?
_("Failed to assign a reviewer because no user was specified.")
else
_('Assigned %{reviewer_users_sentence} as %{reviewer_text}.') % { reviewer_users_sentence: reviewer_users_sentence(users),
reviewer_text: 'reviewer'.pluralize(reviewers.size) }
processed_users = process_reviewer_users(users)
processed_msg = process_reviewer_users_message
if processed_users.present?
[
processed_msg,
_('Assigned %{reviewer_users_sentence} as %{reviewer_text}.') % {
reviewer_users_sentence: reviewer_users_sentence(processed_users),
reviewer_text: 'reviewer'.pluralize(processed_users.size)
}
].compact.join(' ')
else
processed_msg
end
end
end
params do
@ -319,13 +331,15 @@ module Gitlab
extract_users(reviewer_param)
end
command :assign_reviewer, :reviewer do |users|
next if users.empty?
processed_users = process_reviewer_users(users)
next if processed_users.empty?
if quick_action_target.allows_multiple_reviewers?
@updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
@updates[:reviewer_ids] |= users.map(&:id)
@updates[:reviewer_ids] |= processed_users.map(&:id)
else
@updates[:reviewer_ids] = [users.first.id]
@updates[:reviewer_ids] = [processed_users.first.id]
end
end
@ -347,7 +361,19 @@ module Gitlab
if users.blank?
_("Failed to request a review because no user was specified.")
else
_('Requested a review from %{reviewer_users_sentence}.') % { reviewer_users_sentence: reviewer_users_sentence(users) }
processed_users = process_reviewer_users(users)
processed_msg = process_reviewer_users_message
if processed_users.present?
[
processed_msg,
_('Requested a review from %{reviewer_users_sentence}.') % {
reviewer_users_sentence: reviewer_users_sentence(processed_users)
}
].compact.join(' ')
else
processed_msg
end
end
end
params do
@ -361,7 +387,9 @@ module Gitlab
extract_users(reviewer_param)
end
command :request_review do |users|
next if users.empty?
processed_users = process_reviewer_users(users)
next if processed_users.empty?
@updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
@ -370,7 +398,7 @@ module Gitlab
current_user: current_user
)
reviewers_to_add(users).each do |user|
reviewers_to_add(processed_users).each do |user|
if @updates[:reviewer_ids].include?(user.id)
# Request a new review from the reviewer if they are already assigned
service.execute(quick_action_target, user)
@ -479,6 +507,16 @@ module Gitlab
def preferred_auto_merge_strategy(merge_request)
merge_orchestration_service.preferred_auto_merge_strategy(merge_request)
end
# Overriden in EE
def process_reviewer_users(users)
users
end
# Overriden in EE
def process_reviewer_users_message
nil
end
end
end
end

View File

@ -29,24 +29,16 @@ module Gitlab
return Gitlab::SlashCommands::Presenters::Access.new.not_found
end
new_issue = if project.work_item_move_and_clone_flag_enabled?
response = ::WorkItems::DataSync::MoveService.new(
work_item: old_issue, current_user: current_user,
target_namespace: target_project.project_namespace
).execute
response = ::WorkItems::DataSync::MoveService.new(
work_item: old_issue, current_user: current_user,
target_namespace: target_project.project_namespace
).execute
return presenter(old_issue).display_move_error(response.message) if response.error?
return presenter(old_issue).display_move_error(response.message) if response.error?
response[:work_item]
else
::Issues::MoveService.new(
container: project, current_user: current_user
).execute(old_issue, target_project)
end
new_issue = response[:work_item]
presenter(new_issue).present(old_issue)
rescue ::Issues::MoveService::MoveError => e
presenter(old_issue).display_move_error(e.message)
end
private

View File

@ -13232,15 +13232,6 @@ msgstr ""
msgid "Clone with SSH"
msgstr ""
msgid "CloneIssue|Cannot clone issue due to insufficient permissions."
msgstr ""
msgid "CloneIssue|Cannot clone issue to target project as it is pending deletion."
msgstr ""
msgid "CloneIssue|Cannot clone issues of '%{issue_type}' type."
msgstr ""
msgid "CloneWorkItem|Unable to clone. Cloning '%{work_item_type}' is not supported."
msgstr ""
@ -21024,9 +21015,6 @@ msgstr ""
msgid "Dependencies|Export as JSON"
msgstr ""
msgid "Dependencies|Filtering unavailable"
msgstr ""
msgid "Dependencies|Follow the link below to download the export."
msgstr ""
@ -21051,9 +21039,6 @@ msgstr ""
msgid "Dependencies|Packager"
msgstr ""
msgid "Dependencies|Project list unavailable"
msgstr ""
msgid "Dependencies|Projects"
msgstr ""
@ -21090,12 +21075,6 @@ msgstr ""
msgid "Dependencies|There was an error fetching the versions for the selected component. Please try again later."
msgstr ""
msgid "Dependencies|This group exceeds the maximum number of 600 sub-groups. We cannot accurately filter or search the dependency list above this maximum. To view or filter a subset of this information, go to a subgroup's dependency list."
msgstr ""
msgid "Dependencies|This group exceeds the maximum number of sub-groups of 600. We cannot accurately display a project list at this time. Please access a sub-group dependency list to view this information or see the %{linkStart}dependency list help %{linkEnd} page to learn more."
msgstr ""
msgid "Dependencies|This link will expire in %{number} days."
msgstr ""
@ -23041,6 +23020,9 @@ msgstr ""
msgid "DuoCodeReview|I've received your Duo Code Review request, and will review your code shortly."
msgstr ""
msgid "DuoCodeReview|Your account doesn't have GitLab Duo access. Please contact your system administrator for access."
msgstr ""
msgid "DuoCodeReview|is reviewing your merge request and will let you know when it's finished"
msgstr ""
@ -39128,15 +39110,6 @@ msgstr ""
msgid "Move up"
msgstr ""
msgid "MoveIssue|Cannot move issue due to insufficient permissions."
msgstr ""
msgid "MoveIssue|Cannot move issue to project it originates from."
msgstr ""
msgid "MoveIssue|Cannot move issues of '%{issue_type}' type."
msgstr ""
msgid "MoveWorkItem|Unable to move. Moving '%{work_item_type}' is not supported."
msgstr ""
@ -40459,6 +40432,9 @@ msgstr ""
msgid "Non-admin users are restricted to read-only access, in both GitLab UI and API."
msgstr ""
msgid "Non-archived"
msgstr ""
msgid "None"
msgstr ""
@ -46951,6 +46927,9 @@ msgstr ""
msgid "Project ID"
msgstr ""
msgid "Project Status"
msgstr ""
msgid "Project Templates"
msgstr ""

View File

@ -270,6 +270,7 @@
"ajv-formats": "^2.1.1",
"axios-mock-adapter": "^1.15.0",
"babel-jest": "^29.7.0",
"babel-plugin-istanbul": "^7.0.0",
"chalk": "^2.4.1",
"chokidar": "^3.5.3",
"crypto": "^1.0.1",

View File

@ -76,12 +76,8 @@ module QA
click_element 'new-file-menu-item'
end
# Click by JS is needed to bypass the VSCode Web IDE popover
# Change back to regular click_element when vscode_web_ide FF is removed
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/371084
def fork_project
fork_button = find_element('fork-button')
click_by_javascript(fork_button)
click_element 'fork-button'
end
def forked_from?(parent_project_name)

View File

@ -167,7 +167,7 @@ module QA
end
end
# Used for stability, due to feature_caching of vscode_web_ide
# Used for stability
# @param file_name [string] wait for file to be loaded (optional)
def wait_for_ide_to_load(file_name = nil)
page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)

View File

@ -98,6 +98,10 @@ module QA
enabled?(ENV['COVERBAND_ENABLED'], default: false)
end
def istanbul_coverage_enabled?
ENV['BABEL_ENV'] == 'istanbul'
end
def selective_execution_improved_enabled?
enabled?(ENV['SELECTIVE_EXECUTION_IMPROVED'], default: false)
end

View File

@ -26,6 +26,20 @@ RSpec.configure(&:disable_monkey_patching!)
# For JH additionally process when `jh/` exists
require_relative('../../../jh/qa/qa/specs/spec_helper') if GitlabEdition.jh?
front_end_coverage_by_example = {}
def save_front_end_coverage_mapping(map_to_save)
return if map_to_save.empty?
file = "tmp/js-coverage-by-example-#{ENV['CI_JOB_NAME_SLUG'] || 'local'}-#{SecureRandom.hex(6)}.json"
# Write the mapping data
File.write(file, map_to_save.to_json)
QA::Runtime::Logger.info("Saved test coverage mapping data to #{file}")
rescue StandardError => e
QA::Runtime::Logger.error("Failed to save JS coverage mapping data, error: #{e}")
end
RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers
config.include QA::Support::Matchers::EventuallyMatcher
@ -53,6 +67,11 @@ RSpec.configure do |config|
visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.mobile_layout?
# Reset coverage persistence at the start of each test
if Capybara::Session.instance_created? && QA::Runtime::Env.istanbul_coverage_enabled?
Capybara.current_session.execute_script("window.__coveragePathsPersistence.reset()")
end
# Reset fabrication counters tracked in resource base
Thread.current[:api_fabrication] = 0
Thread.current[:browser_ui_fabrication] = 0
@ -78,6 +97,19 @@ RSpec.configure do |config|
QA::Support::PageErrorChecker.log_request_errors(page)
QA::Support::PageErrorChecker.check_page_for_error_code(page) if example.exception
end
# Get coverage paths and store in metadata
if Capybara::Session.instance_created? && QA::Runtime::Env.istanbul_coverage_enabled?
begin
Capybara.current_session.execute_script("window.__coveragePathsPersistence.update()")
coverage_paths = Capybara.current_session.evaluate_script("window.__coveragePathsPersistence.getPaths()")
QA::Runtime::Logger.debug("Coverage paths count: #{coverage_paths.length}")
example.metadata[:coverage_paths] = coverage_paths
front_end_coverage_by_example[example.metadata[:location]] = coverage_paths
rescue StandardError => e
QA::Runtime::Logger.warn("Failed to collect coverage paths: #{e.message}")
end
end
end
config.append_after do |example|
@ -98,6 +130,8 @@ RSpec.configure do |config|
config.after(:suite) do |suite|
# Write all test created resources to JSON file
QA::Tools::TestResourceDataProcessor.write_to_file(suite.reporter.failed_examples.any?)
save_front_end_coverage_mapping(front_end_coverage_by_example) if QA::Runtime::Env.istanbul_coverage_enabled?
end
config.expect_with :rspec do |expectations|

View File

@ -718,3 +718,13 @@ JSON
-H 'Content-type: application/json' \
-d "${json_payload}"
}
function execute_failure_analyzer() {
# IMPORTANT - If you want to change the "failure-analyzer" string,
# please also change the logic for the failure categories, as we rely on this marker.
#
# Class relying on the marker: tooling/lib/tooling/glci/failure_categories/download_job_trace.rb
section_start "failure-analyzer" "Report failure category"
$CI_PROJECT_DIR/tooling/lib/tooling/glci/failure_analyzer.rb $CI_JOB_ID || true
section_end "failure-analyzer"
}

View File

@ -545,21 +545,7 @@ RSpec.describe Projects::IssuesController, :request_store, feature_category: :te
end
end
context 'with work_item_move_and_clone disabled' do
it_behaves_like 'move issue request' do
before do
stub_feature_flags(work_item_move_and_clone: false)
end
end
end
context 'with work_item_move_and_clone enabled' do
it_behaves_like 'move issue request' do
before do
stub_feature_flags(work_item_move_and_clone: true)
end
end
end
it_behaves_like 'move issue request'
end
describe 'PUT #reorder' do

View File

@ -3,13 +3,13 @@
require 'spec_helper'
RSpec.describe 'IDE merge request', :js, feature_category: :web_ide do
include Features::WebIdeSpecHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository, namespace: user.namespace) }
let_it_be(:merge_request) { create(:merge_request, :simple, source_project: project) }
before do
stub_feature_flags(vscode_web_ide: false)
sign_in(user)
visit(merge_request_path(merge_request))
@ -19,10 +19,16 @@ RSpec.describe 'IDE merge request', :js, feature_category: :web_ide do
within '.merge-request' do
click_button 'Code'
end
click_link 'Open in Web IDE'
new_tab = window_opened_by { click_link 'Open in Web IDE' }
switch_to_window new_tab
wait_for_requests
expect(page).not_to have_selector('.monaco-diff-editor')
within_window new_tab do
within_web_ide do
expect(page).to have_css('a[aria-label^="Next Change"]')
end
end
end
end

View File

@ -81,18 +81,24 @@ export const secretDetectionFindings = [
{
message: 'Hello world! glpat-mGYFaXBmNLvLmrEb7xdf',
type: 'GitLab personal access token',
redactedString: 'glpat*****************7xdf',
secret: 'glpat-mGYFaXBmNLvLmrEb7xdf',
},
{
message: 'Second token: gldt-cgyKc1k_AsnEpmP-5fRL',
type: 'GitLab Deploy Token',
redactedString: 'gldt-****************5fRL',
secret: 'gldt-cgyKc1k_AsnEpmP-5fRL',
},
{
message: 'third token: feed_token=ABCDEFGHIJKLMNOPQRSTUVWXYZ',
message: 'Third token: feed_token=ABCDEFGHIJKLMNOPQRSTUVWXYZ',
type: 'Feed Token',
redactedString: 'feed_**********************QRST',
secret: 'feed_token=ABCDEFGHIJKLMNOPQRST',
},
{
message: 'Repeated token: glpat-mGYFaXBmNLvLmrEb7xdf glpat-mGYFaXBmNLvLmrEb7xdf',
type: 'GitLab personal access token',
secret: 'glpat-mGYFaXBmNLvLmrEb7xdf',
},
];

View File

@ -115,15 +115,15 @@ describe('detectAndConfirmSensitiveTokens', () => {
modalHtmlMessage: expect.any(String),
};
describe('with single findings', () => {
const [{ message, type, redactedString }] = findings;
describe('with single finding', () => {
const [{ message, type, secret }] = findings;
it('should call confirmAction with correct parameters', async () => {
await detectAndConfirmSensitiveTokens({ content: message });
const confirmActionArgs = confirmAction.mock.calls[0][1];
expect(confirmActionArgs).toMatchObject(baseConfirmActionParams);
expect(confirmActionArgs.title).toBe('Warning: Potential secret detected');
expect(confirmActionArgs.modalHtmlMessage).toContain(`${type}: ${redactedString}`);
expect(confirmActionArgs.modalHtmlMessage).toContain(`${type}: ${secret}`);
});
});
@ -137,12 +137,28 @@ describe('detectAndConfirmSensitiveTokens', () => {
expect(confirmActionArgs).toMatchObject(baseConfirmActionParams);
expect(confirmActionArgs.title).toBe('Warning: Potential secrets detected');
findings.forEach(({ type, redactedString }) => {
expect(confirmActionArgs.modalHtmlMessage).toContain(`${type}: ${redactedString}`);
findings.forEach(({ type, secret }) => {
expect(confirmActionArgs.modalHtmlMessage).toContain(`${type}: ${secret}`);
});
});
});
describe('with repeated finding', () => {
const { message, type, secret } = findings.at(-1);
it('should call confirmAction with correct parameters', async () => {
await detectAndConfirmSensitiveTokens({ content: message });
const confirmActionArgs = confirmAction.mock.calls[0][1];
const stringToMatch = `${type}: ${secret}`;
expect(confirmActionArgs).toMatchObject(baseConfirmActionParams);
expect(confirmActionArgs.title).toBe('Warning: Potential secret detected');
const tokenRegex = new RegExp(stringToMatch, 'g');
const matches = confirmActionArgs.modalHtmlMessage.match(tokenRegex);
expect(matches).toHaveLength(1);
});
});
describe('with different content type', () => {
const testCases = [
[

View File

@ -26,7 +26,7 @@ RSpec.describe Mutations::Issues::Move, feature_category: :api do
it 'returns error message' do
expect(resolve[:issue]).to eq(nil)
expect(resolve[:errors].first).to eq(permissions_error_message)
expect(resolve[:errors].first).to eq("Unable to move. You have insufficient permissions.")
end
end
@ -43,23 +43,5 @@ RSpec.describe Mutations::Issues::Move, feature_category: :api do
end
end
context 'with work_item_move_and_clone disabled' do
it_behaves_like 'moving work item mutation' do
let(:permissions_error_message) { "Cannot move issue due to insufficient permissions." }
before do
stub_feature_flags(work_item_move_and_clone: false)
end
end
end
context 'with work_item_move_and_clone enabled' do
it_behaves_like 'moving work item mutation' do
let(:permissions_error_message) { "Unable to move. You have insufficient permissions." }
before do
stub_feature_flags(work_item_move_and_clone: true)
end
end
end
it_behaves_like 'moving work item mutation'
end

View File

@ -330,7 +330,9 @@ RSpec.describe Atlassian::JiraConnect::Serializers::DeploymentEntity, feature_ca
last_deploy.update!(ref: 'foo')
end
it_behaves_like 'ignores that deployment'
it 'extracts issue keys from commits made since the last deploy regardless of ref' do
expect(subject.issue_keys).to contain_exactly('add a', 'add d')
end
end
context 'when the deploy was not successful' do

View File

@ -18,33 +18,30 @@ RSpec.describe Gitlab::Doctor::EncryptionKeys, feature_category: :shared do
context 'when no encrypted attributes exist' do
it 'outputs "NONE"' do
expect(logger).to receive(:info).with(/Encryption keys usage for DependencyProxy::GroupSetting: NONE/)
expect(logger).to receive(:info).with("Encryption keys usage for DependencyProxy::GroupSetting: NONE")
doctor_encryption_secrets
end
end
context 'when encrypted attributes exist' do
# This will work in Rails 7.1.4, see https://github.com/rails/rails/issues/52003#issuecomment-2149673942
#
# let!(:key_provider1) { ActiveRecord::Encryption::DerivedSecretKeyProvider.new(SecureRandom.base64(32)) }
#
# before do
# ActiveRecord::Encryption.with_encryption_context(key_provider: key_provider1) do
# create(:dependency_proxy_group_setting)
# end
# end
#
# Until then, we can only use the default key provider
let!(:key_provider1) { ActiveRecord::Encryption.key_provider }
let(:current_key_provider) { ActiveRecord::Encryption.key_provider }
let(:unknown_key_provider) { ActiveRecord::Encryption::DerivedSecretKeyProvider.new(SecureRandom.base64(32)) }
before do
# Create a record with the current encryption key
create(:dependency_proxy_group_setting)
# Create a record with a different encryption key
ActiveRecord::Encryption.with_encryption_context(key_provider: unknown_key_provider) do
create(:dependency_proxy_group_setting)
end
end
it 'detects decryptable secrets' do
expect(logger).to receive(:info).with(/Encryption keys usage for DependencyProxy::GroupSetting:/)
expect(logger).to receive(:info).with(/- `#{key_provider1.encryption_key.id}` => 2/)
expect(logger).to receive(:info).with("Encryption keys usage for DependencyProxy::GroupSetting:")
expect(logger).to receive(:info).with("- `#{current_key_provider.encryption_key.id}` => 2")
expect(logger).to receive(:info).with("- `#{unknown_key_provider.encryption_key.id}` (UNKNOWN KEY!) => 2")
doctor_encryption_secrets
end

View File

@ -44,14 +44,9 @@ RSpec.describe Gitlab::SlashCommands::IssueMove, :service, feature_category: :te
it 'returns the error message' do
message = "issue move #{issue.iid} #{project.full_path}"
if issue.project.work_item_move_and_clone_flag_enabled?
process_message(message)
# move does not happen, as moving issue to same project results in same issue, but we do not show an error
expect(issue.reload.moved_to).to be_nil
else
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching(same_project_error_message))
end
process_message(message)
# move does not happen, as moving issue to same project results in same issue, but we do not show an error
expect(issue.reload.moved_to).to be_nil
end
end
@ -119,30 +114,11 @@ RSpec.describe Gitlab::SlashCommands::IssueMove, :service, feature_category: :te
other_project.team.add_guest(user)
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching(permissions_error_message))
text: a_string_matching("Unable to move. You have insufficient permissions."))
end
end
end
context 'with work_item_move_and_clone disabled' do
before do
stub_feature_flags(work_item_move_and_clone: false)
end
it_behaves_like 'move issue slash command' do
let(:same_project_error_message) { "Cannot move issue to project it originates from." }
let(:permissions_error_message) { "Cannot move issue due to insufficient permissions." }
end
end
context 'with work_item_move_and_clone enabled' do
before do
stub_feature_flags(work_item_move_and_clone: true)
end
it_behaves_like 'move issue slash command' do
let(:permissions_error_message) { "Unable to move. You have insufficient permissions." }
end
end
it_behaves_like 'move issue slash command'
end
end

View File

@ -2,14 +2,18 @@
require 'spec_helper'
RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove, feature_category: :team_planning do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, developers: user) }
let_it_be(:other_project) { create(:project, developers: user) }
let_it_be(:old_issue, reload: true) { create(:issue, project: project) }
let(:new_issue) { Issues::MoveService.new(container: project, current_user: user).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
let(:new_issue) do
::WorkItems::DataSync::MoveService.new(
work_item: old_issue, current_user: user, target_namespace: other_project.project_namespace
).execute[:work_item]
end
subject { described_class.new(new_issue).present(old_issue) }

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueMarkAdminBotRunnersAsHosted, migration: :gitlab_ci, feature_category: :hosted_runners do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_ci,
table_name: :ci_runners,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -575,12 +575,33 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
describe '#linked_items_keyset_order' do
describe '.linked_items_keyset_order' do
subject { described_class.linked_items_keyset_order }
it { is_expected.to eq('"issue_links"."id" DESC') }
end
describe '.linked_items_for' do
let_it_be(:items) { create_list(:work_item, 3, project: reusable_project) }
let_it_be(:linked_items) { create_list(:work_item, 3, project: reusable_project) }
let(:work_item_ids) { items.pluck(:id) }
subject(:linked) { described_class.linked_items_for(work_item_ids) }
before do
items.each_with_index do |item, i|
create(:work_item_link, source: item, target: linked_items[i])
end
end
it 'returns the linked items' do
expect(linked.map(&:issue_link_target_id)).to match_array(work_item_ids)
expect(linked.map(&:issue_link_source_id)).to match_array(linked_items.map(&:id))
expect(linked.map(&:issue_link_type).uniq).to contain_exactly('relates_to')
end
end
context 'with hierarchy' do
let_it_be(:type1) { create(:work_item_type, :non_default) }
let_it_be(:type2) { create(:work_item_type, :non_default) }

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