Skip to content

feat(flags): make the config fields on /flags perfectly map to /decide #33194

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions rust/feature-flags/src/api/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ pub struct ConfigResponse {
pub supported_compression: Vec<String>,

/// If set, disables autocapture
#[serde(skip_serializing_if = "Option::is_none")]
pub autocapture_opt_out: Option<bool>,
#[serde(rename = "autocapture_opt_out")]
pub autocapture_opt_out: bool,

/// Originally capturePerformance was replay only and so boolean true
/// is equivalent to { network_timing: true }
Expand Down Expand Up @@ -163,10 +163,6 @@ pub struct ConfigResponse {
/// Whether to capture dead clicks
#[serde(skip_serializing_if = "Option::is_none")]
pub capture_dead_clicks: Option<bool>,

/// Indicates if the team has any flags enabled (if not we don't need to load them)
#[serde(skip_serializing_if = "Option::is_none")]
pub has_feature_flags: Option<bool>,
}

#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
Expand Down Expand Up @@ -367,8 +363,11 @@ pub struct SessionRecordingConfig {
pub url_blocklist: Option<Value>,
pub event_triggers: Option<Value>,
pub trigger_match_type: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub record_canvas: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub canvas_fps: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub canvas_quality: Option<String>,
}

Expand Down
64 changes: 23 additions & 41 deletions rust/feature-flags/src/handler/config_response_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ fn apply_core_config_fields(response: &mut FlagsResponse, config: &Config, team:
team.autocapture_web_vitals_allowed_metrics.as_ref();
let capture_network_timing = team.capture_performance_opt_in.unwrap_or(false);

response.config.has_feature_flags = Some(!response.flags.is_empty());
response.config.supported_compression = vec!["gzip".to_string(), "gzip-js".to_string()];
response.config.autocapture_opt_out = team.autocapture_opt_out;
response.config.autocapture_opt_out = team.autocapture_opt_out.unwrap_or(false);

response.config.analytics = if !*config.debug
&& !config.is_team_excluded(team.id, &config.new_analytics_capture_excluded_team_ids)
&& !config.new_analytics_capture_endpoint.is_empty()
{
Some(AnalyticsConfig {
endpoint: Some(config.new_analytics_capture_endpoint.clone()),
Expand Down Expand Up @@ -161,10 +161,7 @@ fn apply_core_config_fields(response: &mut FlagsResponse, config: &Config, team:
#[cfg(test)]
mod tests {
use crate::{
api::types::{
ConfigResponse, FlagDetails, FlagDetailsMetadata, FlagEvaluationReason, FlagsResponse,
SessionRecordingField,
},
api::types::{ConfigResponse, FlagsResponse, SessionRecordingField},
config::{Config, FlexBool, TeamIdCollection},
handler::{config_response_builder::apply_core_config_fields, session_recording},
team::team_models::Team,
Expand Down Expand Up @@ -234,7 +231,6 @@ mod tests {
response.config.supported_compression,
vec!["gzip", "gzip-js"]
);
assert_eq!(response.config.has_feature_flags, Some(false)); // empty flags
assert_eq!(response.config.default_identified_only, Some(true));
assert_eq!(response.config.is_authenticated, Some(false));
assert_eq!(
Expand All @@ -244,37 +240,6 @@ mod tests {
assert_eq!(response.config.toolbar_params, Some(json!({})));
}

#[test]
fn test_has_feature_flags_with_flags() {
let mut response = create_base_response();
response.flags.insert(
"test_flag".to_string(),
FlagDetails {
key: "test_flag".to_string(),
enabled: true,
variant: None,
reason: FlagEvaluationReason {
code: "condition_match".to_string(),
condition_index: Some(0),
description: None,
},
metadata: FlagDetailsMetadata {
id: 1,
version: 1,
description: None,
payload: None,
},
},
);

let config = Config::default_test_config();
let team = create_base_team();

apply_core_config_fields(&mut response, &config, &team);

assert_eq!(response.config.has_feature_flags, Some(true));
}

#[test]
fn test_analytics_config_enabled() {
let mut config = Config::default_test_config();
Expand Down Expand Up @@ -323,6 +288,21 @@ mod tests {
assert!(response.config.analytics.is_none());
}

#[test]
fn test_analytics_config_disabled_empty_endpoint() {
let mut config = Config::default_test_config();
config.debug = FlexBool(false);
config.new_analytics_capture_endpoint = "".to_string(); // Empty endpoint
config.new_analytics_capture_excluded_team_ids = TeamIdCollection::None; // None means exclude nobody

let mut response = create_base_response();
let team = create_base_team();

apply_core_config_fields(&mut response, &config, &team);

assert!(response.config.analytics.is_none());
}

#[test]
fn test_elements_chain_as_string_enabled() {
let mut config = Config::default_test_config();
Expand Down Expand Up @@ -546,7 +526,7 @@ mod tests {

apply_core_config_fields(&mut response, &config, &team);

assert_eq!(response.config.autocapture_opt_out, Some(true));
assert!(response.config.autocapture_opt_out);
}

#[test]
Expand All @@ -559,7 +539,7 @@ mod tests {

apply_core_config_fields(&mut response, &config, &team);

assert_eq!(response.config.autocapture_opt_out, Some(false));
assert!(!response.config.autocapture_opt_out);
}

#[test]
Expand Down Expand Up @@ -596,13 +576,15 @@ mod tests {

apply_core_config_fields(&mut response, &config, &team);

println!("response: {:?}", response);

// Test that defaults are applied correctly
assert_eq!(response.config.surveys, Some(json!(false)));
assert_eq!(response.config.heatmaps, Some(false));
assert_eq!(response.config.flags_persistence_default, Some(false));
assert_eq!(response.config.autocapture_exceptions, Some(json!(false)));
assert_eq!(response.config.capture_performance, Some(json!(false)));
assert!(response.config.autocapture_opt_out.is_none());
assert!(!response.config.autocapture_opt_out);
assert!(response.config.capture_dead_clicks.is_none());
}

Expand Down
2 changes: 1 addition & 1 deletion rust/feature-flags/src/handler/session_recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub fn session_recording_config_response(
None
};

// session_replay_config logic
// session_replay_config logic - only include canvas fields if record_canvas is configured
let (record_canvas, canvas_fps, canvas_quality) = if let Some(cfg) = &team.session_replay_config
{
if let Some(record_canvas) = cfg.get("record_canvas") {
Expand Down
2 changes: 1 addition & 1 deletion rust/feature-flags/src/site_apps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ mod tests {
let unique_url = format!(
"test://plugin/{}/{}",
name.replace(" ", "_"),
chrono::Utc::now()
uuid::Uuid::new_v4()
);
let plugin_id: i32 = sqlx::query_scalar(
r#"INSERT INTO posthog_plugin
Expand Down
Loading
Loading