Skip to content

Commit 5b0fde2

Browse files
committed
Merge branch 'main' into chore/remove-heap-mixpanel
2 parents 8902a15 + d06acd3 commit 5b0fde2

19 files changed

Lines changed: 355 additions & 110 deletions

File tree

api/audit/constants.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,13 @@
6767

6868

6969
RELEASE_PIPELINE_CREATED_MESSAGE = "Release Pipeline: %s created"
70+
RELEASE_PIPELINE_CLONED_MESSAGE = "Release Pipeline: %s cloned"
71+
RELEASE_PIPELINE_UPDATED_MESSAGE = "Release Pipeline: %s updated"
7072
RELEASE_PIPELINE_PUBLISHED_MESSAGE = "Release Pipeline: %s published"
71-
# TODO: Add audit log for pipeline update
73+
RELEASE_PIPELINE_UNPUBLISHED_MESSAGE = "Release Pipeline: %s Converted to Draft"
7274
RELEASE_PIPELINE_DELETED_MESSAGE = "Release Pipeline: %s deleted"
7375
RELEASE_PIPELINE_FEATURE_ADDED_MESSAGE = "Feature: %s added to Release Pipeline: %s"
76+
RELEASE_PIPELINE_FEATURE_REMOVED_MESSAGE = (
77+
"Feature: %s removed from Release Pipeline: %s"
78+
)
7479
FEATURE_STATE_UPDATED_BY_RELEASE_PIPELINE_MESSAGE = "Flag state / Remote config updated for feature: %s by Release pipeline: %s (stage: %s)"

api/core/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
FLAGSMITH_SIGNATURE_HEADER = "X-Flagsmith-Signature"
99

1010
FLAGSMITH_UPDATED_AT_HEADER = "X-Flagsmith-Document-Updated-At"
11+
SDK_ENVIRONMENT_KEY_HEADER = "X_ENVIRONMENT_KEY"

api/environments/dynamodb/wrappers/identity_wrapper.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
from boto3.dynamodb.conditions import Attr, Key
88
from django.conf import settings
99
from django.core.exceptions import ObjectDoesNotExist
10+
from flag_engine.context.mappers import map_environment_identity_to_context
1011
from flag_engine.environments.models import EnvironmentModel
1112
from flag_engine.identities.models import IdentityModel
12-
from flag_engine.segments.evaluator import get_identity_segments
13+
from flag_engine.segments.evaluator import get_context_segments
1314
from rest_framework.exceptions import NotFound
1415

1516
from edge_api.identities.search import EdgeIdentitySearchData
@@ -189,7 +190,12 @@ def get_segment_ids(
189190
environment = EnvironmentModel.model_validate(
190191
environment_wrapper.get_item(identity.environment_api_key)
191192
)
192-
segments = get_identity_segments(environment, identity)
193+
context = map_environment_identity_to_context(
194+
environment=environment,
195+
identity=identity,
196+
override_traits=None,
197+
)
198+
segments = get_context_segments(context, environment.project.segments)
193199
return [segment.id for segment in segments]
194200

195201
return []

api/environments/identities/models.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from django.db import models
55
from django.db.models import Prefetch, Q
6-
from flag_engine.segments.evaluator import evaluate_identity_in_segment
6+
from flag_engine.context.mappers import map_environment_identity_to_context
7+
from flag_engine.segments.evaluator import is_context_in_segment
78

89
from environments.identities.managers import IdentityManager
910
from environments.identities.traits.models import Trait
@@ -170,10 +171,15 @@ def get_segments(
170171
for segment in all_segments:
171172
engine_segment = map_segment_to_engine(segment)
172173

173-
if evaluate_identity_in_segment(
174+
context = map_environment_identity_to_context(
175+
environment=self.environment,
174176
identity=engine_identity,
175-
segment=engine_segment,
176177
override_traits=engine_traits,
178+
)
179+
180+
if is_context_in_segment(
181+
context=context,
182+
segment=engine_segment,
177183
):
178184
matching_segments.append(segment)
179185

api/environments/identities/views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
from django.utils import timezone
1111
from django.utils.decorators import method_decorator
1212
from django.views.decorators.cache import cache_page
13+
from django.views.decorators.vary import vary_on_headers
1314
from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped]
1415
from rest_framework import status, viewsets
1516
from rest_framework.permissions import IsAuthenticated
1617
from rest_framework.response import Response
1718

1819
from app.pagination import CustomPagination
19-
from core.constants import FLAGSMITH_UPDATED_AT_HEADER
20+
from core.constants import FLAGSMITH_UPDATED_AT_HEADER, SDK_ENVIRONMENT_KEY_HEADER
2021
from core.request_origin import RequestOrigin
2122
from edge_api.identities.tasks import forward_identity_request
2223
from environments.identities.models import Identity
@@ -161,6 +162,7 @@ class SDKIdentities(SDKAPIView):
161162
query_serializer=SDKIdentitiesQuerySerializer(),
162163
operation_id="identify_user",
163164
)
165+
@method_decorator(vary_on_headers(SDK_ENVIRONMENT_KEY_HEADER))
164166
@method_decorator(
165167
cache_page(
166168
timeout=settings.GET_IDENTITIES_ENDPOINT_CACHE_SECONDS,

api/features/release_pipelines/core/models.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
from audit.constants import (
88
RELEASE_PIPELINE_CREATED_MESSAGE,
99
RELEASE_PIPELINE_DELETED_MESSAGE,
10-
RELEASE_PIPELINE_PUBLISHED_MESSAGE,
1110
)
12-
from audit.models import AuditLog
1311
from audit.related_object_type import RelatedObjectType
1412
from core.models import (
1513
SoftDeleteExportableModel,
1614
abstract_base_auditable_model_factory,
1715
)
1816
from features.release_pipelines.core.constants import MAX_PIPELINE_STAGES
1917
from features.release_pipelines.core.exceptions import InvalidPipelineStateError
18+
from features.versioning.models import EnvironmentFeatureVersion
2019
from projects.models import Project
2120
from users.models import FFAdminUser
2221

@@ -72,7 +71,13 @@ def publish(self, published_by: FFAdminUser) -> None:
7271
self.published_at = timezone.now()
7372
self.published_by = published_by
7473
self.save()
75-
self._create_pipeline_published_audit_log()
74+
75+
def unpublish(self, unpublished_by: FFAdminUser) -> None:
76+
if self.published_at is None:
77+
raise InvalidPipelineStateError("Pipeline is not published.")
78+
self.published_at = None
79+
self.published_by = None
80+
self.save()
7681

7782
def get_first_stage(self) -> "PipelineStage | None":
7883
return self.stages.order_by("order").first()
@@ -90,18 +95,15 @@ def get_delete_log_message(
9095
) -> typing.Optional[str]:
9196
return RELEASE_PIPELINE_DELETED_MESSAGE % self.name
9297

98+
def has_feature_in_flight(self) -> bool:
99+
has_feature_in_flight: bool = EnvironmentFeatureVersion.objects.filter(
100+
published_at__isnull=True, pipeline_stage__in=self.stages.all()
101+
).exists()
102+
return has_feature_in_flight
103+
93104
def _get_project(self) -> Project:
94105
return self.project
95106

96-
def _create_pipeline_published_audit_log(self) -> None:
97-
AuditLog.objects.create(
98-
related_object_id=self.id,
99-
related_object_type=RelatedObjectType.RELEASE_PIPELINE.name,
100-
project=self._get_project(),
101-
log=RELEASE_PIPELINE_PUBLISHED_MESSAGE % self.name,
102-
author=self.published_by,
103-
)
104-
105107

106108
class PipelineStage(models.Model):
107109
name = models.CharField(max_length=255)

api/features/views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.utils import timezone
1111
from django.utils.decorators import method_decorator
1212
from django.views.decorators.cache import cache_page
13+
from django.views.decorators.vary import vary_on_headers
1314
from drf_yasg import openapi # type: ignore[import-untyped]
1415
from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped]
1516
from rest_framework import mixins, serializers, status, viewsets
@@ -24,7 +25,7 @@
2425
from app.pagination import CustomPagination
2526
from app_analytics.analytics_db_service import get_feature_evaluation_data
2627
from app_analytics.influxdb_wrapper import get_multiple_event_list_for_feature
27-
from core.constants import FLAGSMITH_UPDATED_AT_HEADER
28+
from core.constants import FLAGSMITH_UPDATED_AT_HEADER, SDK_ENVIRONMENT_KEY_HEADER
2829
from core.request_origin import RequestOrigin
2930
from environments.authentication import EnvironmentKeyAuthentication
3031
from environments.identities.models import Identity
@@ -782,6 +783,7 @@ class SDKFeatureStates(GenericAPIView): # type: ignore[type-arg]
782783
query_serializer=SDKFeatureStatesQuerySerializer(),
783784
responses={200: FeatureStateSerializerFull(many=True)},
784785
)
786+
@method_decorator(vary_on_headers(SDK_ENVIRONMENT_KEY_HEADER))
785787
@method_decorator(
786788
cache_page(
787789
timeout=settings.GET_FLAGS_ENDPOINT_CACHE_SECONDS,

api/integrations/webhook/serializers.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import typing
22

33
from django.db.models import Q
4-
from flag_engine.segments.evaluator import evaluate_identity_in_segment
4+
from flag_engine.segments.evaluator import is_context_in_segment
55
from rest_framework import serializers
66

77
from features.serializers import FeatureStateSerializerFull
88
from integrations.common.serializers import (
99
BaseEnvironmentIntegrationModelSerializer,
1010
)
1111
from segments.models import Segment
12-
from util.mappers.engine import map_identity_to_engine, map_segment_to_engine
12+
from util.mappers.engine import (
13+
map_engine_identity_to_context,
14+
map_identity_to_engine,
15+
map_segment_to_engine,
16+
)
1317

1418
from .models import WebhookConfiguration
1519

@@ -33,8 +37,9 @@ def get_member(self, obj: Segment) -> bool:
3337
with_overrides=False,
3438
)
3539
engine_segment = map_segment_to_engine(obj)
36-
return evaluate_identity_in_segment(
37-
identity=engine_identity,
40+
context = map_engine_identity_to_context(engine_identity)
41+
return is_context_in_segment(
42+
context=context,
3843
segment=engine_segment,
3944
)
4045

api/poetry.lock

Lines changed: 8 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ environs = "^14.1.1"
133133
django-lifecycle = "~1.2.4"
134134
drf-writable-nested = "~0.6.2"
135135
django-filter = "~2.4.0"
136-
flagsmith-flag-engine = "^5.3.0"
136+
flagsmith-flag-engine = "^5.4.3"
137137
boto3 = "~1.35.95"
138138
slack-sdk = "~3.9.0"
139139
asgiref = "~3.8.1"
@@ -207,7 +207,7 @@ flagsmith-split-testing = { git = "https://github.com/flagsmith/flagsmith-split-
207207
optional = true
208208

209209
[tool.poetry.group.release-pipelines.dependencies]
210-
flagsmith-private = { git = "https://github.com/Flagsmith/flagsmith-private/", rev = "v0.1.0" }
210+
flagsmith-private = { git = "https://github.com/Flagsmith/flagsmith-private/", rev = "v0.2.1" }
211211

212212

213213
[tool.poetry.group.dev.dependencies]

0 commit comments

Comments
 (0)