Skip to content

Commit

Permalink
Merge branch 'main' into fix/fine_tune_feature_import_export
Browse files Browse the repository at this point in the history
  • Loading branch information
zachaysan committed Dec 14, 2023
2 parents b9a6b07 + 9c475e4 commit e5c46e0
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 20 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/platform-docker-publish-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,25 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
context: .

- name: Set up release tag variables
run: |
TAG=${{github.ref_name}}
echo "version_trim=${TAG#v}" >> $GITHUB_ENV
- name: Update flagsmith-charts values.yaml with latest docker version
uses: fjogeleit/yaml-update-action@main
with:
token: ${{ secrets.FLAGSMITH_CHARTS_GITHUB_TOKEN }}
repository: flagsmith/flagsmith-charts
workDir: chart
masterBranchName: 'main'
targetBranch: 'main'
branch: docker-image-version-bump-${{ env.version_trim }}
commitChange: true
createPR: true
message: 'Flagsmith docker image version bump'
description: 'Automated PR generated by a release event in https://github.com/Flagsmith/flagsmith'
valueFile: 'charts/flagsmith/Chart.yaml'
value: ${{ env.version_trim }}
propertyPath: 'appVersion'
4 changes: 2 additions & 2 deletions api/app_analytics/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Resource(models.IntegerChoices):
def get_lowercased_name(cls, resource: int) -> str:
member = next(filter(lambda member: member.value == resource, cls), None)
if not member:
raise ValueError("Invalid resource: {resource}")
raise ValueError(f"Invalid resource: {resource}")

return member.name.lower()

Expand All @@ -24,7 +24,7 @@ def get_from_resource_name(cls, resource: str) -> int:
try:
return getattr(cls, resource.upper().replace("-", "_")).value
except (KeyError, AttributeError) as err:
raise ValueError("Invalid resource: {resource}") from err
raise ValueError(f"Invalid resource: {resource}") from err


class APIUsageRaw(models.Model):
Expand Down
10 changes: 9 additions & 1 deletion api/environments/dynamodb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
from .dynamodb_wrapper import ( # noqa
from .dynamodb_wrapper import (
DynamoEnvironmentAPIKeyWrapper,
DynamoEnvironmentV2Wrapper,
DynamoEnvironmentWrapper,
DynamoIdentityWrapper,
)

__all__ = (
"DynamoEnvironmentAPIKeyWrapper",
"DynamoEnvironmentV2Wrapper",
"DynamoEnvironmentWrapper",
"DynamoIdentityWrapper",
)
5 changes: 3 additions & 2 deletions api/environments/dynamodb/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
logger = logging.getLogger(__name__)


def migrate_environments_to_v2(project_id: int) -> None:
def migrate_environments_to_v2(project_id: int) -> IdentityOverridesV2Changeset | None:
dynamo_wrapper_v2 = DynamoEnvironmentV2Wrapper()
identity_wrapper = DynamoIdentityWrapper()

if not (dynamo_wrapper_v2.is_enabled and identity_wrapper.is_enabled):
return
return None

logger.info("Migrating environments to v2 for project %d", project_id)

Expand All @@ -43,6 +43,7 @@ def migrate_environments_to_v2(project_id: int) -> None:
dynamo_wrapper_v2.update_identity_overrides(changeset)

logger.info("Finished migrating environments to v2 for project %d", project_id)
return changeset


def _iter_paginated_overrides(
Expand Down
12 changes: 11 additions & 1 deletion api/environments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@
)
from environments.dynamodb import (
DynamoEnvironmentAPIKeyWrapper,
DynamoEnvironmentV2Wrapper,
DynamoEnvironmentWrapper,
)
from environments.exceptions import EnvironmentHeaderNotPresentError
from environments.managers import EnvironmentManager
from features.models import Feature, FeatureSegment, FeatureState
from features.versioning.exceptions import FeatureVersioningError
from metadata.models import Metadata
from projects.models import IdentityOverridesV2MigrationStatus, Project
from segments.models import Segment
from util.mappers import map_environment_to_environment_document
from webhooks.models import AbstractBaseExportableWebhookModel
Expand All @@ -57,6 +59,7 @@

# Intialize the dynamo environment wrapper(s) globaly
environment_wrapper = DynamoEnvironmentWrapper()
environment_v2_wrapper = DynamoEnvironmentV2Wrapper()
environment_api_key_wrapper = DynamoEnvironmentAPIKeyWrapper()


Expand Down Expand Up @@ -234,7 +237,7 @@ def write_environments_to_dynamodb(
# grab the first project and verify that each environment is for the same
# project (which should always be the case). Since we're working with fairly
# small querysets here, this shouldn't have a noticeable impact on performance.
project = getattr(environments[0], "project", None)
project: Project | None = getattr(environments[0], "project", None)
for environment in environments[1:]:
if not environment.project == project:
raise RuntimeError("Environments must all belong to the same project.")
Expand All @@ -244,6 +247,13 @@ def write_environments_to_dynamodb(

environment_wrapper.write_environments(environments)

if (
project.identity_overrides_v2_migration_status
== IdentityOverridesV2MigrationStatus.COMPLETE
and environment_v2_wrapper.is_enabled
):
environment_v2_wrapper.write_environments(environments)

def get_feature_state(
self, feature_id: int, filter_kwargs: dict = None
) -> typing.Optional[FeatureState]:
Expand Down
14 changes: 7 additions & 7 deletions api/projects/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@


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

Environment.write_environments_to_dynamodb(project_id=project_id)


@register_task_handler()
def migrate_project_environments_to_v2(project_id: int):
def migrate_project_environments_to_v2(project_id: int) -> None:
from environments.dynamodb.services import migrate_environments_to_v2
from projects.models import IdentityOverridesV2MigrationStatus, Project

with transaction.atomic():
project = Project.objects.select_for_update().get(id=project_id)
migrate_environments_to_v2(project_id=project_id)
project.identity_overrides_v2_migration_status = (
IdentityOverridesV2MigrationStatus.COMPLETE
)
project.save()
if migrate_environments_to_v2(project_id=project_id):
project.identity_overrides_v2_migration_status = (
IdentityOverridesV2MigrationStatus.COMPLETE
)
project.save()
10 changes: 9 additions & 1 deletion api/tests/unit/environments/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from unittest.mock import Mock

import pytest
from pytest_mock import MockerFixture


@pytest.fixture()
def mock_dynamo_env_wrapper(mocker):
def mock_dynamo_env_wrapper(mocker: MockerFixture) -> Mock:
return mocker.patch("environments.models.environment_wrapper")


@pytest.fixture()
def mock_dynamo_env_v2_wrapper(mocker: MockerFixture) -> Mock:
return mocker.patch("environments.models.environment_v2_wrapper")
80 changes: 78 additions & 2 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,7 @@
from copy import copy
from datetime import timedelta
from unittest import mock
from unittest.mock import MagicMock
from unittest.mock import MagicMock, Mock

import pytest
from core.constants import STRING
Expand All @@ -25,7 +25,7 @@
from features.multivariate.models import MultivariateFeatureOption
from features.versioning.models import EnvironmentFeatureVersion
from organisations.models import Organisation, OrganisationRole
from projects.models import Project
from projects.models import IdentityOverridesV2MigrationStatus, Project
from segments.models import Segment
from util.mappers import map_environment_to_environment_document

Expand Down Expand Up @@ -484,6 +484,82 @@ def test_write_environments_to_dynamodb_with_environment_and_project(
)


def test_write_environments_to_dynamodb__project_environments_v2_migrated__call_expected(
dynamo_enabled_project: Project,
dynamo_enabled_project_environment_one: Environment,
dynamo_enabled_project_environment_two: Environment,
mock_dynamo_env_wrapper: Mock,
mock_dynamo_env_v2_wrapper: Mock,
) -> None:
# Given
dynamo_enabled_project.identity_overrides_v2_migration_status = (
IdentityOverridesV2MigrationStatus.COMPLETE
)
dynamo_enabled_project.save()
mock_dynamo_env_v2_wrapper.is_enabled = True

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

# Then
args, kwargs = mock_dynamo_env_v2_wrapper.write_environments.call_args
assert kwargs == {}
assert len(args) == 1
assert_queryset_equal(
args[0], Environment.objects.filter(project=dynamo_enabled_project)
)


def test_write_environments_to_dynamodb__project_environments_v2_migrated__wrapper_disabled__wrapper_not_called(
dynamo_enabled_project: Project,
dynamo_enabled_project_environment_one: Environment,
dynamo_enabled_project_environment_two: Environment,
mock_dynamo_env_wrapper: Mock,
mock_dynamo_env_v2_wrapper: Mock,
) -> None:
# Given
mock_dynamo_env_v2_wrapper.is_enabled = False
dynamo_enabled_project.identity_overrides_v2_migration_status = (
IdentityOverridesV2MigrationStatus.COMPLETE
)
dynamo_enabled_project.save()

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

# Then
mock_dynamo_env_v2_wrapper.write_environments.assert_not_called()


@pytest.mark.parametrize(
"identity_overrides_v2_migration_status",
(
IdentityOverridesV2MigrationStatus.NOT_STARTED,
IdentityOverridesV2MigrationStatus.IN_PROGRESS,
),
)
def test_write_environments_to_dynamodb__project_environments_v2_not_migrated__wrapper_not_called(
dynamo_enabled_project: Project,
dynamo_enabled_project_environment_one: Environment,
dynamo_enabled_project_environment_two: Environment,
mock_dynamo_env_wrapper: Mock,
mock_dynamo_env_v2_wrapper: Mock,
identity_overrides_v2_migration_status: str,
) -> None:
# Given
dynamo_enabled_project.identity_overrides_v2_migration_status = (
identity_overrides_v2_migration_status
)
dynamo_enabled_project.save()
mock_dynamo_env_v2_wrapper.is_enabled = True

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

# Then
mock_dynamo_env_v2_wrapper.write_environments.assert_not_called()


@pytest.mark.parametrize(
"value, identity_id, identifier",
(
Expand Down
24 changes: 22 additions & 2 deletions api/tests/unit/projects/test_tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
from pytest_mock import MockerFixture

from environments.dynamodb.types import IdentityOverridesV2Changeset
from projects.models import IdentityOverridesV2MigrationStatus, Project
from projects.tasks import migrate_project_environments_to_v2

Expand All @@ -16,16 +17,34 @@ def project_v2_migration_in_progress(
return project


@pytest.mark.parametrize(
"migrate_environments_to_v2_return_value, expected_status",
(
(
IdentityOverridesV2Changeset(to_put=[], to_delete=[]),
IdentityOverridesV2MigrationStatus.COMPLETE,
),
(
None,
IdentityOverridesV2MigrationStatus.IN_PROGRESS,
),
),
)
def test_migrate_project_environments_to_v2__calls_expected(
mocker: MockerFixture,
project_v2_migration_in_progress: Project,
migrate_environments_to_v2_return_value: IdentityOverridesV2Changeset | None,
expected_status: str,
):
# Given
mocked_migrate_environments_to_v2 = mocker.patch(
"environments.dynamodb.services.migrate_environments_to_v2",
autospec=True,
return_value=None,
)
mocked_migrate_environments_to_v2.return_value = (
migrate_environments_to_v2_return_value
)

# When
migrate_project_environments_to_v2(project_id=project_v2_migration_in_progress.id)
Expand All @@ -35,8 +54,9 @@ def test_migrate_project_environments_to_v2__calls_expected(
mocked_migrate_environments_to_v2.assert_called_once_with(
project_id=project_v2_migration_in_progress.id,
)
assert project_v2_migration_in_progress.identity_overrides_v2_migration_status == (
IdentityOverridesV2MigrationStatus.COMPLETE
assert (
project_v2_migration_in_progress.identity_overrides_v2_migration_status
== expected_status
)


Expand Down
6 changes: 4 additions & 2 deletions frontend/web/components/pages/ChangeRequestPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,10 @@ const ChangeRequestsPage = class extends Component {
{moment(changeRequest.created_at).format(
'Do MMM YYYY HH:mma',
)}{' '}
by {changeRequest.user && user.first_name}{' '}
{user && user.last_name}
by{' '}
{user
? `${user.first_name} ${user.last_name}`
: 'Unknown user'}
</PageTitle>
<p className='mt-2'>{changeRequest.description}</p>
<div className='row'>
Expand Down

0 comments on commit e5c46e0

Please sign in to comment.