mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(experiments): ship seven treatments to everyone (#2798)
This commit is contained in:
1 parent
9e614026d7
commit
910db6734b
167 files changed
+4608
-58147
No files matched your search
@@ -2,15 +2,11 @@
|
||||
|
||||
use crate::{
|
||||
api::types::{
|
||||
AppPublicConfigResponse, BlockedMessageGroupsConfigResponse,
|
||||
ExperimentDeliveryConfigResponse, ExpressionInfoCardConfigResponse,
|
||||
GatewayRolloutConfigResponse, GuildActivityLogPresentationConfigResponse,
|
||||
GuildHeaderCollapseConfigResponse, InstanceConfigResponse, InstanceIntegrationsResponse,
|
||||
InstanceMediaResponse, InstancePolicyResponse, InstanceRegistrationResponse,
|
||||
LimitConfigResponse, MessageHoverTrackingConfigResponse,
|
||||
MessageKeyboardFocusConfigResponse, NoiseSuppressionBackend, PendingRegistrationResponse,
|
||||
RegistrationUrlResponse, SsoConfigResponse, TypingIndicatorReworkConfigResponse,
|
||||
VOICE_NS_MAX_GUILD_OVERRIDES, VOICE_NS_MAX_TARGETED_USERS,
|
||||
AppPublicConfigResponse, ExperimentDeliveryConfigResponse, GatewayRolloutConfigResponse,
|
||||
InstanceConfigResponse, InstanceIntegrationsResponse, InstanceMediaResponse,
|
||||
InstancePolicyResponse, InstanceRegistrationResponse, LimitConfigResponse,
|
||||
NoiseSuppressionBackend, PendingRegistrationResponse, RegistrationUrlResponse,
|
||||
SsoConfigResponse, VOICE_NS_MAX_GUILD_OVERRIDES, VOICE_NS_MAX_TARGETED_USERS,
|
||||
VoiceNoiseSuppressionConfigResponse,
|
||||
},
|
||||
config::AdminConfig,
|
||||
@@ -152,13 +148,6 @@ pub fn instance_config_page(
|
||||
html! {
|
||||
(gateway_rollout_section(base, csrf_token, &instance_config.gateway_rollout))
|
||||
(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))
|
||||
(guild_activity_log_presentation_section(base, csrf_token, &instance_config.guild_activity_log_presentation))
|
||||
(expression_info_card_section(base, csrf_token, &instance_config.expression_info_card))
|
||||
(guild_header_collapse_section(base, csrf_token, &instance_config.guild_header_collapse))
|
||||
(typing_indicator_rework_section(base, csrf_token, &instance_config.typing_indicator_rework))
|
||||
(experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery))
|
||||
@if let Some(limit_config) = limit_config {
|
||||
(limit_config_section(base, limit_config))
|
||||
@@ -1186,733 +1175,6 @@ fn voice_noise_suppression_section(
|
||||
)
|
||||
}
|
||||
|
||||
fn message_hover_tracking_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
message_hover_tracking: &MessageHoverTrackingConfigResponse,
|
||||
) -> Markup {
|
||||
let status = if message_hover_tracking.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = message_hover_tracking.included_user_ids.join("\n");
|
||||
let excluded_user_ids = message_hover_tracking.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Message Hover Tracking",
|
||||
"Picks which message hover implementation targeted clients run in the message list. A \
|
||||
targeted client resolves the hovered message from one shared pointer oracle and drives \
|
||||
the message action bar from that state. While the master switch below is off every \
|
||||
client keeps the per-row implementation it ships with, whatever the rest of these \
|
||||
fields say.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_message_hover_tracking"} {
|
||||
(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 " (message_hover_tracking.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"message_hover_enabled",
|
||||
"true",
|
||||
"Serve message hover tracking assignments to clients",
|
||||
message_hover_tracking.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 hover behavior, so the rollout \
|
||||
and targeting fields below have no effect at all."
|
||||
}
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
|
||||
(number_field(
|
||||
"message_hover_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&message_hover_tracking.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(
|
||||
"message_hover_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&message_hover_tracking.rollout_salt,
|
||||
"message-hover-tracking-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(
|
||||
"message_hover_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(
|
||||
"message_hover_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 Message Hover Tracking Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn message_keyboard_focus_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
message_keyboard_focus: &MessageKeyboardFocusConfigResponse,
|
||||
) -> Markup {
|
||||
let status = if message_keyboard_focus.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = message_keyboard_focus.included_user_ids.join("\n");
|
||||
let excluded_user_ids = message_keyboard_focus.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Message Keyboard Focus",
|
||||
"Picks whether targeted clients run the keyboard navigation rework in the message list. \
|
||||
A targeted client reaches the message list from the composer with one Tab, walks \
|
||||
messages with the arrow keys through revealed blocked groups, and draws the focus ring \
|
||||
inside each row. While the master switch below is off every client keeps the keyboard \
|
||||
navigation it ships with, whatever the rest of these fields say.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_message_keyboard_focus"} {
|
||||
(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 " (message_keyboard_focus.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"message_keyboard_focus_enabled",
|
||||
"true",
|
||||
"Serve message keyboard focus assignments to clients",
|
||||
message_keyboard_focus.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 keyboard navigation, so the rollout \
|
||||
and targeting fields below have no effect at all."
|
||||
}
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
|
||||
(number_field(
|
||||
"message_keyboard_focus_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&message_keyboard_focus.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(
|
||||
"message_keyboard_focus_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&message_keyboard_focus.rollout_salt,
|
||||
"message-keyboard-focus-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(
|
||||
"message_keyboard_focus_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(
|
||||
"message_keyboard_focus_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 Message Keyboard Focus Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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 guild_activity_log_presentation_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
guild_activity_log_presentation: &GuildActivityLogPresentationConfigResponse,
|
||||
) -> Markup {
|
||||
let status = if guild_activity_log_presentation.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = guild_activity_log_presentation.included_user_ids.join("\n");
|
||||
let excluded_user_ids = guild_activity_log_presentation.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Guild Activity Log Presentation",
|
||||
"Picks which activity log rendering targeted clients run in community settings. A \
|
||||
targeted client renders each activity log entry through the rewritten presenters. \
|
||||
While the master switch below is off every client keeps the previous activity log \
|
||||
rendering, whatever the rest of these fields say.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_guild_activity_log_presentation"} {
|
||||
(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 " (guild_activity_log_presentation.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"guild_activity_log_presentation_enabled",
|
||||
"true",
|
||||
"Serve guild activity log presentation assignments to clients",
|
||||
guild_activity_log_presentation.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 the previous activity log 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(
|
||||
"guild_activity_log_presentation_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&guild_activity_log_presentation.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(
|
||||
"guild_activity_log_presentation_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&guild_activity_log_presentation.rollout_salt,
|
||||
"guild-activity-log-presentation-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(
|
||||
"guild_activity_log_presentation_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(
|
||||
"guild_activity_log_presentation_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 Guild Activity Log Presentation Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn expression_info_card_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
expression_info_card: &ExpressionInfoCardConfigResponse,
|
||||
) -> Markup {
|
||||
let status = if expression_info_card.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = expression_info_card.included_user_ids.join("\n");
|
||||
let excluded_user_ids = expression_info_card.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Expression Info Card",
|
||||
"Picks what a targeted client shows for an emoji or a sticker in a message. A targeted \
|
||||
client opens a click-triggered info card that names the expression, says where it comes \
|
||||
from, and offers a row for the source community the reader can open. While the master \
|
||||
switch below is off every client keeps the hover tooltip it ships with, whatever the \
|
||||
rest of these fields say.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_expression_info_card"} {
|
||||
(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 " (expression_info_card.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"expression_card_enabled",
|
||||
"true",
|
||||
"Serve expression info card assignments to clients",
|
||||
expression_info_card.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 tooltip, so the rollout \
|
||||
and targeting fields below have no effect at all."
|
||||
}
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
|
||||
(number_field(
|
||||
"expression_card_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&expression_info_card.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(
|
||||
"expression_card_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&expression_info_card.rollout_salt,
|
||||
"expression-info-card-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(
|
||||
"expression_card_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(
|
||||
"expression_card_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 Expression Info Card Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn guild_header_collapse_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
guild_header_collapse: &GuildHeaderCollapseConfigResponse,
|
||||
) -> Markup {
|
||||
let status = if guild_header_collapse.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = guild_header_collapse.included_user_ids.join("\n");
|
||||
let excluded_user_ids = guild_header_collapse.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Guild Header Collapse",
|
||||
"Picks how a targeted client draws the guild banner above the channel list. A targeted \
|
||||
client reduces that banner to the height of the header as the channel list scrolls down \
|
||||
and returns it to full height as the list scrolls back up. While the master switch below \
|
||||
is off every client keeps the fixed banner height it ships with, whatever the rest of \
|
||||
these fields say.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_guild_header_collapse"} {
|
||||
(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 " (guild_header_collapse.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"guild_header_collapse_enabled",
|
||||
"true",
|
||||
"Serve guild header collapse assignments to clients",
|
||||
guild_header_collapse.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 banner height, so the rollout \
|
||||
and targeting fields below have no effect at all."
|
||||
}
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
|
||||
(number_field(
|
||||
"guild_header_collapse_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&guild_header_collapse.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(
|
||||
"guild_header_collapse_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&guild_header_collapse.rollout_salt,
|
||||
"guild-header-collapse-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(
|
||||
"guild_header_collapse_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(
|
||||
"guild_header_collapse_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 Guild Header Collapse Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn typing_indicator_rework_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
typing_indicator_rework: &TypingIndicatorReworkConfigResponse,
|
||||
) -> Markup {
|
||||
let status = if typing_indicator_rework.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = typing_indicator_rework.included_user_ids.join("\n");
|
||||
let excluded_user_ids = typing_indicator_rework.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Typing Indicator Rework",
|
||||
"Picks how a targeted client sends and shows typing indicators. A targeted client sends a \
|
||||
typing signal 1.5 seconds after someone starts typing and then at most once every 8 \
|
||||
seconds, names up to three typists, and announces those names to screen readers even \
|
||||
where the visible row collapses them. While the master switch below is off every client \
|
||||
keeps the typing behaviour it ships with, whatever the rest of these fields say.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_typing_indicator_rework"} {
|
||||
(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 " (typing_indicator_rework.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"typing_indicator_rework_enabled",
|
||||
"true",
|
||||
"Serve typing indicator rework assignments to clients",
|
||||
typing_indicator_rework.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 typing behaviour, so the rollout \
|
||||
and targeting fields below have no effect at all."
|
||||
}
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
|
||||
(number_field(
|
||||
"typing_indicator_rework_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&typing_indicator_rework.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(
|
||||
"typing_indicator_rework_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&typing_indicator_rework.rollout_salt,
|
||||
"typing-indicator-rework-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(
|
||||
"typing_indicator_rework_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(
|
||||
"typing_indicator_rework_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 Typing Indicator Rework Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn experiment_delivery_section(
|
||||
base: &str,
|
||||
csrf_token: &str,
|
||||
@@ -2568,44 +1830,4 @@ mod tests {
|
||||
assert!(markup.contains("1000 of 1000 stored"));
|
||||
assert!(markup.contains("at the cap"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guild_header_collapse_section_posts_its_own_action_and_fields() {
|
||||
let guild_header_collapse = GuildHeaderCollapseConfigResponse {
|
||||
included_user_ids: vec!["1500000000000000001".to_owned()],
|
||||
excluded_user_ids: vec!["1500000000000000002".to_owned()],
|
||||
..GuildHeaderCollapseConfigResponse::default()
|
||||
};
|
||||
let markup =
|
||||
guild_header_collapse_section("/admin", "csrf", &guild_header_collapse).into_string();
|
||||
assert!(markup.contains("/instance-config?action=update_guild_header_collapse"));
|
||||
assert!(markup.contains("Guild Header Collapse"));
|
||||
assert!(markup.contains("guild-header-collapse-v1"));
|
||||
assert!(markup.contains("guild_header_collapse_enabled"));
|
||||
assert!(markup.contains("guild_header_collapse_rollout_basis_points"));
|
||||
assert!(markup.contains("guild_header_collapse_rollout_salt"));
|
||||
assert!(markup.contains("guild_header_collapse_included_user_ids"));
|
||||
assert!(markup.contains("guild_header_collapse_excluded_user_ids"));
|
||||
assert!(!markup.contains("expression_card_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_indicator_rework_section_posts_its_own_action_and_fields() {
|
||||
let typing_indicator_rework = TypingIndicatorReworkConfigResponse {
|
||||
included_user_ids: vec!["1500000000000000001".to_owned()],
|
||||
excluded_user_ids: vec!["1500000000000000002".to_owned()],
|
||||
..TypingIndicatorReworkConfigResponse::default()
|
||||
};
|
||||
let markup = typing_indicator_rework_section("/admin", "csrf", &typing_indicator_rework)
|
||||
.into_string();
|
||||
assert!(markup.contains("/instance-config?action=update_typing_indicator_rework"));
|
||||
assert!(markup.contains("Typing Indicator Rework"));
|
||||
assert!(markup.contains("typing-indicator-rework-v1"));
|
||||
assert!(markup.contains("typing_indicator_rework_enabled"));
|
||||
assert!(markup.contains("typing_indicator_rework_rollout_basis_points"));
|
||||
assert!(markup.contains("typing_indicator_rework_rollout_salt"));
|
||||
assert!(markup.contains("typing_indicator_rework_included_user_ids"));
|
||||
assert!(markup.contains("typing_indicator_rework_excluded_user_ids"));
|
||||
assert!(!markup.contains("expression_card_"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user