fix(app): gate blocked group rendering behind an experiment (#2763)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-14 16:08:19 +02:00
1 parent 4b278a0da8
commit ed10f9d323
22 files changed
+938 -30

No files matched your search

@@ -2,13 +2,13 @@
use crate::{
api::types::{
AppPublicConfigResponse, ExperimentDeliveryConfigResponse, GatewayRolloutConfigResponse,
InstanceConfigResponse, InstanceIntegrationsResponse, InstanceMediaResponse,
InstancePolicyResponse, InstanceRegistrationResponse, LimitConfigResponse,
MessageHoverTrackingConfigResponse, MessageKeyboardFocusConfigResponse,
NoiseSuppressionBackend, PendingRegistrationResponse, RegistrationUrlResponse,
SsoConfigResponse, VOICE_NS_MAX_GUILD_OVERRIDES, VOICE_NS_MAX_TARGETED_USERS,
VoiceNoiseSuppressionConfigResponse,
AppPublicConfigResponse, BlockedMessageGroupsConfigResponse,
ExperimentDeliveryConfigResponse, GatewayRolloutConfigResponse, InstanceConfigResponse,
InstanceIntegrationsResponse, InstanceMediaResponse, InstancePolicyResponse,
InstanceRegistrationResponse, LimitConfigResponse, MessageHoverTrackingConfigResponse,
MessageKeyboardFocusConfigResponse, NoiseSuppressionBackend, PendingRegistrationResponse,
RegistrationUrlResponse, SsoConfigResponse, VOICE_NS_MAX_GUILD_OVERRIDES,
VOICE_NS_MAX_TARGETED_USERS, VoiceNoiseSuppressionConfigResponse,
},
config::AdminConfig,
middleware::auth::AuthContext,
@@ -151,6 +151,7 @@ pub fn instance_config_page(
(voice_noise_suppression_section(base, csrf_token, &instance_config.voice_noise_suppression))
(message_hover_tracking_section(base, csrf_token, &instance_config.message_hover_tracking))
(message_keyboard_focus_section(base, csrf_token, &instance_config.message_keyboard_focus))
(blocked_message_groups_section(base, csrf_token, &instance_config.blocked_message_groups))
(experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery))
@if let Some(limit_config) = limit_config {
(limit_config_section(base, limit_config))
@@ -1386,6 +1387,110 @@ fn message_keyboard_focus_section(
)
}
fn blocked_message_groups_section(
base: &str,
csrf_token: &str,
blocked_message_groups: &BlockedMessageGroupsConfigResponse,
) -> Markup {
let status = if blocked_message_groups.enabled {
("Live", BadgeVariant::Success)
} else {
("Inert", BadgeVariant::Default)
};
let included_user_ids = blocked_message_groups.included_user_ids.join("\n");
let excluded_user_ids = blocked_message_groups.excluded_user_ids.join("\n");
section_card_with_description(
"Blocked Message Groups",
"Picks how targeted clients render a revealed block of blocked or suspected spam \
messages. A targeted client draws the block full width, spaces consecutive message \
groups inside it, and keys an unread divider apart from the group below it. While the \
master switch below is off every client keeps the rendering it ships with, whatever the \
rest of these fields say.",
html! {
form method="post" action={(base) "/instance-config?action=update_blocked_message_groups"} {
(csrf_input(csrf_token))
div class="space-y-6" {
div class="flex flex-wrap items-center gap-2" {
h3 class="text-sm font-semibold text-neutral-900" { "Master switch" }
(badge(status.0, status.1))
span class="text-xs text-neutral-500" {
"Config version " (blocked_message_groups.config_version)
}
}
(checkbox(
"blocked_groups_enabled",
"true",
"Serve blocked message groups assignments to clients",
blocked_message_groups.enabled,
true,
))
p class="text-xs text-neutral-500" {
"Off is the safe state. With this unchecked every client is told the \
rollout is inert and keeps its current rendering, so the rollout \
and targeting fields below have no effect at all."
}
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
(number_field(
"blocked_groups_rollout_basis_points",
"Rollout (basis points)",
&blocked_message_groups.rollout_basis_points.to_string(),
Some(0), Some(10000), "1",
Some("Share of users bucketed into the canary, in basis points: 0 is nobody, 100 is 1%, 10000 is everybody."),
))
div class="flex flex-col gap-2" {
(text_input(
"blocked_groups_rollout_salt",
"Rollout Salt",
&blocked_message_groups.rollout_salt,
"blocked-message-groups-v1",
))
p class="text-xs text-neutral-500" {
"Seeds the bucketing hash. Changing it reshuffles which users fall \
inside the percentage above. Leave it alone to keep the current \
cohort stable."
}
}
div class="flex flex-col gap-2" {
(textarea_input(
"blocked_groups_included_user_ids",
"Always-on User IDs",
"1500000000000000001\n1500000000000000002",
&included_user_ids,
4,
false,
))
p class="text-xs text-neutral-500" {
"One snowflake per line, or comma separated. These users are targeted \
regardless of the percentage above. IDs must contain 1 to 20 decimal \
digits. Invalid entries prevent the save; blank entries and duplicate \
IDs are ignored."
}
}
div class="flex flex-col gap-2" {
(textarea_input(
"blocked_groups_excluded_user_ids",
"Never-on User IDs",
"1500000000000000003\n1500000000000000004",
&excluded_user_ids,
4,
false,
))
p class="text-xs text-neutral-500" {
"Same format. Exclusion wins over both the always-on list and the \
percentage, so this is the per-user kill switch."
}
}
(form_actions(html! {
(submit_button("Save Blocked Message Groups Configuration"))
}))
}
}
},
)
}
fn experiment_delivery_section(
base: &str,
csrf_token: &str,