Skip to content
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

fix: Add multivariate values when cloning identities #3894

Merged
merged 4 commits into from
May 13, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions api/edge_api/identities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,6 @@ def clone_flag_states_from(self, source_identity: "EdgeIdentity") -> None:
feature=feature_in_source.feature,
feature_state_value=feature_in_source.feature_state_value,
enabled=feature_in_source.enabled,
multivariate_feature_state_values=feature_in_source.multivariate_feature_state_values,
)
self.add_feature_override(feature_state_target)
9 changes: 7 additions & 2 deletions api/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -981,12 +981,17 @@ def copy_identity_feature_states(
source_feature_id
].enabled

# Copy feature state value from source feature_state
if source_feature_state.feature.type == MULTIVARIATE:
mv_values = [
mv_value.clone(feature_state=target_feature_state, persist=False)
for mv_value in source_feature_state.multivariate_feature_state_values.all()
]
MultivariateFeatureStateValue.objects.bulk_create(mv_values)

target_feature_state.feature_state_value.copy_from(
source_feature_state.feature_state_value
)

# Save changes to target feature_state
target_feature_state.save()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
import pytest
from core.constants import BOOLEAN, INTEGER, STRING
from django.urls import reverse
from flag_engine.features.models import FeatureModel, FeatureStateModel
from flag_engine.features.models import (
FeatureModel,
FeatureStateModel,
MultivariateFeatureOptionModel,
MultivariateFeatureStateValueList,
MultivariateFeatureStateValueModel,
)
from mypy_boto3_dynamodb.service_resource import Table
from pytest_lazyfixture import lazy_fixture
from rest_framework import status
Expand All @@ -18,6 +24,7 @@
IdentityModel,
)
from features.models import Feature
from features.multivariate.models import MultivariateFeatureOption
from projects.models import Project
from tests.integration.helpers import create_mv_option_with_api
from util.mappers.engine import map_feature_to_engine
Expand Down Expand Up @@ -1082,9 +1089,24 @@ def features_for_identity_clone_flag_states_from(
project
)

mv_feature = Feature.objects.create(
type="MULTIVARIATE",
name="mv_feature",
initial_value="foo",
project=project,
)

mv_variant_1 = MultivariateFeatureOption.objects.create(
feature=mv_feature,
default_percentage_allocation=0,
type=STRING,
string_value="bar",
)

feature_model_1: FeatureModel = map_feature_to_engine(feature=feature_1)
feature_model_2: FeatureModel = map_feature_to_engine(feature=feature_2)
feature_model_3: FeatureModel = map_feature_to_engine(feature=feature_3)
mv_feature_model: FeatureModel = map_feature_to_engine(feature=mv_feature)

source_identity: EdgeIdentity = create_identity(identifier="source_identity")
target_identity: EdgeIdentity = create_identity(identifier="target_identity")
Expand All @@ -1105,6 +1127,21 @@ def features_for_identity_clone_flag_states_from(
feature_state_value=source_feature_state_2_value,
)

source_mv_feature_state = FeatureStateModel(
feature=mv_feature_model,
environment_id=dynamo_enabled_environment,
enabled=True,
multivariate_feature_state_values=MultivariateFeatureStateValueList(),
)
source_mv_feature_state.multivariate_feature_state_values.append(
MultivariateFeatureStateValueModel(
multivariate_feature_option=MultivariateFeatureOptionModel(
value=mv_variant_1.value
),
percentage_allocation=100,
)
)

target_feature_state_2_value = "Target Identity value for feature 2"
target_feature_state_2 = FeatureStateModel(
feature=feature_model_2,
Expand All @@ -1122,6 +1159,7 @@ def features_for_identity_clone_flag_states_from(
# Add feature states for features 1 and 2 to source identity
source_identity.add_feature_override(feature_state=source_feature_state_1)
source_identity.add_feature_override(feature_state=source_feature_state_2)
source_identity.add_feature_override(feature_state=source_mv_feature_state)

# Add feature states for features 2 and 3 to target identity.
target_identity.add_feature_override(feature_state=target_feature_state_2)
Expand Down Expand Up @@ -1156,7 +1194,7 @@ def features_for_identity_clone_flag_states_from(
response = clone_identity_feature_states_response.json()

# Target identity contains only the 2 cloned overridden features states and 1 environment feature state
assert len(response) == 3
assert len(response) == 4

assert response[0]["feature"]["id"] == feature_1.id
assert response[0]["enabled"] == source_feature_state_1.enabled
Expand All @@ -1172,3 +1210,14 @@ def features_for_identity_clone_flag_states_from(
assert response[2]["enabled"] == feature_3.default_enabled
assert response[2]["feature_state_value"] == feature_3.initial_value
assert response[2]["overridden_by"] is None

assert response[3]["feature"]["id"] == mv_feature.id
assert response[3]["enabled"] == source_mv_feature_state.enabled
assert response[3]["feature_state_value"] == mv_variant_1.value
assert (
response[3]["multivariate_feature_state_values"][0][
"multivariate_feature_option"
]["value"]
== mv_variant_1.value
)
assert response[3]["overridden_by"] == "IDENTITY"
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json

import pytest
from core.constants import STRING
from django.test import Client
from django.urls import reverse
from rest_framework import status
Expand All @@ -12,6 +13,10 @@
VIEW_ENVIRONMENT,
)
from features.models import Feature, FeatureState, FeatureStateValue
from features.multivariate.models import (
MultivariateFeatureOption,
MultivariateFeatureStateValue,
)
from projects.models import Project
from tests.unit.environments.helpers import get_environment_user_client

Expand Down Expand Up @@ -136,6 +141,20 @@ def features_for_identity_clone_flag_states_from(
project
)

mv_feature = Feature.objects.create(
type="MULTIVARIATE",
name="mv_feature",
initial_value="foo",
project=project,
)

mv_variant_1 = MultivariateFeatureOption.objects.create(
feature=mv_feature,
default_percentage_allocation=0,
type=STRING,
string_value="bar",
)

source_identity: Identity = Identity.objects.create(
identifier="source_identity", environment=environment
)
Expand Down Expand Up @@ -165,6 +184,18 @@ def features_for_identity_clone_flag_states_from(
string_value=source_feature_state_2_value
)

source_mv_feature_state: FeatureState = FeatureState.objects.create(
feature=mv_feature,
environment=environment,
identity=source_identity,
enabled=True,
)
MultivariateFeatureStateValue.objects.create(
feature_state=source_mv_feature_state,
multivariate_feature_option=mv_variant_1,
percentage_allocation=100,
)

target_feature_state_2: FeatureState = FeatureState.objects.create(
feature=feature_2,
environment=environment,
Expand Down Expand Up @@ -201,7 +232,7 @@ def features_for_identity_clone_flag_states_from(
response = clone_identity_feature_states_response.json()

# Target identity contains only the 2 cloned overridden features states and 1 environment feature state
assert len(response) == 3
assert len(response) == 4

# Assert cloned data is correct
assert response[0]["feature"]["id"] == feature_1.id
Expand All @@ -219,6 +250,17 @@ def features_for_identity_clone_flag_states_from(
assert response[2]["feature_state_value"] == feature_3.initial_value
assert response[2]["overridden_by"] is None

assert response[3]["feature"]["id"] == mv_feature.id
assert response[3]["enabled"] == source_mv_feature_state.enabled
assert response[3]["feature_state_value"] == mv_variant_1.value
assert (
response[3]["multivariate_feature_state_values"][0][
"multivariate_feature_option"
]["value"]
== mv_variant_1.value
)
assert response[3]["overridden_by"] == "IDENTITY"

# Target identity feature 3 override has been removed
assert not FeatureState.objects.filter(
feature=feature_3,
Expand Down