Skip to content
Open
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
35 changes: 33 additions & 2 deletions api/features/versioning/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,18 @@ def trigger_update_version_webhooks(environment_feature_version_uuid: str) -> No
)


def _build_feature_state_change_summary(fs: FeatureState) -> str:
if fs.identity_id:
scope = f"Identity override ({fs.identity.identifier})" # type: ignore[union-attr]
elif fs.feature_segment_id:
scope = f"Segment override ({fs.feature_segment.segment.name})" # type: ignore[union-attr]
else:
scope = "Environment default"

state = "enabled" if fs.enabled else "disabled"
return f"{scope}: {state}"


@register_task_handler()
def create_environment_feature_version_published_audit_log_task(
environment_feature_version_uuid: str,
Expand All @@ -269,12 +281,31 @@ def create_environment_feature_version_published_audit_log_task(
"environment", "feature"
).get(uuid=environment_feature_version_uuid)

header = (
ENVIRONMENT_FEATURE_VERSION_PUBLISHED_MESSAGE
% environment_feature_version.feature.name
)

changed_states = get_updated_feature_states_for_version(environment_feature_version)

if changed_states:
changed_states = list(
FeatureState.objects.filter(
id__in=[fs.id for fs in changed_states]
).select_related("feature_segment__segment", "identity")
)
change_lines = "\n".join(
f"- {_build_feature_state_change_summary(fs)}" for fs in changed_states
)
log = f"{header}\n{change_lines}"
else:
log = header

AuditLog.objects.create(
environment=environment_feature_version.environment,
related_object_type=RelatedObjectType.EF_VERSION.name,
related_object_uuid=environment_feature_version.uuid,
log=ENVIRONMENT_FEATURE_VERSION_PUBLISHED_MESSAGE
% environment_feature_version.feature.name,
log=log,
author_id=environment_feature_version.published_by_id,
master_api_key_id=environment_feature_version.published_by_api_key_id,
)
Expand Down
113 changes: 113 additions & 0 deletions api/tests/unit/audit/test_unit_audit_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
create_feature_state_updated_by_change_request_audit_log,
create_segment_priorities_changed_audit_log,
)
from environments.identities.models import Identity
from environments.models import Environment
from features.models import Feature, FeatureSegment, FeatureState
from features.versioning.models import EnvironmentFeatureVersion
from features.versioning.tasks import (
create_environment_feature_version_published_audit_log_task,
)
from features.workflows.core.models import ChangeRequest
from segments.models import Segment


def test_get_audited_instance_from_audit_log_record__change_request__return_expected(
Expand Down Expand Up @@ -98,6 +100,117 @@ def test_get_audited_instance_from_audit_log_record__historical_record__return_e
assert instance == change_request


def test_create_environment_feature_version_published_audit_log_task__no_changes__uses_header_only(
environment_v2_versioning: Environment,
feature: Feature,
) -> None:
# Given
version = EnvironmentFeatureVersion.objects.create(
feature=feature,
environment=environment_v2_versioning,
)

# When
create_environment_feature_version_published_audit_log_task(str(version.uuid))

# Then
audit_log = AuditLog.objects.get(
related_object_type=RelatedObjectType.EF_VERSION.name,
related_object_uuid=version.uuid,
)
assert audit_log.log == f"New version published for feature: {feature.name}"


def test_create_environment_feature_version_published_audit_log_task__environment_default_changed__includes_detail(
environment_v2_versioning: Environment,
feature: Feature,
) -> None:
# Given
version = EnvironmentFeatureVersion.objects.create(
feature=feature,
environment=environment_v2_versioning,
)
fs = version.feature_states.filter(feature=feature).first()
assert fs is not None
fs.enabled = not fs.enabled
fs.save()

# When
create_environment_feature_version_published_audit_log_task(str(version.uuid))

# Then
audit_log = AuditLog.objects.get(
related_object_type=RelatedObjectType.EF_VERSION.name,
related_object_uuid=version.uuid,
)
assert f"New version published for feature: {feature.name}" in audit_log.log
assert "Environment default:" in audit_log.log


def test_create_environment_feature_version_published_audit_log_task__segment_override_changed__includes_segment_name(
environment_v2_versioning: Environment,
feature: Feature,
segment: Segment,
) -> None:
# Given
version = EnvironmentFeatureVersion.objects.create(
feature=feature,
environment=environment_v2_versioning,
)
feature_segment = FeatureSegment.objects.create(
feature=feature,
segment=segment,
environment=environment_v2_versioning,
environment_feature_version=version,
)
FeatureState.objects.create(
feature=feature,
environment=environment_v2_versioning,
feature_segment=feature_segment,
environment_feature_version=version,
enabled=True,
)

# When
create_environment_feature_version_published_audit_log_task(str(version.uuid))

# Then
audit_log = AuditLog.objects.get(
related_object_type=RelatedObjectType.EF_VERSION.name,
related_object_uuid=version.uuid,
)
assert f"Segment override ({segment.name})" in audit_log.log


def test_create_environment_feature_version_published_audit_log_task__identity_override_changed__includes_identity(
environment_v2_versioning: Environment,
feature: Feature,
identity: Identity,
) -> None:
# Given
version = EnvironmentFeatureVersion.objects.create(
feature=feature,
environment=environment_v2_versioning,
)
FeatureState.objects.create(
feature=feature,
environment=environment_v2_versioning,
identity=identity,
environment_feature_version=version,
enabled=True,
)

# When
create_environment_feature_version_published_audit_log_task(str(version.uuid))

# Then
audit_log = AuditLog.objects.get(
related_object_type=RelatedObjectType.EF_VERSION.name,
related_object_uuid=version.uuid,
)
assert f"Identity override ({identity.identifier})" in audit_log.log


def test_get_audited_instance_from_audit_log_record__unexpected_audit_log__return_none(
change_request: ChangeRequest,
) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,10 @@ def test_publish_feature_version__unpublished_version__publishes_and_creates_aud
related_object_uuid=environment_feature_version.uuid,
).first()
assert record
assert record.log == ENVIRONMENT_FEATURE_VERSION_PUBLISHED_MESSAGE % feature.name
assert record.log == (
f"{ENVIRONMENT_FEATURE_VERSION_PUBLISHED_MESSAGE % feature.name}\n"
f"- Environment default: disabled"
)


@pytest.mark.parametrize("live_from", (None, tomorrow))
Expand Down
Loading