Seat assignment create worker
What does this MR do and why?
This Merge Request introduces a worker responsible for creating seat assignments during the transfer of a namespace, which can be either a group or a project.
Context This MR is part of a larger effort to correctly handle seat assignment creation and deletion during group or project transfers. Specifically, it is 1 of 4 MRs addressing:
- Creation of seat assignments worker <- Here
- Deletion of outdated seat assignments worker
- Add create seats worker to group and project after update hooks (needs 1)
- Add remove outdated seats worker to group and project after update hooks (needs 2).
References
It's a part of:
How to set up and validate locally
Test 01:
- Select a namespace with missing seat assignments (N)
- Verify the number of seats in N with
GitlabSubscriptions::SeatAssignment.by_namespace(N) - In the rails console, type
GitlabSubscriptions::SeatAssignments::MemberTransfers::CreateSeatsWorker.perfom_async(N) - Verify the number of seats again with
GitlabSubscriptions::SeatAssignment.by_namespace(N)
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.
SQL Plans:
create_new_seat_assignments
-
GitlabSubscriptions::SeatAssignment.insert_all(seat_assignments, unique_by: [:namespace_id, :user_id])
INSERT INTO "subscription_seat_assignments" ("namespace_id","user_id","created_at","updated_at","organization_id") VALUES (347, 141, '2025-05-13 23:20:16.800991', '2025-05-13 23:20:16.801034', 1) ON CONFLICT ("namespace_id","user_id") WHERE (namespace_id IS NOT NULL) DO NOTHING RETURNING "id"
-
CreateGroupSeatsWorker#collect_user_ids
SELECT "members"."id", "members"."access_level", "members"."source_id", "members"."source_type", "members"."user_id", "members"."notification_level", "members"."type", "members"."created_at", "members"."updated_at", "members"."created_by_id", "members"."invite_email", "members"."invite_token", "members"."invite_accepted_at", "members"."requested_at", "members"."expires_at", "members"."ldap", "members"."override", "members"."state", "members"."invite_email_success", "members"."member_namespace_id", "members"."member_role_id", "members"."expiry_notified_at", "members"."request_accepted_at" FROM ((SELECT "members"."id", "members"."access_level", "members"."source_id", "members"."source_type", "members"."user_id", "members"."notification_level", "members"."type", "members"."created_at", "members"."updated_at", "members"."created_by_id", "members"."invite_email", "members"."invite_token", "members"."invite_accepted_at", "members"."requested_at", "members"."expires_at", "members"."ldap", "members"."override", "members"."state", "members"."invite_email_success", "members"."member_namespace_id", "members"."member_role_id", "members"."expiry_notified_at", "members"."request_accepted_at" FROM "members" WHERE "members"."source_id" IN (SELECT "namespaces"."id" FROM UNNEST(
COALESCE(
(SELECT ids FROM (SELECT "namespace_descendants"."self_and_descendant_group_ids" AS ids FROM "namespace_descendants" WHERE "namespace_descendants"."outdated_at" IS NULL AND "namespace_descendants"."namespace_id" = 9970) cached_query),
(SELECT ids FROM (SELECT ARRAY_AGG("namespaces"."id") AS ids FROM (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{9970}'))) namespaces) consistent_query))
) AS namespaces(id)
) AND "members"."source_type" = 'Namespace')
UNION ALL
(SELECT "members"."id", "members"."access_level", "members"."source_id", "members"."source_type", "members"."user_id", "members"."notification_level", "members"."type", "members"."created_at", "members"."updated_at", "members"."created_by_id", "members"."invite_email", "members"."invite_token", "members"."invite_accepted_at", "members"."requested_at", "members"."expires_at", "members"."ldap", "members"."override", "members"."state", "members"."invite_email_success", "members"."member_namespace_id", "members"."member_role_id", "members"."expiry_notified_at", "members"."request_accepted_at" FROM "members" WHERE "members"."source_id" IN (SELECT "projects"."id" FROM UNNEST(
COALESCE(
(SELECT ids FROM (SELECT "namespace_descendants"."all_project_ids" AS ids FROM "namespace_descendants" WHERE "namespace_descendants"."outdated_at" IS NULL AND "namespace_descendants"."namespace_id" = 9970) cached_query),
(SELECT ids FROM (SELECT ARRAY_AGG("projects"."id") AS ids FROM (SELECT "projects"."id" FROM "projects" WHERE "projects"."namespace_id" IN (SELECT "namespaces"."id" FROM UNNEST(
COALESCE(
(SELECT ids FROM (SELECT "namespace_descendants"."self_and_descendant_group_ids" AS ids FROM "namespace_descendants" WHERE "namespace_descendants"."outdated_at" IS NULL AND "namespace_descendants"."namespace_id" = 9970) cached_query),
(SELECT ids FROM (SELECT ARRAY_AGG("namespaces"."id") AS ids FROM (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{9970}'))) namespaces) consistent_query))
) AS namespaces(id)
)) projects) consistent_query))
) AS projects(id)
) AND "members"."source_type" = 'Project')) members
-
CreateProjectSeatsWorker#collect_user_ids
SELECT
"members"."id",
"members"."access_level",
"members"."source_id",
"members"."source_type",
"members"."user_id",
"members"."notification_level",
"members"."type",
"members"."created_at",
"members"."updated_at",
"members"."created_by_id",
"members"."invite_email",
"members"."invite_token",
"members"."invite_accepted_at",
"members"."requested_at",
"members"."expires_at",
"members"."ldap",
"members"."override",
"members"."state",
"members"."invite_email_success",
"members"."member_namespace_id",
"members"."member_role_id",
"members"."expiry_notified_at",
"members"."request_accepted_at"
FROM "members"
WHERE "members"."type" = 'ProjectMember'
AND "members"."source_id" = 45
AND "members"."source_type" = 'Project'
AND "members"."requested_at" IS NULL;
Edited by Jorge Cook