Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-05-10 00:10:35 +00:00
parent 94356b7dcd
commit a9f80cbfe2
43 changed files with 143 additions and 102 deletions

View File

@ -3292,7 +3292,6 @@ Gitlab/BoundedContexts:
- 'ee/app/services/llm/resolve_vulnerability_service.rb'
- 'ee/app/services/llm/review_merge_request_service.rb'
- 'ee/app/services/llm/summarize_new_merge_request_service.rb'
- 'ee/app/services/member_roles/base_service.rb'
- 'ee/app/services/member_roles/create_service.rb'
- 'ee/app/services/member_roles/delete_service.rb'
- 'ee/app/services/member_roles/update_service.rb'

View File

@ -2,17 +2,6 @@
# Cop supports --autocorrect.
Layout/LineBreakAfterFinalMixin:
Exclude:
- 'app/graphql/resolvers/integrations/exclusions_resolver.rb'
- 'app/graphql/resolvers/projects/deploy_key_resolver.rb'
- 'app/policies/project_member_policy.rb'
- 'app/serializers/cluster_serializer.rb'
- 'app/serializers/container_repositories_serializer.rb'
- 'app/serializers/feature_flag_serializer.rb'
- 'app/serializers/pipeline_serializer.rb'
- 'app/services/authorized_project_update/project_recalculate_service.rb'
- 'app/services/batched_git_ref_updates/cleanup_scheduler_service.rb'
- 'app/services/jira_connect_subscriptions/create_service.rb'
- 'app/services/projects/transfer_service.rb'
- 'app/workers/ci/delete_unit_tests_worker.rb'
- 'app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb'
- 'app/workers/ci/schedule_delete_objects_cron_worker.rb'
@ -37,12 +26,6 @@ Layout/LineBreakAfterFinalMixin:
- 'app/workers/stuck_export_jobs_worker.rb'
- 'app/workers/user_status_cleanup/batch_worker.rb'
- 'app/workers/users/create_statistics_worker.rb'
- 'ee/app/graphql/subscriptions/ai_completion_response.rb'
- 'ee/app/services/ee/ip_restrictions/update_service.rb'
- 'ee/app/services/incident_management/oncall_rotations/edit_service.rb'
- 'ee/app/services/incident_management/oncall_rotations/remove_participant_service.rb'
- 'ee/app/services/llm/generate_description_service.rb'
- 'ee/app/services/product_analytics/initialize_stack_service.rb'
- 'ee/app/workers/active_user_count_threshold_worker.rb'
- 'ee/app/workers/analytics/value_stream_dashboard/count_worker.rb'
- 'ee/app/workers/arkose/blocked_users_report_worker.rb'
@ -56,10 +39,3 @@ Layout/LineBreakAfterFinalMixin:
- 'ee/app/workers/sync_seat_link_worker.rb'
- 'ee/app/workers/vulnerabilities/historical_statistics/deletion_worker.rb'
- 'ee/app/workers/vulnerabilities/statistics/schedule_worker.rb'
- 'lib/banzai/filter/references/design_reference_filter.rb'
- 'lib/feature.rb'
- 'lib/gitlab/auth/current_user_mode.rb'
- 'lib/gitlab/auth/otp/strategies/forti_token_cloud.rb'
- 'lib/gitlab/ci/config/interpolation/config.rb'
- 'lib/gitlab/cluster/rack_timeout_observer.rb'
- 'lib/gitlab/etag_caching/router/graphql.rb'

View File

@ -2247,7 +2247,6 @@ Layout/LineLength:
- 'lib/security/ci_configuration/base_build_action.rb'
- 'lib/security/ci_configuration/sast_build_action.rb'
- 'lib/sidebars/groups/menus/packages_registries_menu.rb'
- 'lib/sidebars/menu_item.rb'
- 'lib/sidebars/projects/menus/repository_menu.rb'
- 'lib/system_check/app/orphaned_group_members_check.rb'
- 'lib/system_check/app/redis_version_check.rb'

View File

@ -182,7 +182,6 @@ RSpec/BeEq:
- 'ee/spec/lib/search/elastic/task_status_spec.rb'
- 'ee/spec/lib/security/scan_result_policies/policy_violation_details_spec.rb'
- 'ee/spec/lib/security/security_orchestration_policies/policy_scope_checker_spec.rb'
- 'ee/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb'
- 'ee/spec/lib/telesign/transaction_callback_payload_spec.rb'
- 'ee/spec/lib/telesign/transaction_callback_spec.rb'
- 'ee/spec/lib/users/identity_verification/authorize_ci_spec.rb'

View File

@ -432,7 +432,6 @@ RSpec/NamedSubject:
- 'ee/spec/lib/repositories/project_push_rules_changes_auditor_spec.rb'
- 'ee/spec/lib/sidebars/groups/menus/epics_menu_spec.rb'
- 'ee/spec/lib/sidebars/groups/menus/work_item_epics_menu_spec.rb'
- 'ee/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb'
- 'ee/spec/lib/sidebars/user_settings/menus/profile_billing_menu_spec.rb'
- 'ee/spec/lib/system_check/geo/authorized_keys_check_spec.rb'
- 'ee/spec/lib/system_check/geo/authorized_keys_flag_check_spec.rb'

View File

@ -75,6 +75,7 @@ export default {
return {
isMouseIn: false,
canClickPinButton: false,
localPillCount: this.item.pill_count,
};
},
computed: {
@ -82,6 +83,11 @@ export default {
if (this.item.pill_count_field) {
return this.asyncCount[this.item.pill_count_field];
}
if (this.item.pill_count_dynamic) {
return this.localPillCount;
}
return this.item.pill_count;
},
hasPill() {
@ -213,11 +219,7 @@ export default {
},
updatePillValue({ value, itemId }) {
if (this.item.id === itemId) {
// https://gitlab.com/gitlab-org/gitlab/-/issues/428246
// fixing this linting issue is causing the pills not to async update for learn gitlab nav item
//
// eslint-disable-next-line vue/no-mutating-props
this.item.pill_count = value;
this.localPillCount = value;
}
},
},

View File

@ -4,6 +4,7 @@ module Resolvers
module Integrations
class ExclusionsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::Integrations::ExclusionType.connection_type, null: true
argument :integration_name, Types::Integrations::IntegrationTypeEnum,

View File

@ -4,6 +4,7 @@ module Resolvers
module Projects
class DeployKeyResolver < BaseResolver
include LooksAhead
type Types::AccessLevels::DeployKeyType, null: true
def resolve_with_lookahead(**args)

View File

@ -1631,7 +1631,7 @@ class MergeRequest < ApplicationRecord
end
def visible_closing_issues_for(current_user = self.author)
strong_memoize(:visible_closing_issues_for) do
strong_memoize_with(:visible_closing_issues_for, current_user&.id) do
visible_issues = if self.target_project.has_external_issue_tracker?
closes_issues(current_user)
else
@ -1644,7 +1644,7 @@ class MergeRequest < ApplicationRecord
records: visible_issues.select { |issue| issue.is_a?(Issue) },
associations: :project
).call
# Exclude isues that have been cached but their project setting has been disabled, or they belong to a group
# Exclude issues that have been cached but their project setting has been disabled, or they belong to a group
visible_issues.select do |issue|
!issue.is_a?(Issue) || issue.autoclose_by_merged_closing_merge_request?
end

View File

@ -2,6 +2,7 @@
class ProjectMemberPolicy < BasePolicy
include MemberPolicyHelpers
delegate { @subject.project }
condition(:target_is_holder_of_the_personal_namespace, scope: :subject) do

View File

@ -2,6 +2,7 @@
class ClusterSerializer < BaseSerializer
include WithPagination
entity ClusterEntity
def represent_list(resource)

View File

@ -2,6 +2,7 @@
class ContainerRepositoriesSerializer < BaseSerializer
include WithPagination
entity ContainerRepositoryEntity
def represent_read_only(resource)

View File

@ -2,6 +2,7 @@
class FeatureFlagSerializer < BaseSerializer
include WithPagination
entity FeatureFlagEntity
def represent(resource, opts = {})

View File

@ -2,6 +2,7 @@
class PipelineSerializer < BaseSerializer
include WithPagination
entity PipelineDetailsEntity
# rubocop: disable CodeReuse/ActiveRecord

View File

@ -4,6 +4,7 @@ module AuthorizedProjectUpdate
class ProjectRecalculateService
# Service for refreshing all the authorizations to a particular project.
include Gitlab::Utils::StrongMemoize
BATCH_SIZE = 1000
def initialize(project)

View File

@ -3,6 +3,7 @@
module BatchedGitRefUpdates
class CleanupSchedulerService
include Gitlab::ExclusiveLeaseHelpers
MAX_PROJECTS = 10_000
BATCH_SIZE = 100
LOCK_TIMEOUT = 10.minutes

View File

@ -3,6 +3,7 @@
module JiraConnectSubscriptions
class CreateService < ::JiraConnectSubscriptions::BaseService
include Gitlab::Utils::StrongMemoize
MERGE_REQUEST_SYNC_BATCH_SIZE = 20
MERGE_REQUEST_SYNC_BATCH_DELAY = 1.minute.freeze
BATCH_SIZE = 1_000

View File

@ -11,6 +11,7 @@
module Projects
class TransferService < BaseService
include Gitlab::ShellAdapter
TransferError = Class.new(StandardError)
def log_project_transfer_success(project, new_namespace)

View File

@ -14,9 +14,9 @@
#performance-bar-root
%template{ :shadowrootmode => "open" }
= render 'peek/bar'
= universal_stylesheet_link_tag 'application'
= universal_stylesheet_link_tag 'application_utilities'
= universal_stylesheet_link_tag 'tailwind'
= universal_stylesheet_link_tag 'performance_bar'
= universal_stylesheet_link_tag 'application', crossorigin: 'anonymous'
= universal_stylesheet_link_tag 'application_utilities', crossorigin: 'anonymous'
= universal_stylesheet_link_tag 'tailwind', crossorigin: 'anonymous'
= universal_stylesheet_link_tag 'performance_bar', crossorigin: 'anonymous'
= webpack_bundle_tag 'performance_bar'
#graphiql-container Loading...

View File

@ -1527,7 +1527,10 @@ Instead of:
## limitations
Do not use **limitations**. Use **known issues** instead.
Do not use **Limitations** as a topic title. For more information,
see [reference topic titles](../topic_types/reference.md#reference-topic-titles).
If you must, you can use the title **Known issues**.
## log in, log on

View File

@ -30,9 +30,11 @@ Reference topic titles are usually nouns.
Avoid these topic titles:
- `Important notes`. Instead, incorporate this information
closer to where it belongs. For example, this information might be a prerequisite
closer to where it belongs. This information might be a prerequisite
for a task, or information about a concept.
- `Limitations`. Instead, move the content near other similar information.
Content listed as limitations can often be considered prerequisite
information about how a feature works.
If you must, you can use the title `Known issues`.
## Example

View File

@ -124,27 +124,6 @@ For GitLab Self-Managed instances, you can choose to run most of the GitLab secu
GitLab Self-Managed instances can also run the security scanners on a GitLab Runner [running inside OpenShift](../../install/openshift_and_gitlab/_index.md).
## Security report validation
GitLab 15.0 enforces validation of the security report artifacts before ingesting the vulnerabilities.
This prevents ingestion of broken vulnerability data into the database. GitLab validates the
artifacts against the [report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist),
according to the schema version declared in the report.
The pipeline's **Security** tab lists any report artifacts that failed validation, and the
validation error message.
Validation depends on the schema version declared in the security report artifact:
- If your security report specifies a supported schema version, GitLab uses this version to validate.
- If your security report uses a deprecated version, GitLab attempts validation against that version and adds a deprecation warning to the validation result.
- If your security report uses a supported MAJOR-MINOR version of the report schema but the PATCH version doesn't match any vendored versions, GitLab attempts to validate it against latest vendored PATCH version of the schema.
- Example: security report uses version 14.1.1 but the latest vendored version is 14.1.0. GitLab would validate against schema version 14.1.0.
- If your security report uses a version that is not supported, GitLab attempts to validate it against the earliest schema version available in your installation but doesn't ingest the report.
- If your security report does not specify a schema version, GitLab attempts to validate it against the earliest schema version available in GitLab. Because the `version` property is required, validation always fails in this case, but other validation errors may also be present.
You can always find supported and deprecated schema versions in the [source code](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/parsers/security/validators/schema_validator.rb).
## Security scanning configuration tips
Each GitLab security scanning tool has a default

View File

@ -0,0 +1,38 @@
---
stage: Application Security Testing
group: Static Analysis
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
title: Security report validation
---
{{< details >}}
- Tier: Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
Security reports are validated before their content is added to the database. This prevents
ingestion of broken vulnerability data into the database. Reports that fail validation are listed in
the pipeline's **Security** tab with the validation error message.
Validation is done against the
[report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist),
according to the schema version declared in the report:
- If the security report specifies a supported schema version, GitLab uses this version to validate.
- If the security report uses a deprecated version, GitLab attempts validation against that version
and adds a deprecation warning to the validation result.
- If the security report uses a supported MAJOR-MINOR version of the report schema but the PATCH
version doesn't match any vendored versions, GitLab attempts to validate it against latest
vendored PATCH version of the schema.
- Example: security report uses version 14.1.1 but the latest vendored version is 14.1.0. GitLab
would validate against schema version 14.1.0.
- If the security report uses a version that is not supported, GitLab attempts to validate it
against the earliest schema version available in your installation but doesn't ingest the report.
- If the security report does not specify a schema version, GitLab attempts to validate it against
the earliest schema version available in GitLab. Because the `version` property is required,
validation always fails in this case, but other validation errors may also be present.
For details of the supported and deprecated schema versions, view the
[schema validator source code](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/parsers/security/validators/schema_validator.rb).

View File

@ -30,6 +30,8 @@ features:
1. Upgrade to GitLab 18.0 or later.
1. Turn on the IDE features for your group or instance.
It may take up to 10 minutes for the change to take effect.
### For a group
On GitLab.com, you can turn GitLab Duo Core on or off for a top-level group, but not for a subgroup or project.
@ -46,6 +48,8 @@ To turn GitLab Duo Core on or off for a top-level group on GitLab.com:
1. Below **GitLab Duo Core**, select or clear the **Turn on IDE features** checkbox.
1. Select **Save changes**.
It may take up to 10 minutes for the change to take effect.
### For an instance
On GitLab Self-Managed, you can turn GitLab Duo Core on or off for an instance.
@ -339,7 +343,7 @@ To turn on GitLab Duo experiment and beta features for a top-level group:
1. On the left sidebar, select **Search or go to** and find your group.
1. Select **Settings > GitLab Duo**.
1. Under **GitLab Duo** section, select **Change configuration**.
1. Select **Turn on experiment and beta GitLab Duo features**.
1. Select **Turn on experiment and beta GitLab Duo features**.
1. Select **Save changes**.
{{< /tab >}}

View File

@ -6,6 +6,7 @@ module Banzai
class DesignReferenceFilter < AbstractReferenceFilter
class Identifier
include Comparable
attr_reader :issue_iid, :filename
def initialize(issue_iid:, filename:)

View File

@ -7,6 +7,7 @@ module Feature
class FlipperRecord < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord -- This class perfectly replaces
# Flipper::Adapters::ActiveRecord::Model, which inherits ActiveRecord::Base
include DatabaseReflection
self.abstract_class = true
# Bypass the load balancer by restoring the default behavior of `connection`

View File

@ -9,6 +9,7 @@ module Gitlab
# e.g. on web access require re-authentication
class CurrentUserMode
include Gitlab::Utils::StrongMemoize
NotRequestedError = Class.new(StandardError)
NonSidekiqEnvironmentError = Class.new(StandardError)

View File

@ -6,6 +6,7 @@ module Gitlab
module Strategies
class FortiTokenCloud < Base
include Gitlab::Utils::StrongMemoize
BASE_API_URL = 'https://ftc.fortinet.com:9696/api/v1'
def validate(otp_code)

View File

@ -9,6 +9,7 @@ module Gitlab
#
class Config
include Gitlab::Utils::StrongMemoize
##
# Loading the YAML below would result in a hash having 12 nodes instead of 9,
# because hash values are being counted before we recursively traverse them.

View File

@ -4,6 +4,7 @@ module Gitlab
module Cluster
class RackTimeoutObserver
include ActionView::Helpers::SanitizeHelper
TRANSITION_STATES = %i[ready active].freeze
def initialize

View File

@ -5,6 +5,7 @@ module Gitlab
module Router
class Graphql
extend EtagCaching::Router::Helpers
GRAPHQL_ETAG_RESOURCE_HEADER = 'X-GITLAB-GRAPHQL-RESOURCE-ETAG'
ROUTES = [

View File

@ -4,12 +4,18 @@ module Sidebars
class MenuItem
include ::Sidebars::Concerns::LinkWithHtmlOptions
attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon, :sprite_icon_html_options, :has_pill, :pill_count, :pill_count_field, :super_sidebar_parent, :avatar, :entity_id
attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon,
:sprite_icon_html_options, :has_pill, :pill_count, :pill_count_field, :pill_count_dynamic, :super_sidebar_parent,
:avatar, :entity_id
attr_accessor :render
alias_method :has_pill?, :has_pill
# rubocop: disable Metrics/ParameterLists
def initialize(title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil, sprite_icon_html_options: {}, has_pill: false, pill_count: nil, pill_count_field: nil, super_sidebar_parent: nil, avatar: nil, entity_id: nil)
def initialize(
title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil,
sprite_icon_html_options: {}, has_pill: false, pill_count_dynamic: false, pill_count: nil,
pill_count_field: nil, super_sidebar_parent: nil, avatar: nil, entity_id: nil
)
@title = title
@link = link
@active_routes = active_routes
@ -22,6 +28,7 @@ module Sidebars
@has_pill = has_pill
@pill_count = pill_count
@pill_count_field = pill_count_field
@pill_count_dynamic = pill_count_dynamic
@super_sidebar_parent = super_sidebar_parent
end
# rubocop: enable Metrics/ParameterLists
@ -41,15 +48,23 @@ module Sidebars
entity_id: entity_id,
link: link,
active_routes: active_routes,
pill_count: has_pill ? pill_count : nil,
pill_count_field: has_pill ? pill_count_field : nil,
link_classes: container_html_options[:class]
# Check whether support is needed for the following properties,
# in order to get feature parity with the HAML renderer
# https://gitlab.com/gitlab-org/gitlab/-/issues/391864
#
# container_html_options
}.compact
}.merge(pill_attributes).compact
end
def pill_attributes
return {} unless has_pill?
{
pill_count: pill_count,
pill_count_field: pill_count_field,
pill_count_dynamic: pill_count_dynamic
}
end
end
end

View File

@ -37,16 +37,13 @@ describe('Batch comments draft note component', () => {
},
};
const createComponent = (propsData = { draft }, glFeatures = {}) => {
const createComponent = (propsData = { draft }) => {
wrapper = shallowMount(DraftNote, {
store,
propsData,
stubs: {
NoteableNote: NoteableNoteStub,
},
provide: {
glFeatures,
},
});
jest.spyOn(store, 'dispatch').mockImplementation();

View File

@ -21,7 +21,6 @@ import RunnerListEmptyState from '~/ci/runner/components/runner_list_empty_state
describe('RunnerListEmptyState', () => {
let wrapper;
let glFeatures;
const findEmptySearchResult = () => wrapper.findComponent(EmptyResult);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
@ -48,14 +47,9 @@ describe('RunnerListEmptyState', () => {
GlEmptyState,
GlSprintf,
},
provide: { glFeatures },
});
};
beforeEach(() => {
glFeatures = null;
});
describe('when search is not filtered', () => {
describe.each`
newRunnerPath | registrationToken | expectedMessages

View File

@ -75,18 +75,11 @@ describe('diffs/components/app', () => {
const codeQualityAndSastQueryHandlerSuccess = jest.fn().mockResolvedValue({});
const createComponent = ({ props = {}, provisions = {} } = {}) => {
const createComponent = ({ props = {} } = {}) => {
fakeApollo = createMockApollo([
[getMRCodequalityAndSecurityReports, codeQualityAndSastQueryHandlerSuccess],
]);
const provide = {
...provisions,
glFeatures: {
...provisions.glFeatures,
},
};
wrapper = shallowMount(App, {
apolloProvider: fakeApollo,
propsData: {
@ -99,7 +92,6 @@ describe('diffs/components/app', () => {
changesEmptyStateIllustration: '',
...props,
},
provide,
store: createDiffsStore(),
pinia,
});

View File

@ -33,16 +33,13 @@ describe('DiffContent', () => {
diffFile: getDiffFileMock(),
};
const createComponent = ({ props, provide } = {}) => {
const glFeatures = provide ? { ...provide.glFeatures } : {};
const createComponent = ({ props } = {}) => {
wrapper = shallowMount(DiffContentComponent, {
propsData: {
...defaultProps,
...props,
},
pinia,
provide: { glFeatures },
});
};

View File

@ -38,15 +38,12 @@ describe('Environments detail header component', () => {
['Destroy', findDestroyButton],
];
const createWrapper = ({ props, glFeatures = {} }) => {
const createWrapper = ({ props }) => {
wrapper = shallowMountExtended(EnvironmentsDetailHeader, {
stubs: {
GlSprintf,
TimeAgo,
},
provide: {
glFeatures,
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},

View File

@ -17,7 +17,7 @@ const handleEnterSpy = jest.fn();
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createComponent = ({ props = {}, glFeatures = {} } = {}) => {
const createComponent = ({ props = {} } = {}) => {
wrapper = mountExtended(MembersTokenSelect, {
propsData: {
ariaLabelledby: label,
@ -25,7 +25,6 @@ const createComponent = ({ props = {}, glFeatures = {} } = {}) => {
placeholder,
...props,
},
provide: { glFeatures },
});
};

View File

@ -75,7 +75,9 @@ describe('NavItem component', () => {
};
it('updates the pill count', async () => {
createWrapper({ item: { id: itemIdForUpdate, pill_count: initialPillValue } });
createWrapper({
item: { id: itemIdForUpdate, pill_count: initialPillValue, pill_count_dynamic: true },
});
await triggerPillValueUpdate();
@ -83,7 +85,9 @@ describe('NavItem component', () => {
});
it('does not update the pill count for non matching item id', async () => {
createWrapper({ item: { id: '_non_matching_id_', pill_count: initialPillValue } });
createWrapper({
item: { id: '_non_matching_id_', pill_count: initialPillValue, pill_count_dynamic: true },
});
await triggerPillValueUpdate();

View File

@ -31,7 +31,6 @@ describe('Blob Simple Viewer component', () => {
isBlameLinkHidden = false,
isRawContent = false,
propsData = {},
glFeatures = {},
} = {}) {
fakeApollo = createMockApollo([[blameDataQuery, blameDataQueryHandlerSuccess]]);
@ -39,7 +38,6 @@ describe('Blob Simple Viewer component', () => {
apolloProvider: fakeApollo,
provide: {
blobHash,
glFeatures,
},
propsData: {
content,

View File

@ -71,7 +71,8 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
title: "Not active",
link: "foo3",
is_active: false,
pill_count: 10
pill_count: 10,
pill_count_dynamic: false
}
]
})

View File

@ -1348,6 +1348,7 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
subject.cache_merge_request_closes_issues!
expect(subject.visible_closing_issues_for(guest)).to match_array([issue_1, issue_2])
expect(subject.visible_closing_issues_for(nil)).to be_empty
end
it 'shows only allowed issues to developer' do
@ -1356,6 +1357,30 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
subject.cache_merge_request_closes_issues!
expect(subject.visible_closing_issues_for(developer)).to match_array([issue_1, confidential_issue, issue_2])
expect(subject.visible_closing_issues_for(nil)).to be_empty
end
it 'isolates cache per user so each role sees its own issues' do
project.add_guest(guest)
project.add_developer(developer)
subject.cache_merge_request_closes_issues!
# Call in one order
developer_view_first = subject.visible_closing_issues_for(developer)
guest_view_first = subject.visible_closing_issues_for(guest)
expect(developer_view_first).to match_array([issue_1, issue_2, confidential_issue])
expect(guest_view_first).to match_array([issue_1, issue_2])
# Reset memoization
subject.clear_memoization(:visible_closing_issues_for) # Specific to strong_memoize
# Call in reverse order on the same instance after clearing memoization
guest_view_second = subject.visible_closing_issues_for(guest)
developer_view_second = subject.visible_closing_issues_for(developer)
expect(guest_view_second).to match_array([issue_1, issue_2])
expect(developer_view_second).to match_array([issue_1, issue_2, confidential_issue])
end
context 'when external issue tracker is enabled' do

View File

@ -7,6 +7,10 @@ module Features
def invite_member(names, role: 'Guest', expires_at: nil, use_exact_text_match: true)
click_on 'Invite members'
invite_with_opened_modal(names, role: role, expires_at: expires_at, use_exact_text_match: use_exact_text_match)
end
def invite_with_opened_modal(names, role: 'Guest', expires_at: nil, use_exact_text_match: true)
page.within invite_modal_selector do
select_members(names)
choose_options(role, expires_at, use_exact_text_match)