feat(app): gate a collapsing guild header behind an experiment (#2779)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-14 21:15:31 +02:00
1 parent 550e6b05a1
commit d0c6146429
29 files changed
+1449 -115

No files matched your search

+111 -1
View File
@@ -10,7 +10,8 @@ use crate::{
CreateRegistrationUrlRequest, DeferredPhoneGateUpdateRequest,
ExperimentDeliveryConfigUpdateRequest, ExpressionInfoCardConfigUpdateRequest,
GatewayRolloutConfigUpdateRequest, GatewayRolloutMode,
GuildActivityLogPresentationConfigUpdateRequest, InstanceAttachmentDecayUpdateRequest,
GuildActivityLogPresentationConfigUpdateRequest,
GuildHeaderCollapseConfigUpdateRequest, InstanceAttachmentDecayUpdateRequest,
InstanceBlueskyIntegrationUpdateRequest, InstanceBlueskyKeyIntegrationUpdateRequest,
InstanceCaptchaIntegrationUpdateRequest, InstanceConfigUpdateRequest,
InstanceEmailIntegrationUpdateRequest, InstanceEmailSmtpIntegrationUpdateRequest,
@@ -231,6 +232,10 @@ pub async fn instance_config_post(
Ok(update) => instance_config_result(client.update_instance_config(&update).await),
Err(message) => FlashData::error(message),
},
"update_guild_header_collapse" => match build_guild_header_collapse_update(&form) {
Ok(update) => instance_config_result(client.update_instance_config(&update).await),
Err(message) => FlashData::error(message),
},
"update_experiment_delivery" => match build_experiment_delivery_update(&form) {
Ok(update) => instance_config_result(client.update_instance_config(&update).await),
Err(message) => FlashData::error(message),
@@ -805,6 +810,38 @@ fn build_expression_info_card_update(
})
}
fn build_guild_header_collapse_update(
form: &MultiValueForm,
) -> Result<InstanceConfigUpdateRequest, String> {
Ok(InstanceConfigUpdateRequest {
guild_header_collapse: Some(GuildHeaderCollapseConfigUpdateRequest {
enabled: Some(form.bool_value("guild_header_collapse_enabled")),
rollout_basis_points: parse_form_number(
form,
"guild_header_collapse_rollout_basis_points",
"Rollout basis points",
0,
EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX,
)?,
rollout_salt: parse_experiment_rollout_salt(
form,
"guild_header_collapse_rollout_salt",
)?,
included_user_ids: Some(parse_experiment_user_ids(
form.first("guild_header_collapse_included_user_ids")
.unwrap_or_default(),
"Included user IDs",
)?),
excluded_user_ids: Some(parse_experiment_user_ids(
form.first("guild_header_collapse_excluded_user_ids")
.unwrap_or_default(),
"Excluded user IDs",
)?),
}),
..Default::default()
})
}
fn build_experiment_delivery_update(
form: &MultiValueForm,
) -> Result<InstanceConfigUpdateRequest, String> {
@@ -1726,6 +1763,79 @@ mod tests {
}
}
#[test]
fn build_guild_header_collapse_update_reads_the_whole_form() {
let form = MultiValueForm::parse(
b"guild_header_collapse_enabled=true&guild_header_collapse_rollout_basis_points=2500&guild_header_collapse_rollout_salt=%20guild-header-collapse-v2%20&guild_header_collapse_included_user_ids=1500000000000000001%0A1500000000000000001&guild_header_collapse_excluded_user_ids=1500000000000000002%2C%201500000000000000003",
);
let update = build_guild_header_collapse_update(&form)
.expect("valid form")
.guild_header_collapse
.expect("guild header collapse update");
assert_eq!(update.enabled, Some(true));
assert_eq!(update.rollout_basis_points, Some(2_500));
assert_eq!(
update.rollout_salt,
Some("guild-header-collapse-v2".to_owned())
);
assert_eq!(
update.included_user_ids,
Some(vec!["1500000000000000001".to_owned()])
);
assert_eq!(
update.excluded_user_ids,
Some(vec![
"1500000000000000002".to_owned(),
"1500000000000000003".to_owned()
])
);
}
#[test]
fn build_guild_header_collapse_update_leaves_the_rollout_inert_when_nothing_is_submitted() {
let form = MultiValueForm::parse(b"_csrf=token");
let request = build_guild_header_collapse_update(&form).expect("valid form");
assert_eq!(
serde_json::to_value(request).expect("serializable update"),
serde_json::json!({"guild_header_collapse": {
"enabled": false,
"included_user_ids": [],
"excluded_user_ids": [],
}})
);
}
#[test]
fn build_guild_header_collapse_update_rejects_a_rollout_above_the_maximum() {
let form = MultiValueForm::parse(b"guild_header_collapse_rollout_basis_points=10001");
assert_eq!(
build_guild_header_collapse_update(&form).expect_err("invalid rollout"),
"Rollout basis points must be a whole number between 0 and 10000"
);
}
#[test]
fn build_guild_header_collapse_update_reads_only_its_own_prefix() {
let form = MultiValueForm::parse(
b"guild_header_collapse_enabled=true&guild_header_collapse_rollout_basis_points=2500&guild_header_collapse_rollout_salt=guild-header-collapse-v2&guild_header_collapse_included_user_ids=1500000000000000001&expression_card_rollout_basis_points=750&expression_card_rollout_salt=expression-info-card-v2&expression_card_excluded_user_ids=1500000000000000009",
);
let update = build_guild_header_collapse_update(&form)
.expect("valid form")
.guild_header_collapse
.expect("guild header collapse update");
assert_eq!(update.enabled, Some(true));
assert_eq!(update.rollout_basis_points, Some(2_500));
assert_eq!(
update.rollout_salt,
Some("guild-header-collapse-v2".to_owned())
);
assert_eq!(
update.included_user_ids,
Some(vec!["1500000000000000001".to_owned()])
);
assert_eq!(update.excluded_user_ids, Some(Vec::new()));
}
#[test]
fn build_experiment_delivery_update_leaves_both_fields_unchanged_when_absent() {
let form = MultiValueForm::parse(b"_csrf=token");