diff --git a/.generator/schemas/v2/openapi.yaml b/.generator/schemas/v2/openapi.yaml index 2d470c8f2f..c5b00f59f4 100644 --- a/.generator/schemas/v2/openapi.yaml +++ b/.generator/schemas/v2/openapi.yaml @@ -19568,10 +19568,14 @@ components: - CONTAINER - CALLOUTVALUE Condition: - description: Targeting condition details. + description: |- + Targeting condition details. A condition is either an inline + predicate with `operator`, `attribute`, and `value`, or a reference to a + saved filter with `saved_filter_id`. The inline fields are omitted for saved-filter + references. properties: attribute: - description: The user or request attribute to evaluate. + description: The user or request attribute to evaluate. Omitted for saved-filter references. example: "country" type: string created_at: @@ -19586,13 +19590,19 @@ components: type: string operator: $ref: "#/components/schemas/ConditionOperator" + saved_filter_id: + description: The ID of the saved filter referenced by this condition, or null for inline conditions. + example: "550e8400-e29b-41d4-a716-446655440090" + format: uuid + nullable: true + type: string updated_at: description: The timestamp when the condition was last updated. example: "2024-01-01T12:00:00Z" format: date-time type: string value: - description: Values used by the selected operator. + description: Values used by the selected operator. Omitted for saved-filter references. example: ["US", "CA"] items: description: Target value for the selected operator. @@ -19600,9 +19610,6 @@ components: type: array required: - id - - operator - - attribute - - value - created_at - updated_at type: object @@ -19633,25 +19640,32 @@ components: - IS_NULL - EQUALS ConditionRequest: - description: Condition request payload for targeting rules. + description: |- + Condition request payload for targeting rules. A condition is either an inline + predicate with `operator`, `attribute`, and `value`, or a reference to a + saved filter with `saved_filter_id`. The two shapes are mutually exclusive. properties: attribute: - description: The user or request attribute to evaluate. + description: The user or request attribute to evaluate. Required for inline conditions; omit when `saved_filter_id` is set. example: "user_tier" type: string operator: $ref: "#/components/schemas/ConditionOperator" + saved_filter_id: + description: |- + The ID of a saved filter to reference as this condition. Mutually exclusive + with `operator`, `attribute`, and `value`. When set, the saved filter's + targeting rules are evaluated in place of an inline predicate. + example: "550e8400-e29b-41d4-a716-446655440090" + format: uuid + type: string value: - description: Values used by the selected operator. + description: Values used by the selected operator. Required for inline conditions; omit when `saved_filter_id` is set. example: ["premium", "enterprise"] items: description: Target value for the selected operator. type: string type: array - required: - - operator - - attribute - - value type: object ConfigCatCredentials: description: The definition of the `ConfigCatCredentials` object. diff --git a/examples/v2_feature-flags_CreateAllocationsForFeatureFlagInEnvironment.rs b/examples/v2_feature-flags_CreateAllocationsForFeatureFlagInEnvironment.rs index c0014f0737..7f76824e2d 100644 --- a/examples/v2_feature-flags_CreateAllocationsForFeatureFlagInEnvironment.rs +++ b/examples/v2_feature-flags_CreateAllocationsForFeatureFlagInEnvironment.rs @@ -53,11 +53,13 @@ async fn main() { )]) .id(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440020").expect("invalid UUID")) .targeting_rules(vec![TargetingRuleRequest::new(vec![ - ConditionRequest::new( - "user_tier".to_string(), - ConditionOperator::ONE_OF, - vec!["premium".to_string(), "enterprise".to_string()], - ), + ConditionRequest::new() + .attribute("user_tier".to_string()) + .operator(ConditionOperator::ONE_OF) + .saved_filter_id( + Uuid::parse_str("550e8400-e29b-41d4-a716-446655440090").expect("invalid UUID"), + ) + .value(vec!["premium".to_string(), "enterprise".to_string()]), ])]) .variant_weights(vec![VariantWeightRequest::new(50.0) .variant_id( diff --git a/examples/v2_feature-flags_UpdateAllocationsForFeatureFlagInEnvironment.rs b/examples/v2_feature-flags_UpdateAllocationsForFeatureFlagInEnvironment.rs index a150cbc7f7..95dbb14310 100644 --- a/examples/v2_feature-flags_UpdateAllocationsForFeatureFlagInEnvironment.rs +++ b/examples/v2_feature-flags_UpdateAllocationsForFeatureFlagInEnvironment.rs @@ -53,11 +53,13 @@ async fn main() { )]) .id(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440020").expect("invalid UUID")) .targeting_rules(vec![TargetingRuleRequest::new(vec![ - ConditionRequest::new( - "user_tier".to_string(), - ConditionOperator::ONE_OF, - vec!["premium".to_string(), "enterprise".to_string()], - ), + ConditionRequest::new() + .attribute("user_tier".to_string()) + .operator(ConditionOperator::ONE_OF) + .saved_filter_id( + Uuid::parse_str("550e8400-e29b-41d4-a716-446655440090").expect("invalid UUID"), + ) + .value(vec!["premium".to_string(), "enterprise".to_string()]), ])]) .variant_weights(vec![VariantWeightRequest::new(50.0) .variant_id( diff --git a/src/datadogV2/model/model_condition.rs b/src/datadogV2/model/model_condition.rs index db232907d0..de43c667d9 100644 --- a/src/datadogV2/model/model_condition.rs +++ b/src/datadogV2/model/model_condition.rs @@ -6,14 +6,17 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; use std::fmt::{self, Formatter}; -/// Targeting condition details. +/// Targeting condition details. A condition is either an inline +/// predicate with `operator`, `attribute`, and `value`, or a reference to a +/// saved filter with `saved_filter_id`. The inline fields are omitted for saved-filter +/// references. #[non_exhaustive] #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct Condition { - /// The user or request attribute to evaluate. + /// The user or request attribute to evaluate. Omitted for saved-filter references. #[serde(rename = "attribute")] - pub attribute: String, + pub attribute: Option, /// The timestamp when the condition was created. #[serde(rename = "created_at")] pub created_at: chrono::DateTime, @@ -22,13 +25,20 @@ pub struct Condition { pub id: uuid::Uuid, /// The operator used in a targeting condition. #[serde(rename = "operator")] - pub operator: crate::datadogV2::model::ConditionOperator, + pub operator: Option, + /// The ID of the saved filter referenced by this condition, or null for inline conditions. + #[serde( + rename = "saved_filter_id", + default, + with = "::serde_with::rust::double_option" + )] + pub saved_filter_id: Option>, /// The timestamp when the condition was last updated. #[serde(rename = "updated_at")] pub updated_at: chrono::DateTime, - /// Values used by the selected operator. + /// Values used by the selected operator. Omitted for saved-filter references. #[serde(rename = "value")] - pub value: Vec, + pub value: Option>, #[serde(flatten)] pub additional_properties: std::collections::BTreeMap, #[serde(skip)] @@ -38,25 +48,43 @@ pub struct Condition { impl Condition { pub fn new( - attribute: String, created_at: chrono::DateTime, id: uuid::Uuid, - operator: crate::datadogV2::model::ConditionOperator, updated_at: chrono::DateTime, - value: Vec, ) -> Condition { Condition { - attribute, + attribute: None, created_at, id, - operator, + operator: None, + saved_filter_id: None, updated_at, - value, + value: None, additional_properties: std::collections::BTreeMap::new(), _unparsed: false, } } + pub fn attribute(mut self, value: String) -> Self { + self.attribute = Some(value); + self + } + + pub fn operator(mut self, value: crate::datadogV2::model::ConditionOperator) -> Self { + self.operator = Some(value); + self + } + + pub fn saved_filter_id(mut self, value: Option) -> Self { + self.saved_filter_id = Some(value); + self + } + + pub fn value(mut self, value: Vec) -> Self { + self.value = Some(value); + self + } + pub fn additional_properties( mut self, value: std::collections::BTreeMap, @@ -87,6 +115,7 @@ impl<'de> Deserialize<'de> for Condition { let mut created_at: Option> = None; let mut id: Option = None; let mut operator: Option = None; + let mut saved_filter_id: Option> = None; let mut updated_at: Option> = None; let mut value: Option> = None; let mut additional_properties: std::collections::BTreeMap< @@ -98,6 +127,9 @@ impl<'de> Deserialize<'de> for Condition { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { "attribute" => { + if v.is_null() { + continue; + } attribute = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } "created_at" => { @@ -107,6 +139,9 @@ impl<'de> Deserialize<'de> for Condition { id = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } "operator" => { + if v.is_null() { + continue; + } operator = Some(serde_json::from_value(v).map_err(M::Error::custom)?); if let Some(ref _operator) = operator { match _operator { @@ -119,10 +154,17 @@ impl<'de> Deserialize<'de> for Condition { } } } + "saved_filter_id" => { + saved_filter_id = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "updated_at" => { updated_at = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } "value" => { + if v.is_null() { + continue; + } value = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } &_ => { @@ -132,18 +174,16 @@ impl<'de> Deserialize<'de> for Condition { } } } - let attribute = attribute.ok_or_else(|| M::Error::missing_field("attribute"))?; let created_at = created_at.ok_or_else(|| M::Error::missing_field("created_at"))?; let id = id.ok_or_else(|| M::Error::missing_field("id"))?; - let operator = operator.ok_or_else(|| M::Error::missing_field("operator"))?; let updated_at = updated_at.ok_or_else(|| M::Error::missing_field("updated_at"))?; - let value = value.ok_or_else(|| M::Error::missing_field("value"))?; let content = Condition { attribute, created_at, id, operator, + saved_filter_id, updated_at, value, additional_properties, diff --git a/src/datadogV2/model/model_condition_request.rs b/src/datadogV2/model/model_condition_request.rs index c95063b55d..9d63ec7ae3 100644 --- a/src/datadogV2/model/model_condition_request.rs +++ b/src/datadogV2/model/model_condition_request.rs @@ -6,20 +6,27 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; use std::fmt::{self, Formatter}; -/// Condition request payload for targeting rules. +/// Condition request payload for targeting rules. A condition is either an inline +/// predicate with `operator`, `attribute`, and `value`, or a reference to a +/// saved filter with `saved_filter_id`. The two shapes are mutually exclusive. #[non_exhaustive] #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct ConditionRequest { - /// The user or request attribute to evaluate. + /// The user or request attribute to evaluate. Required for inline conditions; omit when `saved_filter_id` is set. #[serde(rename = "attribute")] - pub attribute: String, + pub attribute: Option, /// The operator used in a targeting condition. #[serde(rename = "operator")] - pub operator: crate::datadogV2::model::ConditionOperator, - /// Values used by the selected operator. + pub operator: Option, + /// The ID of a saved filter to reference as this condition. Mutually exclusive + /// with `operator`, `attribute`, and `value`. When set, the saved filter's + /// targeting rules are evaluated in place of an inline predicate. + #[serde(rename = "saved_filter_id")] + pub saved_filter_id: Option, + /// Values used by the selected operator. Required for inline conditions; omit when `saved_filter_id` is set. #[serde(rename = "value")] - pub value: Vec, + pub value: Option>, #[serde(flatten)] pub additional_properties: std::collections::BTreeMap, #[serde(skip)] @@ -28,20 +35,37 @@ pub struct ConditionRequest { } impl ConditionRequest { - pub fn new( - attribute: String, - operator: crate::datadogV2::model::ConditionOperator, - value: Vec, - ) -> ConditionRequest { + pub fn new() -> ConditionRequest { ConditionRequest { - attribute, - operator, - value, + attribute: None, + operator: None, + saved_filter_id: None, + value: None, additional_properties: std::collections::BTreeMap::new(), _unparsed: false, } } + pub fn attribute(mut self, value: String) -> Self { + self.attribute = Some(value); + self + } + + pub fn operator(mut self, value: crate::datadogV2::model::ConditionOperator) -> Self { + self.operator = Some(value); + self + } + + pub fn saved_filter_id(mut self, value: uuid::Uuid) -> Self { + self.saved_filter_id = Some(value); + self + } + + pub fn value(mut self, value: Vec) -> Self { + self.value = Some(value); + self + } + pub fn additional_properties( mut self, value: std::collections::BTreeMap, @@ -51,6 +75,12 @@ impl ConditionRequest { } } +impl Default for ConditionRequest { + fn default() -> Self { + Self::new() + } +} + impl<'de> Deserialize<'de> for ConditionRequest { fn deserialize(deserializer: D) -> Result where @@ -70,6 +100,7 @@ impl<'de> Deserialize<'de> for ConditionRequest { { let mut attribute: Option = None; let mut operator: Option = None; + let mut saved_filter_id: Option = None; let mut value: Option> = None; let mut additional_properties: std::collections::BTreeMap< String, @@ -80,9 +111,15 @@ impl<'de> Deserialize<'de> for ConditionRequest { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { "attribute" => { + if v.is_null() { + continue; + } attribute = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } "operator" => { + if v.is_null() { + continue; + } operator = Some(serde_json::from_value(v).map_err(M::Error::custom)?); if let Some(ref _operator) = operator { match _operator { @@ -95,7 +132,17 @@ impl<'de> Deserialize<'de> for ConditionRequest { } } } + "saved_filter_id" => { + if v.is_null() { + continue; + } + saved_filter_id = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "value" => { + if v.is_null() { + continue; + } value = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } &_ => { @@ -105,13 +152,11 @@ impl<'de> Deserialize<'de> for ConditionRequest { } } } - let attribute = attribute.ok_or_else(|| M::Error::missing_field("attribute"))?; - let operator = operator.ok_or_else(|| M::Error::missing_field("operator"))?; - let value = value.ok_or_else(|| M::Error::missing_field("value"))?; let content = ConditionRequest { attribute, operator, + saved_filter_id, value, additional_properties, _unparsed, diff --git a/tests/scenarios/features/v2/feature_flags.feature b/tests/scenarios/features/v2/feature_flags.feature index df31d246a4..d8238606bf 100644 --- a/tests/scenarios/features/v2/feature_flags.feature +++ b/tests/scenarios/features/v2/feature_flags.feature @@ -90,7 +90,7 @@ Feature: Feature Flags Given new "CreateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} + And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} When the request is sent Then the response status is 202 Accepted - Approval required for this change @@ -99,7 +99,7 @@ Feature: Feature Flags Given new "CreateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} + And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} When the request is sent Then the response status is 400 Bad Request @@ -108,7 +108,7 @@ Feature: Feature Flags Given new "CreateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} + And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} When the request is sent Then the response status is 409 Conflict @@ -117,7 +117,7 @@ Feature: Feature Flags Given new "CreateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} + And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} When the request is sent Then the response status is 201 Created @@ -126,7 +126,7 @@ Feature: Feature Flags Given new "CreateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} + And body with value {"data": {"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}} When the request is sent Then the response status is 404 Not Found @@ -449,7 +449,7 @@ Feature: Feature Flags Given new "UpdateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} + And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} When the request is sent Then the response status is 202 Accepted - Approval required for this change @@ -458,7 +458,7 @@ Feature: Feature Flags Given new "UpdateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} + And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} When the request is sent Then the response status is 400 Bad Request @@ -467,7 +467,7 @@ Feature: Feature Flags Given new "UpdateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} + And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} When the request is sent Then the response status is 409 Conflict @@ -476,7 +476,7 @@ Feature: Feature Flags Given new "UpdateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} + And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} When the request is sent Then the response status is 404 Not Found @@ -485,6 +485,6 @@ Feature: Feature Flags Given new "UpdateAllocationsForFeatureFlagInEnvironment" request And request contains "feature_flag_id" parameter from "REPLACE.ME" And request contains "environment_id" parameter from "REPLACE.ME" - And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} + And body with value {"data": [{"attributes": {"experiment_id": "550e8400-e29b-41d4-a716-446655440030", "exposure_schedule": {"absolute_start_time": "2025-06-13T12:00:00Z", "control_variant_id": "550e8400-e29b-41d4-a716-446655440012", "control_variant_key": "control", "id": "550e8400-e29b-41d4-a716-446655440010", "rollout_options": {"autostart": false, "selection_interval_ms": 3600000, "strategy": "UNIFORM_INTERVALS"}, "rollout_steps": [{"exposure_ratio": 0.5, "grouped_step_index": 1, "id": "550e8400-e29b-41d4-a716-446655440040", "interval_ms": 3600000, "is_pause_record": false}]}, "guardrail_metrics": [{"metric_id": "metric-error-rate", "trigger_action": "PAUSE"}], "id": "550e8400-e29b-41d4-a716-446655440020", "key": "prod-rollout", "name": "Production Rollout", "targeting_rules": [{"conditions": [{"attribute": "user_tier", "operator": "ONE_OF", "saved_filter_id": "550e8400-e29b-41d4-a716-446655440090", "value": ["premium", "enterprise"]}]}], "type": "FEATURE_GATE", "variant_weights": [{"value": 50, "variant_id": "550e8400-e29b-41d4-a716-446655440001", "variant_key": "control"}]}, "type": "allocations"}]} When the request is sent Then the response status is 200 OK