Skip to content

Commit 913daeb

Browse files
fix(versioning): include multivariate values in v2 webhook payloads (#7496)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 33f78a9 commit 913daeb

3 files changed

Lines changed: 148 additions & 0 deletions

File tree

api/features/versioning/schemas.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,30 @@ class _UserSchema(Schema):
1919
class _FeatureStateSchema(Schema):
2020
enabled = fields.Bool()
2121
value = fields.Method(serialize="get_feature_state_value")
22+
multivariate_feature_state_values = fields.Method(
23+
serialize="get_multivariate_feature_state_values"
24+
)
2225

2326
def get_feature_state_value(self, obj: FeatureState) -> Any:
2427
return obj.get_feature_state_value()
2528

29+
def get_multivariate_feature_state_values(
30+
self, obj: FeatureState
31+
) -> list[dict[str, Any]]:
32+
return [
33+
{
34+
"id": mv.id,
35+
"multivariate_feature_option": {
36+
"id": mv.multivariate_feature_option_id,
37+
"value": mv.multivariate_feature_option.value,
38+
},
39+
"percentage_allocation": mv.percentage_allocation,
40+
}
41+
for mv in obj.multivariate_feature_state_values.select_related(
42+
"multivariate_feature_option"
43+
).all()
44+
]
45+
2646

2747
class EnvironmentFeatureVersionWebhookDataSerializer(Schema):
2848
uuid = fields.UUID()

api/features/versioning/tasks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
if typing.TYPE_CHECKING:
3434
from environments.models import Environment
35+
from features.multivariate.models import MultivariateFeatureStateValue
3536

3637

3738
logger = logging.getLogger(__name__)
@@ -135,6 +136,16 @@ def _create_initial_feature_versions(environment: "Environment"): # type: ignor
135136
)
136137

137138

139+
def _get_multivariate_values(
140+
feature_state: FeatureState,
141+
) -> list["MultivariateFeatureStateValue"]:
142+
return list(
143+
feature_state.multivariate_feature_state_values.select_related(
144+
"multivariate_feature_option"
145+
).all()
146+
)
147+
148+
138149
def _trigger_feature_state_webhooks_for_version(
139150
environment_feature_version: EnvironmentFeatureVersion,
140151
) -> None:
@@ -182,6 +193,7 @@ def _trigger_feature_state_webhooks_for_version(
182193
identity_id=feature_state.identity_id,
183194
identity_identifier=getattr(feature_state.identity, "identifier", None),
184195
feature_segment=feature_state.feature_segment,
196+
multivariate_feature_state_values=_get_multivariate_values(feature_state),
185197
)
186198

187199
# Build webhook data
@@ -210,6 +222,7 @@ def _trigger_feature_state_webhooks_for_version(
210222
identity_id=previous_fs.identity_id,
211223
identity_identifier=getattr(previous_fs.identity, "identifier", None),
212224
feature_segment=previous_fs.feature_segment,
225+
multivariate_feature_state_values=_get_multivariate_values(previous_fs),
213226
)
214227
data["previous_state"] = previous_state
215228

api/tests/unit/features/versioning/test_unit_versioning_tasks.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ def test_trigger_update_version_webhooks__version_with_changes__triggers_flag_up
216216
flag_updated_env_body["data"]["previous_state"]["feature_state_value"]
217217
== v1_fs.get_feature_state_value()
218218
)
219+
assert (
220+
flag_updated_env_body["data"]["new_state"]["multivariate_feature_state_values"]
221+
== []
222+
)
223+
assert (
224+
flag_updated_env_body["data"]["previous_state"][
225+
"multivariate_feature_state_values"
226+
]
227+
== []
228+
)
219229
assert flag_updated_env_body["data"]["changed_by"] == staff_user.email
220230
assert "timestamp" in flag_updated_env_body["data"]
221231

@@ -235,6 +245,7 @@ def test_trigger_update_version_webhooks__version_with_changes__triggers_flag_up
235245
{
236246
"enabled": v2_fs.enabled,
237247
"value": v2_fs.get_feature_state_value(),
248+
"multivariate_feature_state_values": [],
238249
}
239250
],
240251
},
@@ -284,12 +295,116 @@ def test_trigger_update_version_webhooks__version_without_changes__triggers_only
284295
{
285296
"enabled": v2.feature_states.first().enabled,
286297
"value": v2.feature_states.first().get_feature_state_value(),
298+
"multivariate_feature_state_values": [],
287299
}
288300
],
289301
},
290302
}
291303

292304

305+
@responses.activate
306+
def test_trigger_update_version_webhooks__multivariate_feature__includes_mv_values_in_payloads(
307+
multivariate_feature: Feature,
308+
environment_v2_versioning: Environment,
309+
staff_user: FFAdminUser,
310+
) -> None:
311+
# Given
312+
v1 = EnvironmentFeatureVersion.objects.get(
313+
feature=multivariate_feature, environment=environment_v2_versioning
314+
)
315+
v1_fs = v1.feature_states.first()
316+
317+
# Bump one option's allocation in v2 so we count as a change and so
318+
# previous/new mv values differ.
319+
v2 = EnvironmentFeatureVersion.objects.create(
320+
environment=environment_v2_versioning, feature=multivariate_feature
321+
)
322+
v2_fs = v2.feature_states.first()
323+
v2_mv_values = list(
324+
v2_fs.multivariate_feature_state_values.select_related(
325+
"multivariate_feature_option"
326+
).order_by("multivariate_feature_option_id")
327+
)
328+
bumped_mv_value = v2_mv_values[0]
329+
original_allocation = bumped_mv_value.percentage_allocation
330+
bumped_mv_value.percentage_allocation = original_allocation + 5
331+
bumped_mv_value.save()
332+
v2.publish(published_by=staff_user)
333+
334+
environment_webhook_url = "https://example.com/env-webhook/"
335+
Webhook.objects.create(
336+
environment=environment_v2_versioning,
337+
url=environment_webhook_url,
338+
enabled=True,
339+
)
340+
responses.post(url=environment_webhook_url, status=200)
341+
342+
# When
343+
trigger_update_version_webhooks(str(v2.uuid))
344+
345+
# Then
346+
flag_updated_body = json.loads(responses.calls[0].request.body) # type: ignore[union-attr]
347+
assert flag_updated_body["event_type"] == WebhookEventType.FLAG_UPDATED.name
348+
349+
expected_new_mv_payload = [
350+
{
351+
"id": mv.id,
352+
"multivariate_feature_option": {
353+
"id": mv.multivariate_feature_option_id,
354+
"value": mv.multivariate_feature_option.value,
355+
},
356+
"percentage_allocation": mv.percentage_allocation,
357+
}
358+
for mv in v2_fs.multivariate_feature_state_values.select_related(
359+
"multivariate_feature_option"
360+
).order_by("multivariate_feature_option_id")
361+
]
362+
assert (
363+
sorted(
364+
flag_updated_body["data"]["new_state"]["multivariate_feature_state_values"],
365+
key=lambda mv: mv["multivariate_feature_option"]["id"],
366+
)
367+
== expected_new_mv_payload
368+
)
369+
370+
expected_previous_mv_payload = [
371+
{
372+
"id": mv.id,
373+
"multivariate_feature_option": {
374+
"id": mv.multivariate_feature_option_id,
375+
"value": mv.multivariate_feature_option.value,
376+
},
377+
"percentage_allocation": mv.percentage_allocation,
378+
}
379+
for mv in v1_fs.multivariate_feature_state_values.select_related(
380+
"multivariate_feature_option"
381+
).order_by("multivariate_feature_option_id")
382+
]
383+
assert (
384+
sorted(
385+
flag_updated_body["data"]["previous_state"][
386+
"multivariate_feature_state_values"
387+
],
388+
key=lambda mv: mv["multivariate_feature_option"]["id"],
389+
)
390+
== expected_previous_mv_payload
391+
)
392+
393+
# NEW_VERSION_PUBLISHED summary event should also carry mv values.
394+
new_version_body = json.loads(responses.calls[1].request.body) # type: ignore[union-attr]
395+
assert new_version_body["event_type"] == WebhookEventType.NEW_VERSION_PUBLISHED.name
396+
summary_mv_payload = new_version_body["data"]["feature_states"][0][
397+
"multivariate_feature_state_values"
398+
]
399+
assert (
400+
sorted(
401+
summary_mv_payload,
402+
key=lambda mv: mv["multivariate_feature_option"]["id"],
403+
)
404+
== expected_new_mv_payload
405+
)
406+
407+
293408
def test_enable_v2_versioning__scheduled_changes_exist__converts_published_scheduled_changes(
294409
environment: Environment, staff_user: FFAdminUser, feature: Feature
295410
) -> None:

0 commit comments

Comments
 (0)