Only show membership groups in group invites
What does this MR do and why?
Fixes #224661 (closed)
Only show the groups that the current user is a member of in the project members invite page.
Therefore, only fetch the groups where the current user has minimum access level >= Guest by using the min_access_level
parameter in the /api/:version/groups.json` API.
Database
Raw SQL
EXPLAIN WITH RECURSIVE "base_and_descendants" AS (
(
SELECT
"namespaces"."id",
"namespaces"."name",
"namespaces"."path",
"namespaces"."owner_id",
"namespaces"."created_at",
"namespaces"."updated_at",
"namespaces"."type",
"namespaces"."description",
"namespaces"."avatar",
"namespaces"."membership_lock",
"namespaces"."share_with_group_lock",
"namespaces"."visibility_level",
"namespaces"."request_access_enabled",
"namespaces"."ldap_sync_status",
"namespaces"."ldap_sync_error",
"namespaces"."ldap_sync_last_update_at",
"namespaces"."ldap_sync_last_successful_update_at",
"namespaces"."ldap_sync_last_sync_at",
"namespaces"."description_html",
"namespaces"."lfs_enabled",
"namespaces"."parent_id",
"namespaces"."shared_runners_minutes_limit",
"namespaces"."repository_size_limit",
"namespaces"."require_two_factor_authentication",
"namespaces"."two_factor_grace_period",
"namespaces"."cached_markdown_version",
"namespaces"."project_creation_level",
"namespaces"."runners_token",
"namespaces"."file_template_project_id",
"namespaces"."saml_discovery_token",
"namespaces"."runners_token_encrypted",
"namespaces"."custom_project_templates_group_id",
"namespaces"."auto_devops_enabled",
"namespaces"."extra_shared_runners_minutes_limit",
"namespaces"."last_ci_minutes_notification_at",
"namespaces"."last_ci_minutes_usage_notification_level",
"namespaces"."subgroup_creation_level",
"namespaces"."emails_disabled",
"namespaces"."max_pages_size",
"namespaces"."max_artifacts_size",
"namespaces"."mentions_disabled",
"namespaces"."default_branch_protection",
"namespaces"."unlock_membership_to_ldap",
"namespaces"."max_personal_access_token_lifetime",
"namespaces"."push_rule_id",
"namespaces"."shared_runners_enabled",
"namespaces"."allow_descendants_override_disabled_shared_runners",
"namespaces"."traversal_ids"
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND "namespaces"."id" IN (
SELECT
"namespaces"."id"
FROM
"namespaces"
INNER JOIN "members" ON "namespaces"."id" = "members"."source_id"
WHERE
"members"."type" = 'GroupMember'
AND "members"."source_type" = 'Namespace'
AND "namespaces"."type" = 'Group'
AND "members"."user_id" = 33100
AND "members"."requested_at" IS NULL
AND (access_level >= 10)))
UNION (
SELECT
"namespaces"."id",
"namespaces"."name",
"namespaces"."path",
"namespaces"."owner_id",
"namespaces"."created_at",
"namespaces"."updated_at",
"namespaces"."type",
"namespaces"."description",
"namespaces"."avatar",
"namespaces"."membership_lock",
"namespaces"."share_with_group_lock",
"namespaces"."visibility_level",
"namespaces"."request_access_enabled",
"namespaces"."ldap_sync_status",
"namespaces"."ldap_sync_error",
"namespaces"."ldap_sync_last_update_at",
"namespaces"."ldap_sync_last_successful_update_at",
"namespaces"."ldap_sync_last_sync_at",
"namespaces"."description_html",
"namespaces"."lfs_enabled",
"namespaces"."parent_id",
"namespaces"."shared_runners_minutes_limit",
"namespaces"."repository_size_limit",
"namespaces"."require_two_factor_authentication",
"namespaces"."two_factor_grace_period",
"namespaces"."cached_markdown_version",
"namespaces"."project_creation_level",
"namespaces"."runners_token",
"namespaces"."file_template_project_id",
"namespaces"."saml_discovery_token",
"namespaces"."runners_token_encrypted",
"namespaces"."custom_project_templates_group_id",
"namespaces"."auto_devops_enabled",
"namespaces"."extra_shared_runners_minutes_limit",
"namespaces"."last_ci_minutes_notification_at",
"namespaces"."last_ci_minutes_usage_notification_level",
"namespaces"."subgroup_creation_level",
"namespaces"."emails_disabled",
"namespaces"."max_pages_size",
"namespaces"."max_artifacts_size",
"namespaces"."mentions_disabled",
"namespaces"."default_branch_protection",
"namespaces"."unlock_membership_to_ldap",
"namespaces"."max_personal_access_token_lifetime",
"namespaces"."push_rule_id",
"namespaces"."shared_runners_enabled",
"namespaces"."allow_descendants_override_disabled_shared_runners",
"namespaces"."traversal_ids"
FROM
"namespaces",
"base_and_descendants"
WHERE
"namespaces"."type" = 'Group'
AND "namespaces"."parent_id" = "base_and_descendants"."id"))
SELECT
"namespaces"."id",
"namespaces"."name",
"namespaces"."path",
"namespaces"."owner_id",
"namespaces"."created_at",
"namespaces"."updated_at",
"namespaces"."type",
"namespaces"."description",
"namespaces"."avatar",
"namespaces"."membership_lock",
"namespaces"."share_with_group_lock",
"namespaces"."visibility_level",
"namespaces"."request_access_enabled",
"namespaces"."ldap_sync_status",
"namespaces"."ldap_sync_error",
"namespaces"."ldap_sync_last_update_at",
"namespaces"."ldap_sync_last_successful_update_at",
"namespaces"."ldap_sync_last_sync_at",
"namespaces"."description_html",
"namespaces"."lfs_enabled",
"namespaces"."parent_id",
"namespaces"."shared_runners_minutes_limit",
"namespaces"."repository_size_limit",
"namespaces"."require_two_factor_authentication",
"namespaces"."two_factor_grace_period",
"namespaces"."cached_markdown_version",
"namespaces"."project_creation_level",
"namespaces"."runners_token",
"namespaces"."file_template_project_id",
"namespaces"."saml_discovery_token",
"namespaces"."runners_token_encrypted",
"namespaces"."custom_project_templates_group_id",
"namespaces"."auto_devops_enabled",
"namespaces"."extra_shared_runners_minutes_limit",
"namespaces"."last_ci_minutes_notification_at",
"namespaces"."last_ci_minutes_usage_notification_level",
"namespaces"."subgroup_creation_level",
"namespaces"."emails_disabled",
"namespaces"."max_pages_size",
"namespaces"."max_artifacts_size",
"namespaces"."mentions_disabled",
"namespaces"."default_branch_protection",
"namespaces"."unlock_membership_to_ldap",
"namespaces"."max_personal_access_token_lifetime",
"namespaces"."push_rule_id",
"namespaces"."shared_runners_enabled",
"namespaces"."allow_descendants_override_disabled_shared_runners",
"namespaces"."traversal_ids"
FROM
"base_and_descendants" AS "namespaces"
Query Plan
CTE Scan on base_and_descendants namespaces (cost=1552.00..1556.02 rows=201 width=2945) (actual time=242.117..2923.712 rows=1390 loops=1)
Buffers: shared hit=5150 read=2098 dirtied=60
I/O Timings: read=2826.460 write=0.000
CTE base_and_descendants
-> Recursive Union (cost=12.26..1552.00 rows=201 width=358) (actual time=242.112..2918.975 rows=1390 loops=1)
Buffers: shared hit=5150 read=2098 dirtied=60
I/O Timings: read=2826.460 write=0.000
-> Nested Loop (cost=12.26..12.51 rows=1 width=358) (actual time=242.096..242.253 rows=18 loops=1)
Buffers: shared hit=100 read=66 dirtied=1
I/O Timings: read=240.337 write=0.000
-> HashAggregate (cost=11.83..11.84 rows=1 width=8) (actual time=242.059..242.070 rows=18 loops=1)
Group Key: namespaces_2.id
Buffers: shared hit=28 read=66 dirtied=1
I/O Timings: read=240.337 write=0.000
-> Nested Loop (cost=1.00..11.83 rows=1 width=8) (actual time=24.653..241.909 rows=18 loops=1)
Buffers: shared hit=28 read=66 dirtied=1
I/O Timings: read=240.337 write=0.000
-> Index Scan using index_members_on_user_id_source_id_source_type on public.members (cost=0.56..8.37 rows=1 width=4) (actual time=19.635..81.011 rows=18 loops=1)
Index Cond: ((members.user_id = 33100) AND ((members.source_type)::text = 'Namespace'::text))
Filter: ((members.requested_at IS NULL) AND (members.access_level >= 10) AND ((members.type)::text = 'GroupMember'::text))
Rows Removed by Filter: 0
Buffers: shared hit=1 read=21 dirtied=1
I/O Timings: read=80.712 write=0.000
-> Index Scan using namespaces_pkey on public.namespaces namespaces_2 (cost=0.43..3.46 rows=1 width=4) (actual time=8.931..8.931 rows=1 loops=18)
Index Cond: (namespaces_2.id = members.source_id)
Filter: ((namespaces_2.type)::text = 'Group'::text)
Rows Removed by Filter: 0
Buffers: shared hit=27 read=45
I/O Timings: read=159.625 write=0.000
-> Index Scan using namespaces_pkey on public.namespaces namespaces_1 (cost=0.43..0.67 rows=1 width=358) (actual time=0.009..0.009 rows=1 loops=18)
Index Cond: (namespaces_1.id = namespaces_2.id)
Filter: ((namespaces_1.type)::text = 'Group'::text)
Rows Removed by Filter: 0
Buffers: shared hit=72
I/O Timings: read=0.000 write=0.000
-> Nested Loop (cost=0.56..153.55 rows=20 width=358) (actual time=4.559..295.429 rows=154 loops=9)
Buffers: shared hit=5050 read=2032 dirtied=59
I/O Timings: read=2586.123 write=0.000
-> WorkTable Scan on base_and_descendants (cost=0.00..0.20 rows=10 width=4) (actual time=0.001..0.160 rows=154 loops=9)
I/O Timings: read=0.000 write=0.000
-> Index Scan using index_namespaces_on_parent_id_and_id on public.namespaces namespaces_3 (cost=0.56..15.31 rows=2 width=358) (actual time=0.915..1.908 rows=1 loops=1390)
Index Cond: (namespaces_3.parent_id = base_and_descendants.id)
Filter: ((namespaces_3.type)::text = 'Group'::text)
Rows Removed by Filter: 0
Buffers: shared hit=5050 read=2032 dirtied=59
I/O Timings: read=2586.123 write=0.000
Database Lab
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/8237/commands/29147Screenshots or screen recordings
Similar to #224661 (closed), just GitLab Data is not in the drop-down
How to set up and validate locally
Here are the ordered steps to reproduce the issue & validate the fix #224661 (comment 806790185)
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Related to #224661 (closed)
Edited by Abdul Wadood