Cascade duo_features_enabled on save
What does this MR do and why?
- This MR updates the duo_features_enabled setting so that when a parent group changes the value of the attribute, all children also get a new vaue.
- This is already a "cascading attribute" but the way cascading
attributes currently work is that only if the child has a
nil
value for the setting they do a lookup and find the value of the attribute for the closest ancestor and inherit that value. Otherwise, the only way a setting cascades is if the parent locks the attribute. - This ancestor lookup method results in an N+1. Ideally we would move all cascading settings to actuall cascade like this one so that we avoid the N+1 but I am starting with just 1.
- Follow-up to !144931 (comment 1777185055)
- Feature flag issue: #460874 (closed)
Changelog: changed EE: true
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
This parameter is not sent anywhere, but will be once we implement [Ban AI] Add REST and GraphQL mutations/resourc... (#441482 - closed) (soon!)
Database
Database analysis of queries in the newly-added Worker:
Group#all_projects
For finding all projects within the root group and its descendants.
SQL:
puts Group.first.all_projects.to_sql
SELECT "projects"."id", "projects"."name", "projects"."path", "projects"."description", "projects"."created_at", "projects"."updated_at", "projects"."creator_id", "projects"."namespace_id", "projects"."last_activity_at", "projects"."import_url", "projects"."visibility_level", "projects"."archived", "projects"."avatar", "projects"."merge_requests_template", "projects"."star_count", "projects"."merge_requests_rebase_enabled", "projects"."import_type", "projects"."import_source", "projects"."approvals_before_merge", "projects"."reset_approvals_on_push", "projects"."merge_requests_ff_only_enabled", "projects"."issues_template", "projects"."mirror", "projects"."mirror_last_update_at", "projects"."mirror_last_successful_update_at", "projects"."mirror_user_id", "projects"."shared_runners_enabled", "projects"."runners_token", "projects"."build_allow_git_fetch", "projects"."build_timeout", "projects"."mirror_trigger_builds", "projects"."pending_delete", "projects"."public_builds", "projects"."last_repository_check_failed", "projects"."last_repository_check_at", "projects"."only_allow_merge_if_pipeline_succeeds", "projects"."has_external_issue_tracker", "projects"."repository_storage", "projects"."repository_read_only", "projects"."request_access_enabled", "projects"."has_external_wiki", "projects"."ci_config_path", "projects"."lfs_enabled", "projects"."description_html", "projects"."only_allow_merge_if_all_discussions_are_resolved", "projects"."repository_size_limit", "projects"."printing_merge_request_link_enabled", "projects"."auto_cancel_pending_pipelines", "projects"."service_desk_enabled", "projects"."cached_markdown_version", "projects"."delete_error", "projects"."last_repository_updated_at", "projects"."disable_overriding_approvers_per_merge_request", "projects"."storage_version", "projects"."resolve_outdated_diff_discussions", "projects"."remote_mirror_available_overridden", "projects"."only_mirror_protected_branches", "projects"."pull_mirror_available_overridden", "projects"."jobs_cache_index", "projects"."external_authorization_classification_label", "projects"."mirror_overwrites_diverged_branches", "projects"."pages_https_only", "projects"."external_webhook_token", "projects"."packages_enabled", "projects"."merge_requests_author_approval", "projects"."pool_repository_id", "projects"."runners_token_encrypted", "projects"."bfg_object_map", "projects"."detected_repository_languages", "projects"."merge_requests_disable_committers_approval", "projects"."require_password_to_approve", "projects"."max_pages_size", "projects"."max_artifacts_size", "projects"."pull_mirror_branch_prefix", "projects"."remove_source_branch_after_merge", "projects"."marked_for_deletion_at", "projects"."marked_for_deletion_by_user_id", "projects"."autoclose_referenced_issues", "projects"."suggestion_commit_message", "projects"."project_namespace_id", "projects"."hidden", "projects"."organization_id" FROM "projects" WHERE "projects"."namespace_id" IN (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{22}')))
Postgres.ai analysis for this sql using the gitlab-org
root group:
https://postgres.ai/console/gitlab/gitlab-production-tunnel-pg12/sessions/26763/commands/83520.
Group#self_and_descendants
For finding all groups within the root group.
SQL:
puts Group.first.self_and_descendants.to_sql
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"."max_personal_access_token_lifetime", "namespaces"."push_rule_id", "namespaces"."shared_runners_enabled", "namespaces"."allow_descendants_override_disabled_shared_runners", "namespaces"."traversal_ids", "namespaces"."organization_id" FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{22}'))
Postgres.ai analysis for this sql using the gitlab-org
root group: https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/26763/commands/83521
ProjectSetting.for_projects(project_ids_to_update).duo_features_set(!duo_features_enabled)
For finding all project settings with duo_features_enabled set to a certain value for a set of projects.
puts ProjectSetting.for_projects(project_ids_to_update).duo_features_set(!duo_features_enabled).to_sql
SELECT
"project_settings"."project_id",
"project_settings"."created_at",
"project_settings"."updated_at",
"project_settings"."push_rule_id",
"project_settings"."show_default_award_emojis",
"project_settings"."allow_merge_on_skipped_pipeline",
"project_settings"."squash_option",
"project_settings"."has_confluence",
"project_settings"."has_vulnerabilities",
"project_settings"."prevent_merge_without_jira_issue",
"project_settings"."cve_id_request_enabled",
"project_settings"."mr_default_target_self",
"project_settings"."previous_default_branch",
"project_settings"."warn_about_potentially_unwanted_characters",
"project_settings"."merge_commit_template",
"project_settings"."has_shimo",
"project_settings"."squash_commit_template",
"project_settings"."legacy_open_source_license_available",
"project_settings"."target_platforms",
"project_settings"."enforce_auth_checks_on_uploads",
"project_settings"."selective_code_owner_removals",
"project_settings"."issue_branch_template",
"project_settings"."show_diff_preview_in_email",
"project_settings"."suggested_reviewers_enabled",
"project_settings"."only_allow_merge_if_all_status_checks_passed",
"project_settings"."mirror_branch_regex",
"project_settings"."allow_pipeline_trigger_approve_deployment",
"project_settings"."emails_enabled",
"project_settings"."pages_unique_domain_enabled",
"project_settings"."pages_unique_domain",
"project_settings"."runner_registration_enabled",
"project_settings"."product_analytics_instrumentation_key",
"project_settings"."product_analytics_data_collector_host",
"project_settings"."cube_api_base_url",
"project_settings"."encrypted_cube_api_key",
"project_settings"."encrypted_cube_api_key_iv",
"project_settings"."encrypted_product_analytics_configurator_connection_string",
"project_settings"."encrypted_product_analytics_configurator_connection_string_iv",
"project_settings"."pages_multiple_versions_enabled",
"project_settings"."allow_merge_without_pipeline",
"project_settings"."duo_features_enabled",
"project_settings"."require_reauthentication_to_approve"
FROM
"project_settings"
WHERE
"project_settings"."project_id" = 41 AND
"project_settings"."duo_features_enabled" = FALSE;
Postgres.ai analysis for this sql using the gitlab-org
root group: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/28277/commands/88274
**See also: **!145876 (comment 1911124186)
`NamespaceSetting.for_namespaces(namespace_ids)
.duo_features_set(!duo_features_enabled)
.update_all(duo_features_enabled: duo_features_enabled)`
puts NamespaceSetting.where(namespace_id: namespace_ids, duo_features_enabled: !duo_features_enabled).to_sql
SELECT "namespace_settings"."created_at", "namespace_settings"."updated_at", "namespace_settings"."namespace_id", "namespace_settings"."prevent_forking_outside_group", "namespace_settings"."allow_mfa_for_subgroups", "namespace_settings"."default_branch_name", "namespace_settings"."repository_read_only", "namespace_settings"."resource_access_token_creation_allowed", "namespace_settings"."prevent_sharing_groups_outside_hierarchy", "namespace_settings"."new_user_signups_cap", "namespace_settings"."setup_for_company", "namespace_settings"."jobs_to_be_done", "namespace_settings"."runner_token_expiration_interval", "namespace_settings"."subgroup_runner_token_expiration_interval", "namespace_settings"."project_runner_token_expiration_interval", "namespace_settings"."show_diff_preview_in_email", "namespace_settings"."enabled_git_access_protocol", "namespace_settings"."unique_project_download_limit", "namespace_settings"."unique_project_download_limit_interval_in_seconds", "namespace_settings"."unique_project_download_limit_allowlist", "namespace_settings"."auto_ban_user_on_excessive_projects_download", "namespace_settings"."only_allow_merge_if_pipeline_succeeds", "namespace_settings"."allow_merge_on_skipped_pipeline", "namespace_settings"."only_allow_merge_if_all_discussions_are_resolved", "namespace_settings"."default_compliance_framework_id", "namespace_settings"."runner_registration_enabled", "namespace_settings"."allow_runner_registration_token", "namespace_settings"."unique_project_download_limit_alertlist", "namespace_settings"."emails_enabled", "namespace_settings"."experiment_features_enabled", "namespace_settings"."default_branch_protection_defaults", "namespace_settings"."service_access_tokens_expiration_enforced", "namespace_settings"."product_analytics_enabled", "namespace_settings"."allow_merge_without_pipeline", "namespace_settings"."enforce_ssh_certificates", "namespace_settings"."toggle_security_policy_custom_ci", "namespace_settings"."lock_toggle_security_policy_custom_ci", "namespace_settings"."lock_toggle_security_policies_policy_scope", "namespace_settings"."math_rendering_limits_enabled", "namespace_settings"."lock_math_rendering_limits_enabled", "namespace_settings"."duo_features_enabled", "namespace_settings"."lock_duo_features_enabled", "namespace_settings"."disable_personal_access_tokens", "namespace_settings"."enable_auto_assign_gitlab_duo_pro_seats" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" IN (125, 126) AND "namespace_settings"."duo_features_enabled" = FALSE
Postgres.ai analysis for this sql using the gitlab-org
root group: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/28277/commands/88276
**See also: **!145876 (comment 1911124202)
How to set up and validate locally
- Create a group with subgroups and projects (and even projects within the subgroups) and add a user as an owner of the group.
- Update the
duo_features_enabled
attribute on that group usingEE::Groups::UpdateService.new(group, user, { duo_features_enabled: false }).execute
- All subgroups and projects should also now have
duo_features_enable
set tofalse