Skip to content
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
248 changes: 242 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 4 additions & 13 deletions crates/cac_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,20 +160,11 @@ impl Client {
prefix: Option<Vec<String>>,
) -> Result<Config, String> {
let cac = self.config.read().await;
let mut config = cac.to_owned();
if let Some(prefix_list) = prefix {
config = config.filter_by_prefix(&HashSet::from_iter(prefix_list));
}

let dimension_filtered_config = query_data
.filter(|query_map| !query_map.is_empty())
.map(|query_map| config.filter_by_dimensions(&query_map));

if let Some(filtered_config) = dimension_filtered_config {
config = filtered_config;
};
let filtered_config = cac
.to_owned()
.filter(query_data.as_ref(), prefix.map(HashSet::from_iter).as_ref());

Ok(config)
Ok(filtered_config)
}

pub async fn get_last_modified(&self) -> DateTime<Utc> {
Expand Down
64 changes: 23 additions & 41 deletions crates/context_aware_config/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ use actix_web::{
};
use chrono::Utc;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
use fred::{interfaces::KeysInterface, types::Expiration};
use fred::types::Expiration;
use serde_json::{Map, Value, json};
use service_utils::{
helpers::get_from_env_or_default,
redis::{CONFIG_KEY_SUFFIX, CONFIG_VERSION_KEY_SUFFIX, LAST_MODIFIED_KEY_SUFFIX},
redis::{
CONFIG_KEY_SUFFIX, CONFIG_VERSION_KEY_SUFFIX, LAST_MODIFIED_KEY_SUFFIX,
redis_set_data,
},
};
use service_utils::{
helpers::{fetch_dimensions_info_map, generate_snowflake_id},
Expand Down Expand Up @@ -250,45 +253,24 @@ pub async fn put_config_in_redis(
let last_modified_at_key = format!("{}{LAST_MODIFIED_KEY_SUFFIX}", **schema_name);
let config_version_key = format!("{}{CONFIG_VERSION_KEY_SUFFIX}", **schema_name);

redis_pool
.set::<(), String, String>(
config_key,
parsed_config,
expiration.clone(),
None,
false,
)
.await
.map_err(|e| {
log::warn!("failed to set config in redis: {}", e);
unexpected_error!("failed to set config in redis")
})?;
redis_pool
.set::<(), String, String>(
last_modified_at_key,
config_version.created_at.to_rfc2822(),
expiration.clone(),
None,
false,
)
.await
.map_err(|e| {
log::warn!("failed to set last_modified_key in redis: {}", e);
unexpected_error!("failed to set last_modified_key in redis")
})?;
redis_pool
.set::<(), String, i64>(
config_version_key,
config_version.id,
expiration,
None,
false,
)
.await
.map_err(|e| {
log::warn!("failed to set config_version_key in redis: {}", e);
unexpected_error!("failed to set config_version_key in redis")
})?;
redis_set_data(redis_pool, config_key, parsed_config, expiration.clone()).await?;

redis_set_data(
redis_pool,
last_modified_at_key,
config_version.created_at.to_rfc2822(),
expiration.clone(),
)
.await?;

redis_set_data(
redis_pool,
config_version_key,
config_version.id,
expiration,
)
.await?;

Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ fn get_experiment_config_db(
workspace_context: &WorkspaceContext,
) -> superposition::DieselResult<ExperimentConfig> {
let filters = filters.into_inner();
let dimension_match_strategy = filters.dimension_match_strategy.unwrap_or_default();

let exp_list = {
let mut experiment_list: Vec<Experiment> = experiments::experiments
Expand All @@ -173,7 +174,7 @@ fn get_experiment_config_db(
}

if !dimension_params.is_empty() {
let filter_fn = match filters.dimension_match_strategy.unwrap_or_default() {
let filter_fn = match dimension_match_strategy {
DimensionMatchStrategy::Exact => Experiment::get_satisfied,
DimensionMatchStrategy::Subset => Experiment::filter_by_eval,
};
Expand All @@ -193,7 +194,7 @@ fn get_experiment_config_db(
.load::<ExperimentGroup>(conn)?;

if !dimension_params.is_empty() {
let filter_fn = match filters.dimension_match_strategy.unwrap_or_default() {
let filter_fn = match dimension_match_strategy {
DimensionMatchStrategy::Exact => ExperimentGroup::get_satisfied,
DimensionMatchStrategy::Subset => ExperimentGroup::filter_by_eval,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ use chrono::{DateTime, Utc};
use diesel::{
BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper,
};
use fred::{
prelude::{KeysInterface, RedisPool},
types::Expiration,
};
use fred::{prelude::RedisPool, types::Expiration};
use serde_json::Value;
use service_utils::{
helpers::{generate_snowflake_id, get_from_env_or_default},
redis::{
EXPERIMENT_CONFIG_LAST_MODIFIED_KEY_SUFFIX,
EXPERIMENT_GROUPS_LAST_MODIFIED_KEY_SUFFIX, EXPERIMENT_GROUPS_LIST_KEY_SUFFIX,
redis_set_data,
},
service::types::{AppState, SchemaName, WorkspaceContext},
};
Expand Down Expand Up @@ -506,41 +504,23 @@ pub async fn put_experiment_groups_in_redis(
let key_ttl: i64 = get_from_env_or_default("REDIS_KEY_TTL", 604800);
let expiration = Some(Expiration::EX(key_ttl));

pool.set::<(), String, String>(
redis_set_data(
pool,
config_modified_at_key,
last_modified.clone(),
expiration.clone(),
None,
false,
)
.await
.map_err(|e| {
log::warn!(
"failed to set experiment config last_modified_key in redis: {}",
e
);
unexpected_error!("failed to set experiment config last_modified_key in redis")
})?;
.await?;

pool.set::<(), String, String>(
redis_set_data(
pool,
last_modified_at_key,
last_modified,
expiration.clone(),
None,
false,
)
.await
.map_err(|e| {
log::warn!("failed to set experiment last_modified_key in redis: {}", e);
unexpected_error!("failed to set experiment last_modified_key in redis")
})?;
.await?;

pool.set::<(), String, String>(key, serialized, expiration, None, false)
.await
.map_err(|e| {
log::warn!("Failed to write experiment groups to redis: {}", e);
unexpected_error!("Failed to write experiment groups to redis: {}", e)
})?;
redis_set_data(pool, key, serialized, expiration).await?;

log::debug!("Successfully updated experiment groups cache in Redis");
Ok(())
Expand Down
40 changes: 10 additions & 30 deletions crates/experimentation_platform/src/api/experiments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ use diesel::{
pg::PgConnection,
r2d2::{ConnectionManager, PooledConnection},
};
use fred::{
prelude::{KeysInterface, RedisPool},
types::Expiration,
};
use fred::{prelude::RedisPool, types::Expiration};
use serde_json::{Map, Value};
use service_utils::{
helpers::get_from_env_or_default,
redis::{
EXPERIMENT_CONFIG_LAST_MODIFIED_KEY_SUFFIX, EXPERIMENTS_LAST_MODIFIED_KEY_SUFFIX,
EXPERIMENTS_LIST_KEY_SUFFIX,
EXPERIMENTS_LIST_KEY_SUFFIX, redis_set_data,
},
service::types::{AppState, ExperimentationFlags, SchemaName, WorkspaceContext},
};
Expand Down Expand Up @@ -858,40 +855,23 @@ pub async fn put_experiments_in_redis(
let key_ttl: i64 = get_from_env_or_default("REDIS_KEY_TTL", 604800);
let expiration = Some(Expiration::EX(key_ttl));

pool.set::<(), String, String>(
redis_set_data(
pool,
config_modified_at_key,
last_modified.clone(),
expiration.clone(),
None,
false,
)
.await
.map_err(|e| {
log::warn!(
"failed to set experiment config last_modified_key in redis: {}",
e
);
unexpected_error!("failed to set experiment config last_modified_key in redis")
})?;
.await?;

pool.set::<(), String, String>(
redis_set_data(
pool,
last_modified_at_key,
last_modified,
expiration.clone(),
None,
false,
)
.await
.map_err(|e| {
log::warn!("failed to set experiment last_modified_key in redis: {}", e);
unexpected_error!("failed to set experiment last_modified_key in redis")
})?;
pool.set::<(), String, String>(key, serialized, expiration, None, false)
.await
.map_err(|e| {
log::warn!("Failed to write experiments to redis: {}", e);
unexpected_error!("Failed to write experiments to redis: {}", e)
})?;
.await?;

redis_set_data(pool, key, serialized, expiration).await?;

log::debug!("Successfully updated experiments cache in Redis");
Ok(())
Expand Down
18 changes: 17 additions & 1 deletion crates/service_utils/src/redis.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use fred::{
prelude::{KeysInterface, RedisClient, RedisPool},
types::Expiration,
types::{Expiration, RedisValue},
};
use serde::{Serialize, de::DeserializeOwned};
use superposition_macros::unexpected_error;
use superposition_types::{DBConnection, result as superposition};

use crate::{
Expand Down Expand Up @@ -114,3 +115,18 @@ where
})?;
Ok(value)
}

pub async fn redis_set_data<T: Send + Into<RedisValue>>(
pool: &RedisPool,
key_name: String,
value: T,
expiration: Option<Expiration>,
) -> superposition::Result<()> {
let key = key_name.clone();
pool.set::<(), String, T>(key_name, value, expiration, None, false)
.await
.map_err(|e| {
log::warn!("Failed to set {} in redis: {}", key, e);
unexpected_error!("failed to set {} in redis", key)
})
}
29 changes: 19 additions & 10 deletions crates/superposition_core/src/experiment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ pub struct FfiExperimentGroup {
pub buckets: Buckets,
}

impl Experimental for FfiExperimentGroup {
fn get_condition(&self) -> &Condition {
&self.context
}
}

impl From<ExperimentGroup> for FfiExperimentGroup {
fn from(experiment_group: ExperimentGroup) -> Self {
Self {
Expand Down Expand Up @@ -97,29 +103,32 @@ pub type Experiments = Vec<FfiExperiment>;

pub type ExperimentGroups = Vec<FfiExperimentGroup>;

#[derive(Debug, Clone)]
pub struct ExperimentConfig {
pub experiments: Experiments,
pub experiment_groups: ExperimentGroups,
}

pub fn get_applicable_variants(
dimensions_info: &HashMap<String, DimensionInfo>,
experiments: Experiments,
experiment_groups: &ExperimentGroups,
query_data: &Map<String, Value>,
identifier: &str,
prefix: Option<Vec<String>>,
) -> Result<Vec<String>, String> {
) -> Vec<String> {
let context = evaluate_local_cohorts(dimensions_info, query_data);

let buckets =
get_applicable_buckets_from_group(experiment_groups, &context, identifier);

let experiments: HashMap<String, FfiExperiment> =
get_satisfied_experiments(experiments, &context, prefix)?
get_satisfied_experiments(experiments, &context, prefix)
.into_iter()
.map(|exp| (exp.id.clone(), exp))
.collect();

let applicable_variants =
get_applicable_variants_from_group_response(&experiments, &context, &buckets);

Ok(applicable_variants)
get_applicable_variants_from_group_response(&experiments, &context, &buckets)
}

pub fn get_applicable_buckets_from_group(
Expand Down Expand Up @@ -202,7 +211,7 @@ pub fn get_satisfied_experiments(
mut experiments: Experiments,
context: &Map<String, Value>,
filter_prefixes: Option<Vec<String>>,
) -> Result<Experiments, String> {
) -> Experiments {
if let Some(prefix_list) = filter_prefixes.filter(|p| !p.is_empty()) {
let prefix_list: HashSet<String> = HashSet::from_iter(prefix_list);
experiments = FfiExperiment::filter_keys_by_prefix(experiments, &prefix_list);
Expand All @@ -212,14 +221,14 @@ pub fn get_satisfied_experiments(
experiments = FfiExperiment::get_satisfied(experiments, context);
}

Ok(experiments)
experiments
}

pub fn filter_experiments_by_context(
mut experiments: Experiments,
context: &Map<String, Value>,
filter_prefixes: Option<Vec<String>>,
) -> Result<Experiments, String> {
) -> Experiments {
if let Some(prefix_list) = filter_prefixes.filter(|p| !p.is_empty()) {
let prefix_list: HashSet<String> = HashSet::from_iter(prefix_list);
experiments = FfiExperiment::filter_keys_by_prefix(experiments, &prefix_list);
Expand All @@ -229,5 +238,5 @@ pub fn filter_experiments_by_context(
experiments = FfiExperiment::filter_by_eval(experiments, context);
}

Ok(experiments)
experiments
}
Loading
Loading