Skip to content

Commit

Permalink
Ensure environments are written to dynamo db when projects are updated (
Browse files Browse the repository at this point in the history
Flagsmith#1946)

* Write environments to dynamo when project changes

* Fix tests

* Add unit test
  • Loading branch information
matthewelwell authored Feb 23, 2023
1 parent c50d430 commit 312601b
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 27 deletions.
8 changes: 2 additions & 6 deletions api/audit/signals.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging

from django.db.models import Q
from django.db.models.signals import post_save
from django.dispatch import receiver

Expand Down Expand Up @@ -123,12 +122,9 @@ def send_audit_log_event_to_dynatrace(sender, instance, **kwargs):
@receiver(post_save, sender=AuditLog)
@handle_skipped_signals
def send_environments_to_dynamodb(sender, instance, **kwargs):
environments_filter = (
Q(id=instance.environment_id)
if instance.environment_id
else Q(project=instance.project)
Environment.write_environments_to_dynamodb(
environment_id=instance.environment_id, project_id=instance.project_id
)
Environment.write_environments_to_dynamodb(environments_filter)


@receiver(post_save, sender=AuditLog)
Expand Down
12 changes: 8 additions & 4 deletions api/environments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
from environments.exceptions import EnvironmentHeaderNotPresentError
from environments.managers import EnvironmentManager
from features.models import Feature, FeatureSegment, FeatureState
from projects.models import Project
from segments.models import Segment
from webhooks.models import AbstractBaseWebhookModel

Expand All @@ -66,7 +65,7 @@ class Environment(
created_date = models.DateTimeField("DateCreated", auto_now_add=True)
description = models.TextField(null=True, blank=True, max_length=20000)
project = models.ForeignKey(
Project,
"projects.Project",
related_name="environments",
help_text=_(
"Changing the project selected will remove all previous Feature States for the "
Expand Down Expand Up @@ -200,10 +199,15 @@ def get_from_cache(cls, api_key):
logger.info("Environment with api_key %s does not exist" % api_key)

@classmethod
def write_environments_to_dynamodb(cls, environments_filter: Q) -> None:
def write_environments_to_dynamodb(
cls, environment_id: int = None, project_id: int = None
) -> None:
# use a list to make sure the entire qs is evaluated up front
environments_filter = (
Q(id=environment_id) if environment_id else Q(project_id=project_id)
)
environments = list(
Environment.objects.filter_for_document_builder(environments_filter)
cls.objects.filter_for_document_builder(environments_filter)
)

if not environments:
Expand Down
3 changes: 1 addition & 2 deletions api/integrations/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from core.models import AbstractBaseExportableModel
from django.db import models
from django.db.models import Q
from django_lifecycle import (
AFTER_DELETE,
AFTER_SAVE,
Expand Down Expand Up @@ -38,7 +37,7 @@ def write_environment_to_dynamodb(self):
self.__class__.__name__,
)
return
Environment.write_environments_to_dynamodb(Q(id=self.environment_id))
Environment.write_environments_to_dynamodb(environment_id=self.environment_id)

@hook(AFTER_UPDATE)
def clear_environment_cache(self):
Expand Down
3 changes: 1 addition & 2 deletions api/integrations/webhook/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from django.db import models
from django.db.models import Q
from django_lifecycle import (
AFTER_DELETE,
AFTER_SAVE,
Expand All @@ -19,4 +18,4 @@ class WebhookConfiguration(AbstractBaseWebhookModel, LifecycleModelMixin):
@hook(AFTER_SAVE)
@hook(AFTER_DELETE)
def write_environment_to_dynamodb(self):
Environment.write_environments_to_dynamodb(Q(id=self.environment_id))
Environment.write_environments_to_dynamodb(environment_id=self.environment_id)
6 changes: 6 additions & 0 deletions api/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.utils import timezone
from django_lifecycle import (
AFTER_SAVE,
AFTER_UPDATE,
BEFORE_CREATE,
LifecycleModelMixin,
hook,
Expand All @@ -22,6 +23,7 @@
PermissionModel,
)
from projects.managers import ProjectManager
from projects.tasks import write_environments_to_dynamodb

project_segments_cache = caches[settings.PROJECT_SEGMENTS_CACHE_LOCATION]
environment_cache = caches[settings.ENVIRONMENT_CACHE_NAME]
Expand Down Expand Up @@ -100,6 +102,10 @@ def clear_environments_cache(self):
list(self.environments.values_list("api_key", flat=True))
)

@hook(AFTER_UPDATE)
def write_to_dynamo(self):
write_environments_to_dynamodb.delay(kwargs={"project_id": self.id})

@property
def is_edge_project_by_default(self) -> bool:
return bool(
Expand Down
8 changes: 8 additions & 0 deletions api/projects/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from task_processor.decorators import register_task_handler


@register_task_handler()
def write_environments_to_dynamodb(project_id: int):
from environments.models import Environment

Environment.write_environments_to_dynamodb(project_id=project_id)
9 changes: 4 additions & 5 deletions api/tests/unit/audit/test_unit_audit_signals.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from django.db.models import Q

from audit.models import AuditLog
from audit.signals import (
send_environments_to_dynamodb,
Expand All @@ -19,7 +17,7 @@ def test_send_env_to_dynamodb_from_audit_log_with_environment(

# Then
mock_environment_model_class.write_environments_to_dynamodb.assert_called_once_with(
Q(id=dynamo_enabled_project_environment_one.id)
environment_id=dynamo_enabled_project_environment_one.id, project_id=None
)


Expand Down Expand Up @@ -56,7 +54,7 @@ def test_send_env_to_dynamodb_from_audit_log_with_project(

# Then
mock_environment_model_class.write_environments_to_dynamodb.assert_called_once_with(
Q(project=dynamo_enabled_project)
project_id=dynamo_enabled_project.id, environment_id=None
)


Expand Down Expand Up @@ -96,7 +94,8 @@ def test_send_env_to_dynamodb_from_audit_log_with_environment_and_project(

# Then
mock_environment_model_class.write_environments_to_dynamodb.assert_called_once_with(
Q(id=dynamo_enabled_project_environment_one.id)
environment_id=dynamo_enabled_project_environment_one.id,
project_id=dynamo_enabled_project.id,
)


Expand Down
7 changes: 3 additions & 4 deletions api/tests/unit/environments/test_unit_environments_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import pytest
from core.request_origin import RequestOrigin
from django.db.models import Q
from flag_engine.api.document_builders import build_environment_document
from pytest_django.asserts import assertQuerysetEqual as assert_queryset_equal

Expand Down Expand Up @@ -42,7 +41,7 @@ def test_write_environments_to_dynamodb_with_environment(

# When
Environment.write_environments_to_dynamodb(
Q(id=dynamo_enabled_project_environment_one.id)
environment_id=dynamo_enabled_project_environment_one.id
)

# Then
Expand All @@ -65,7 +64,7 @@ def test_write_environments_to_dynamodb_project(
mock_dynamo_env_wrapper.reset_mock()

# When
Environment.write_environments_to_dynamodb(Q(project=dynamo_enabled_project))
Environment.write_environments_to_dynamodb(project_id=dynamo_enabled_project.id)

# Then
args, kwargs = mock_dynamo_env_wrapper.write_environments.call_args
Expand All @@ -86,7 +85,7 @@ def test_write_environments_to_dynamodb_with_environment_and_project(

# When
Environment.write_environments_to_dynamodb(
Q(id=dynamo_enabled_project_environment_one.id)
environment_id=dynamo_enabled_project_environment_one.id
)

# Then
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from django.db.models import Q

from integrations.amplitude.models import AmplitudeConfiguration


Expand All @@ -22,7 +20,7 @@ def test_amplitude_configuration_save_writes_environment_to_dynamodb(

# Then
mock_environment_model_class.write_environments_to_dynamodb.assert_called_once_with(
Q(id=environment.id)
environment_id=environment.id
)


Expand All @@ -46,7 +44,7 @@ def test_amplitude_configuration_delete_writes_environment_to_dynamodb(

# Then
mock_environment_model_class.write_environments_to_dynamodb.assert_called_once_with(
Q(id=environment.id)
environment_id=environment.id
)


Expand Down
19 changes: 19 additions & 0 deletions api/tests/unit/projects/test_unit_projects_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,22 @@ def test_updating_project_clears_environment_caches(environment, project, mocker

# Then
mock_environment_cache.delete_many.assert_called_once_with([environment.api_key])


def test_environments_are_updated_in_dynamodb_when_project_id_updated(
dynamo_enabled_project,
dynamo_enabled_project_environment_one,
dynamo_enabled_project_environment_two,
mocker,
):
# Given
mock_environments_wrapper = mocker.patch("environments.models.environment_wrapper")

# When
dynamo_enabled_project.name = dynamo_enabled_project.name + " updated"
dynamo_enabled_project.save()

# Then
mock_environments_wrapper.write_environments.assert_called_once_with(
[dynamo_enabled_project_environment_one, dynamo_enabled_project_environment_two]
)

0 comments on commit 312601b

Please sign in to comment.