Skip to content

Commit

Permalink
feat: explicitly set audit log created date (#3083)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewelwell authored Dec 5, 2023
1 parent d129397 commit e470ddb
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 13 deletions.
18 changes: 18 additions & 0 deletions api/audit/migrations/0013_allow_manual_override_of_created_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.23 on 2023-12-01 10:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('audit', '0012_auto_20230517_1006'),
]

operations = [
migrations.AlterField(
model_name='auditlog',
name='created_date',
field=models.DateTimeField(verbose_name='DateCreated'),
),
]
8 changes: 7 additions & 1 deletion api/audit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.db import models
from django.db.models import Model, Q
from django.utils import timezone
from django_lifecycle import (
AFTER_CREATE,
BEFORE_CREATE,
Expand All @@ -19,7 +20,7 @@


class AuditLog(LifecycleModel):
created_date = models.DateTimeField("DateCreated", auto_now_add=True)
created_date = models.DateTimeField("DateCreated")

project = models.ForeignKey(
Project, related_name="audit_logs", null=True, on_delete=models.DO_NOTHING
Expand Down Expand Up @@ -103,6 +104,11 @@ def add_project(self):
if self.environment and self.project is None:
self.project = self.environment.project

@hook(BEFORE_CREATE)
def add_created_date(self) -> None:
if not self.created_date:
self.created_date = timezone.now()

@hook(
AFTER_CREATE,
priority=priority.HIGHEST_PRIORITY,
Expand Down
8 changes: 8 additions & 0 deletions api/audit/tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
import typing
from datetime import datetime

from django.contrib.auth import get_user_model
from django.utils import timezone

from audit.constants import (
FEATURE_STATE_UPDATED_BY_CHANGE_REQUEST_MESSAGE,
Expand Down Expand Up @@ -55,6 +57,7 @@ def _create_feature_state_audit_log_for_change_request(
project=feature_state.environment.project,
log=log,
is_system_event=True,
created_date=feature_state.live_from,
)


Expand Down Expand Up @@ -113,6 +116,7 @@ def create_audit_log_from_historical_record(
related_object_type=related_object_type.name,
log=log_message,
master_api_key=history_instance.master_api_key,
created_date=history_instance.history_date,
**instance.get_extra_audit_log_kwargs(history_instance),
)

Expand All @@ -123,6 +127,7 @@ def create_segment_priorities_changed_audit_log(
feature_segment_ids: typing.List[int],
user_id: int = None,
master_api_key_id: int = None,
changed_at: str = None,
):
"""
This needs to be a separate task called by the view itself. This is because the OrderedModelBase class
Expand Down Expand Up @@ -166,4 +171,7 @@ def create_segment_priorities_changed_audit_log(
related_object_id=feature.id,
related_object_type=RelatedObjectType.FEATURE.name,
master_api_key_id=master_api_key_id,
created_date=datetime.fromisoformat(changed_at)
if changed_at is not None
else timezone.now(),
)
3 changes: 3 additions & 0 deletions api/edge_api/identities/tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging
import typing

from django.utils import timezone

from audit.models import AuditLog
from audit.related_object_type import RelatedObjectType
from environments.models import Environment, Webhook
Expand Down Expand Up @@ -123,6 +125,7 @@ def generate_audit_log_records(
related_object_type=RelatedObjectType.EDGE_IDENTITY.name,
related_object_uuid=identity_uuid,
master_api_key_id=master_api_key_id,
created_date=timezone.now(),
)
)

Expand Down
1 change: 1 addition & 0 deletions api/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ def sort_function(id_priority_pair):
"master_api_key_id": request.master_api_key.id
if hasattr(request, "master_api_key")
else None,
"changed_at": timezone.now().isoformat(),
}
)

Expand Down
41 changes: 32 additions & 9 deletions api/tests/unit/audit/test_unit_audit_tasks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.utils import timezone

from audit.constants import (
FEATURE_STATE_UPDATED_BY_CHANGE_REQUEST_MESSAGE,
FEATURE_STATE_WENT_LIVE_MESSAGE,
Expand All @@ -11,8 +13,9 @@
create_segment_priorities_changed_audit_log,
)
from environments.models import Environment
from features.models import FeatureSegment
from features.models import Feature, FeatureSegment, FeatureState
from segments.models import Segment
from users.models import FFAdminUser


def test_create_audit_log_from_historical_record_does_nothing_if_no_user_or_api_key(
Expand Down Expand Up @@ -163,7 +166,11 @@ def test_create_audit_log_from_historical_record_creates_audit_log_with_correct_
instance.get_audit_log_related_object_type.return_value = related_object_type
instance.get_extra_audit_log_kwargs.return_value = {}
history_instance = mocker.MagicMock(
history_id=1, instance=instance, master_api_key=None, history_type="+"
history_id=1,
instance=instance,
master_api_key=None,
history_type="+",
history_date=timezone.now(),
)

history_user = mocker.MagicMock()
Expand Down Expand Up @@ -205,12 +212,16 @@ def test_create_audit_log_from_historical_record_creates_audit_log_with_correct_
related_object_type=related_object_type.name,
log=log_message,
master_api_key=None,
created_date=history_instance.history_date,
)


def test_create_segment_priorities_changed_audit_log(
admin_user, feature_segment, feature, environment
):
admin_user: FFAdminUser,
feature_segment: FeatureSegment,
feature: Feature,
environment: Environment,
) -> None:
# Given
another_segment = Segment.objects.create(
project=environment.project, name="Another Segment"
Expand All @@ -219,6 +230,8 @@ def test_create_segment_priorities_changed_audit_log(
feature=feature, environment=environment, segment=another_segment
)

now = timezone.now()

# When
create_segment_priorities_changed_audit_log(
previous_id_priority_pairs=[
Expand All @@ -227,16 +240,20 @@ def test_create_segment_priorities_changed_audit_log(
],
feature_segment_ids=[feature_segment.id, another_feature_segment.id],
user_id=admin_user.id,
changed_at=now.isoformat(),
)

# Then
assert AuditLog.objects.filter(
environment=environment,
log=f"Segment overrides re-ordered for feature '{feature.name}'.",
created_date=now,
).exists()


def test_create_feature_state_went_live_audit_log(change_request_feature_state):
def test_create_feature_state_went_live_audit_log(
change_request_feature_state: FeatureState,
) -> None:
# Given
message = FEATURE_STATE_WENT_LIVE_MESSAGE % (
change_request_feature_state.feature.name,
Expand All @@ -250,15 +267,18 @@ def test_create_feature_state_went_live_audit_log(change_request_feature_state):
# Then
assert (
AuditLog.objects.filter(
related_object_id=feature_state_id, is_system_event=True, log=message
related_object_id=feature_state_id,
is_system_event=True,
log=message,
created_date=change_request_feature_state.live_from,
).count()
== 1
)


def test_create_feature_state_updated_by_change_request_audit_log(
change_request_feature_state,
):
change_request_feature_state: FeatureState,
) -> None:
# Given
message = FEATURE_STATE_UPDATED_BY_CHANGE_REQUEST_MESSAGE % (
change_request_feature_state.feature.name,
Expand All @@ -272,7 +292,10 @@ def test_create_feature_state_updated_by_change_request_audit_log(
# Then
assert (
AuditLog.objects.filter(
related_object_id=feature_state_id, is_system_event=True, log=message
related_object_id=feature_state_id,
is_system_event=True,
log=message,
created_date=change_request_feature_state.live_from,
).count()
== 1
)
Expand Down
9 changes: 6 additions & 3 deletions api/tests/unit/features/test_unit_features_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import timedelta
from unittest import mock

import freezegun
import pytest
from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
Expand Down Expand Up @@ -848,9 +849,10 @@ def test_feature_segment_update_priorities_when_changes(
new_id_priority_pairs = [(feature_segment.id, 1), (another_feature_segment.id, 0)]

# When
returned_feature_segments = FeatureSegment.update_priorities(
new_feature_segment_id_priorities=new_id_priority_pairs
)
with freezegun.freeze_time(now):
returned_feature_segments = FeatureSegment.update_priorities(
new_feature_segment_id_priorities=new_id_priority_pairs
)

# Then
assert sorted(
Expand All @@ -864,6 +866,7 @@ def test_feature_segment_update_priorities_when_changes(
"feature_segment_ids": [feature_segment.id, another_feature_segment.id],
"user_id": mocked_request.user.id,
"master_api_key_id": mocked_request.master_api_key.id,
"changed_at": now.isoformat(),
}
)

Expand Down

3 comments on commit e470ddb

@vercel
Copy link

@vercel vercel bot commented on e470ddb Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./docs

docs-git-main-flagsmith.vercel.app
docs-flagsmith.vercel.app
docs.flagsmith.com
docs.bullet-train.io

@vercel
Copy link

@vercel vercel bot commented on e470ddb Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on e470ddb Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.