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
11 changes: 10 additions & 1 deletion api/api/urls/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

from django.urls import path

from features.feature_states.views import update_flag_v1, update_flag_v2
from features.feature_states.views import (
delete_segment_override,
update_flag_v1,
update_flag_v2,
)

app_name = "experiments"

Expand All @@ -22,4 +26,9 @@
update_flag_v2,
name="update-flag-v2",
),
path(
"environments/<str:environment_key>/delete-segment-override/",
delete_segment_override,
name="delete-segment-override",
),
]
27 changes: 26 additions & 1 deletion api/features/feature_states/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
FlagChangeSetV2,
SegmentOverrideChangeSet,
)
from features.versioning.versioning_service import update_flag, update_flag_v2
from features.versioning.versioning_service import (
delete_segment_override,
update_flag,
update_flag_v2,
)
from segments.models import Segment


Expand Down Expand Up @@ -188,3 +192,24 @@ def change_set_v2(self) -> FlagChangeSetV2:
def save(self, **kwargs: object) -> None:
feature = self.get_feature()
update_flag_v2(self.environment, feature, self.change_set_v2)


class SegmentIdentifierSerializer(serializers.Serializer): # type: ignore[type-arg]
id = serializers.IntegerField(required=True)


class DeleteSegmentOverrideSerializer(BaseFeatureUpdateSerializer):
feature = FeatureIdentifierSerializer(required=True)
segment = SegmentIdentifierSerializer(required=True)

def validate_segment(self, value: dict) -> dict: # type: ignore[type-arg]
if value and value.get("id"):
self.validate_segment_id(value["id"])
return value

def save(self, **kwargs: object) -> None:
feature = self.get_feature()
segment_id = self.validated_data["segment"]["id"]
author = AuthorData.from_request(self.context["request"])

delete_segment_override(self.environment, feature, segment_id, author)
55 changes: 54 additions & 1 deletion api/features/feature_states/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
from environments.models import Environment

from .permissions import EnvironmentUpdateFeatureStatePermission
from .serializers import UpdateFlagSerializer, UpdateFlagV2Serializer
from .serializers import (
DeleteSegmentOverrideSerializer,
UpdateFlagSerializer,
UpdateFlagV2Serializer,
)


def _check_workflow_not_enabled(environment: Environment) -> None:
Expand Down Expand Up @@ -145,3 +149,52 @@ def update_flag_v2(request: Request, environment_key: str) -> Response:
serializer.save()

return Response(status=status.HTTP_204_NO_CONTENT)


@swagger_auto_schema(
method="post",
operation_summary="Delete segment override",
operation_description="""
**EXPERIMENTAL ENDPOINT** - Subject to change without notice.

Deletes a segment override for a feature in the given environment.

**Feature Identification:**
- Use `feature.name` OR `feature.id` (mutually exclusive)
- Feature must belong to the environment's project

**Segment Identification:**
- Use `segment.id` (required)

The segment override must exist for the given feature/environment combination.
Returns 400 if the segment override does not exist.
""",
manual_parameters=[
openapi.Parameter(
"environment_key",
openapi.IN_PATH,
description="The environment API key",
type=openapi.TYPE_STRING,
required=True,
)
],
request_body=DeleteSegmentOverrideSerializer,
responses={
204: openapi.Response(description="Segment override deleted successfully")
},
tags=["experimental"],
) # type: ignore[misc]
@api_view(http_method_names=["POST"])
@permission_classes([IsAuthenticated, EnvironmentUpdateFeatureStatePermission])
def delete_segment_override(request: Request, environment_key: str) -> Response:
environment = Environment.objects.get(api_key=environment_key)
_check_workflow_not_enabled(environment)

serializer = DeleteSegmentOverrideSerializer(
data=request.data,
context={"request": request, "environment": environment},
)
serializer.is_valid(raise_exception=True)
serializer.save()

return Response(status=status.HTTP_204_NO_CONTENT)
62 changes: 61 additions & 1 deletion api/features/versioning/versioning_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from common.core.utils import using_database_replica
from django.db.models import Prefetch, Q, QuerySet
from django.utils import timezone
from rest_framework.exceptions import NotFound

from environments.models import Environment
from features.feature_states.models import FeatureValueType
from features.models import Feature, FeatureState, FeatureStateValue
from features.models import Feature, FeatureSegment, FeatureState, FeatureStateValue
from features.versioning.dataclasses import (
AuthorData,
FlagChangeSet,
FlagChangeSetV2,
)
Expand Down Expand Up @@ -395,6 +397,64 @@ def _update_flag_v2_for_versioning_v1(
_update_segment_priority(segment_state, override.priority)


def delete_segment_override(
environment: "Environment",
feature: "Feature",
segment_id: int,
author: AuthorData,
) -> None:
if environment.use_v2_feature_versioning:
_delete_segment_override_v2(environment, feature, segment_id, author)
else:
_delete_segment_override_v1(environment, feature, segment_id)


def _delete_segment_override_v1(
environment: "Environment",
feature: "Feature",
segment_id: int,
) -> None:
deleted_count, _ = FeatureSegment.objects.filter(
feature=feature,
segment_id=segment_id,
environment=environment,
).delete()
if deleted_count == 0:
raise NotFound(f"Segment override for segment {segment_id} does not exist")


def _delete_segment_override_v2(
environment: "Environment",
feature: "Feature",
segment_id: int,
author: AuthorData,
) -> None:
current_version = get_current_live_environment_feature_version(
environment.id, feature.id
)
if (
not current_version
or not current_version.feature_states.filter(
feature_segment__segment_id=segment_id
).exists()
):
raise NotFound(f"Segment override for segment {segment_id} does not exist")

new_version = EnvironmentFeatureVersion.objects.create(
environment=environment,
feature=feature,
created_by=author.user,
created_by_api_key=author.api_key,
)

segment_feature_state = new_version.feature_states.get(
feature_segment__segment_id=segment_id
)
segment_feature_state.feature_segment.delete()

new_version.publish(published_by=author.user, published_by_api_key=author.api_key)


def get_updated_feature_states_for_version(
version: EnvironmentFeatureVersion,
) -> list[FeatureState]:
Expand Down
Loading
Loading