ItemConsumersFinder & GraphQL support for group inheritance
What does this MR do and why?
ItemConsumersFinder & GraphQL support inheritance
This MR adds support for group inheritance records within ItemConsumersFinder, which are now exposed through the existing aiCatalogConfiguredItems GraphQL field.
By default, consumers enabled for group inheritance will be included in results. The caller can pass include_inherited: false to opt-out of receiving group inheritance records. The intention of this decision is to make group inheritance look ups to work by default.
References
Group inheritance (BE): Finders and GraphQL type (#627519)
How to set up and validate locally
In rails console enable the feature flag:
Feature.enable(:ai_catalog_group_inherited_consumers)Create an agent in the project (Automate > Flows > New flow).
Ensure you set its visibility to public.
Now create a group inheritance consumer for a top-level group (replacing <item-id> with the new agent ID, and <top-level-group-id> with the group ID:
mutation {
aiCatalogItemConsumerCreate(input: {
itemId: "gid://gitlab/Ai::Catalog::Item/<item-id>",
target: { groupId: "gid://gitlab/Group/<top-level-group-id>" },
consumerKind: INHERITANCE
}) {
errors
itemConsumer {
id
consumerKind
}
}
}Now, for any descendant project of the group, query for the configured items of the project (replacing <project-id> with the real project ID):
query {
aiCatalogConfiguredItems(projectId: "gid://gitlab/Project/<project-id>"){
nodes {
id
consumerKind
item {
name
}
}
}
}In amongst the results should be the group inheritance record, for example:
{
"data": {
"aiCatalogConfiguredItems": {
"nodes": [
{
"id": "gid://gitlab/Ai::Catalog::ItemConsumer/450",
"consumerKind": "INHERITANCE",
"item": {
"name": "Agent for group enablement"
}
}
]
}
},
"correlationId": "01M49T9JTJWRGVJVP9BE3GQ5P9"
}Now call the same query, this time with includeInherited: false:
query {
aiCatalogConfiguredItems(projectId: "gid://gitlab/Project/<project-id>", includeInherited: false){
nodes {
id
consumerKind
item {
name
}
}
}
}The inheritance record should not be included.
Also test without includeInherited, but the flag disabled, and inheritance records should not be included.
New finder SQL
We've added this line to ItemConsumersFinder.
First, adding 100 inheritance records into a Postgres.ai session, to be able to test the new OR:
exec INSERT INTO ai_catalog_item_consumers
(ai_catalog_item_id, group_id, kind, enabled, locked, created_at, updated_at)
SELECT i.id, 9970, 1, true, true, now(), now()
FROM ai_catalog_items i
WHERE i.visibility = 2 -- public
AND i.deleted_at IS NULL
AND i.foundational_flow_reference IS NULL -- foundational flows can't be inherited
AND i.organization_id = (SELECT organization_id FROM namespaces WHERE id = 9970)
AND NOT EXISTS (
SELECT 1 FROM ai_catalog_item_consumers c
WHERE c.group_id = 9970 AND c.ai_catalog_item_id = i.id
)
ORDER BY i.id
LIMIT 100;SQL generated through GraphQL:
SELECT "ai_catalog_item_consumers".*,
CASE
WHEN "item"."id" IN ( 348, 356, 1003596, 1004583,
1005590, 1003979 ) THEN 1
WHEN "item"."id" IN ( 2337, 2334, 2331, 2332 ) THEN 2
WHEN "item"."verification_level" = 100 THEN 3
WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
ELSE 5
end AS consumer_priority
FROM "ai_catalog_item_consumers"
INNER JOIN "ai_catalog_items" "item"
ON "item"."id" = "ai_catalog_item_consumers"."ai_catalog_item_id"
LEFT OUTER JOIN "project_authorizations"
ON "project_authorizations"."project_id" =
"item"."project_id"
AND "project_authorizations"."user_id" = 2702368
WHERE ( "ai_catalog_item_consumers"."project_id" = 278964
OR "ai_catalog_item_consumers"."group_id" = 9970
AND "ai_catalog_item_consumers"."kind" = 1 )
ORDER BY CASE
WHEN "item"."id" IN ( 348, 356, 1003596, 1004583,
1005590, 1003979 ) THEN 1
WHEN "item"."id" IN ( 2337, 2334, 2331, 2332 ) THEN 2
WHEN "item"."verification_level" = 100 THEN 3
WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
ELSE 5
end ASC,
"ai_catalog_item_consumers"."id" DESC
LIMIT 20 Explain in Postgres.ai: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/62263/commands/167392
Explain plan:
Limit (cost=45.81..45.83 rows=6 width=86) (actual time=49.570..49.576 rows=20 loops=1)
Buffers: shared hit=1308 read=47
I/O Timings: read=48.181 write=0.000
-> Sort (cost=45.81..45.83 rows=6 width=86) (actual time=49.569..49.572 rows=20 loops=1)
Sort Key: (CASE WHEN (item.id = ANY ('{348,356,1003596,1004583,1005590,1003979}'::bigint[])) THEN 1 WHEN (item.id = ANY ('{2337,2334,2331,2332}'::bigint[])) THEN 2 WHEN (item.verification_level = 100) THEN 3 WHEN (project_authorizations.access_level IS NOT NULL) THEN 4 ELSE 5 END), ai_catalog_item_consumers.id DESC
Sort Method: top-N heapsort Memory: 30kB
Buffers: shared hit=1308 read=47
I/O Timings: read=48.181 write=0.000
-> Nested Loop Left Join (cost=4.79..45.74 rows=6 width=86) (actual time=4.431..49.477 rows=182 loops=1)
Buffers: shared hit=1302 read=47
I/O Timings: read=48.181 write=0.000
-> Nested Loop (cost=4.21..32.97 rows=6 width=100) (actual time=0.067..0.775 rows=182 loops=1)
Buffers: shared hit=620
I/O Timings: read=0.000 write=0.000
-> Bitmap Heap Scan on public.ai_catalog_item_consumers (cost=3.92..13.15 rows=6 width=82) (actual time=0.055..0.240 rows=182 loops=1)
Recheck Cond: ((ai_catalog_item_consumers.project_id = 278964) OR ((ai_catalog_item_consumers.group_id = 9970) AND (ai_catalog_item_consumers.kind = 1)))
Heap Blocks: exact=62
Buffers: shared hit=74
I/O Timings: read=0.000 write=0.000
-> BitmapOr (cost=3.92..3.92 rows=6 width=0) (actual time=0.045..0.046 rows=0 loops=1)
Buffers: shared hit=12
I/O Timings: read=0.000 write=0.000
-> Bitmap Index Scan on index_ai_catalog_item_consumers_on_project_id (cost=0.00..1.98 rows=6 width=0) (actual time=0.024..0.024 rows=63 loops=1)
Index Cond: (ai_catalog_item_consumers.project_id = 278964)
Buffers: shared hit=6
I/O Timings: read=0.000 write=0.000
-> Bitmap Index Scan on index_ai_catalog_item_consumers_on_group_id_kind_sa (cost=0.00..1.94 rows=1 width=0) (actual time=0.020..0.020 rows=119 loops=1)
Index Cond: ((ai_catalog_item_consumers.group_id = 9970) AND (ai_catalog_item_consumers.kind = 1))
Buffers: shared hit=6
I/O Timings: read=0.000 write=0.000
-> Index Scan using ai_catalog_items_pkey on public.ai_catalog_items item (cost=0.29..3.30 rows=1 width=18) (actual time=0.003..0.003 rows=1 loops=182)
Index Cond: (item.id = ai_catalog_item_consumers.ai_catalog_item_id)
Buffers: shared hit=546
I/O Timings: read=0.000 write=0.000
-> Index Only Scan using project_authorizations_pkey on public.project_authorizations (cost=0.58..2.10 rows=1 width=8) (actual time=0.267..0.267 rows=0 loops=182)
Index Cond: ((project_authorizations.user_id = 2702368) AND (project_authorizations.project_id = item.project_id))
Heap Fetches: 1
Buffers: shared hit=682 read=47
I/O Timings: read=48.181 write=0.000
Settings: work_mem = '230MB', jit = 'off', random_page_cost = '1.5', seq_page_cost = '4', effective_cache_size = '472585MB'
Query ID: -5221354515240028815Explain link: https://explain.depesz.com/s/nLTd
SQL Generated within API endpoints
Generated here (as requested !259236 (comment 3967653840)).
SELECT "ai_catalog_item_consumers".*,
CASE
WHEN "item"."verification_level" = 100 THEN 3
WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
ELSE 5
end AS consumer_priority
FROM "ai_catalog_item_consumers"
INNER JOIN "ai_catalog_items" "item"
ON "item"."id" = "ai_catalog_item_consumers"."ai_catalog_item_id"
LEFT OUTER JOIN "project_authorizations"
ON "project_authorizations"."project_id" =
"item"."project_id"
AND "project_authorizations"."user_id" = 2702368
WHERE ( "ai_catalog_item_consumers"."project_id" = 278964
OR "ai_catalog_item_consumers"."group_id" = 9970
AND "ai_catalog_item_consumers"."kind" = 1 )
AND "ai_catalog_item_consumers"."ai_catalog_item_id" = 1015514
ORDER BY CASE
WHEN "item"."verification_level" = 100 THEN 3
WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
ELSE 5
end ASC,
"ai_catalog_item_consumers"."id" DESC
LIMIT 1 Explain in Postgres.ai https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/62263/commands/167393
Explain plan:
Limit (cost=14.78..14.78 rows=1 width=86) (actual time=2.846..2.848 rows=1 loops=1)
Buffers: shared hit=30 read=3
I/O Timings: read=2.657 write=0.000
-> Sort (cost=14.78..14.78 rows=1 width=86) (actual time=2.844..2.847 rows=1 loops=1)
Sort Key: (CASE WHEN (item.verification_level = 100) THEN 3 WHEN (project_authorizations.access_level IS NOT NULL) THEN 4 ELSE 5 END), ai_catalog_item_consumers.id DESC
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=30 read=3
I/O Timings: read=2.657 write=0.000
-> Nested Loop Left Join (cost=7.19..14.77 rows=1 width=86) (actual time=2.812..2.818 rows=1 loops=1)
Buffers: shared hit=24 read=3
I/O Timings: read=2.657 write=0.000
-> Nested Loop (cost=6.61..11.15 rows=1 width=92) (actual time=0.767..0.772 rows=1 loops=1)
Buffers: shared hit=18 read=1
I/O Timings: read=0.662 write=0.000
-> Bitmap Heap Scan on public.ai_catalog_item_consumers (cost=6.32..7.84 rows=1 width=82) (actual time=0.752..0.754 rows=1 loops=1)
Recheck Cond: ((ai_catalog_item_consumers.ai_catalog_item_id = 1015514) AND ((ai_catalog_item_consumers.project_id = 278964) OR ((ai_catalog_item_consumers.group_id = 9970) AND (ai_catalog_item_consumers.kind = 1))))
Heap Blocks: exact=1
Buffers: shared hit=15 read=1
I/O Timings: read=0.662 write=0.000
-> BitmapAnd (cost=6.32..6.32 rows=1 width=0) (actual time=0.739..0.740 rows=0 loops=1)
Buffers: shared hit=14 read=1
I/O Timings: read=0.662 write=0.000
-> Bitmap Index Scan on index_ai_catalog_item_consumers_on_ai_catalog_item_id (cost=0.00..2.15 rows=29 width=0) (actual time=0.691..0.691 rows=2 loops=1)
Index Cond: (ai_catalog_item_consumers.ai_catalog_item_id = 1015514)
Buffers: shared hit=5 read=1
I/O Timings: read=0.662 write=0.000
-> BitmapOr (cost=3.92..3.92 rows=6 width=0) (actual time=0.046..0.047 rows=0 loops=1)
Buffers: shared hit=9
I/O Timings: read=0.000 write=0.000
-> Bitmap Index Scan on index_ai_catalog_item_consumers_on_project_id (cost=0.00..1.98 rows=6 width=0) (actual time=0.022..0.022 rows=63 loops=1)
Index Cond: (ai_catalog_item_consumers.project_id = 278964)
Buffers: shared hit=3
I/O Timings: read=0.000 write=0.000
-> Bitmap Index Scan on index_ai_catalog_item_consumers_on_group_id_kind_sa (cost=0.00..1.94 rows=1 width=0) (actual time=0.024..0.024 rows=119 loops=1)
Index Cond: ((ai_catalog_item_consumers.group_id = 9970) AND (ai_catalog_item_consumers.kind = 1))
Buffers: shared hit=6
I/O Timings: read=0.000 write=0.000
-> Index Scan using ai_catalog_items_pkey on public.ai_catalog_items item (cost=0.29..3.30 rows=1 width=18) (actual time=0.014..0.015 rows=1 loops=1)
Index Cond: (item.id = 1015514)
Buffers: shared hit=3
I/O Timings: read=0.000 write=0.000
-> Index Only Scan using index_project_authorizations_on_project_user_access_level on public.project_authorizations (cost=0.58..3.60 rows=1 width=8) (actual time=2.041..2.042 rows=1 loops=1)
Index Cond: ((project_authorizations.project_id = item.project_id) AND (project_authorizations.user_id = 2702368))
Heap Fetches: 0
Buffers: shared hit=6 read=2
I/O Timings: read=1.994 write=0.000
Settings: work_mem = '230MB', jit = 'off', random_page_cost = '1.5', seq_page_cost = '4', effective_cache_size = '472585MB'
Query ID: -4169221697175183799Explain link: https://explain.depesz.com/s/gjRS
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #627519