Skip to content

Commit

Permalink
Merge branch 'main' into feat/create_change_requests_for_segments
Browse files Browse the repository at this point in the history
  • Loading branch information
zachaysan committed Nov 21, 2024
2 parents ce7fca8 + 294b352 commit 502e448
Show file tree
Hide file tree
Showing 33 changed files with 367 additions and 687 deletions.
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.153.0"
".": "2.154.0"
}
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## [2.154.0](https://github.com/Flagsmith/flagsmith/compare/v2.153.0...v2.154.0) (2024-11-21)


### Features

* Add Hubspot cookie logging ([#4854](https://github.com/Flagsmith/flagsmith/issues/4854)) ([8fe4d41](https://github.com/Flagsmith/flagsmith/commit/8fe4d413ff3a7a5cb7ddcd2f04a5a26ed2651316))


### Bug Fixes

* Allow teardown to use FLAGSMITH_API_URL ([#4849](https://github.com/Flagsmith/flagsmith/issues/4849)) ([9ad8da6](https://github.com/Flagsmith/flagsmith/commit/9ad8da6a1b8a034d4436e8579ecadbac1327f9ce))
* Custom Gunicorn logger not sending StatsD stats ([#4819](https://github.com/Flagsmith/flagsmith/issues/4819)) ([9bbfdf0](https://github.com/Flagsmith/flagsmith/commit/9bbfdf0d2bc4cb95fa0d029144c4bc61fb23f0bc))
* flagsmith stale flags check ([#4831](https://github.com/Flagsmith/flagsmith/issues/4831)) ([ea6a169](https://github.com/Flagsmith/flagsmith/commit/ea6a169d9f84172850b2b01e5fd7d70b8852c9bf))
* Google OAuth broken in unified docker image ([#4839](https://github.com/Flagsmith/flagsmith/issues/4839)) ([051cc6f](https://github.com/Flagsmith/flagsmith/commit/051cc6fe0db5a311998bece472982e19926a2bc5))
* Handle environment admin not being able to check VIEW_PROJECT permissions ([#4827](https://github.com/Flagsmith/flagsmith/issues/4827)) ([23ab3c1](https://github.com/Flagsmith/flagsmith/commit/23ab3c1a8a26284e87503b389c597b58866ee27b))
* Handle invalid colour codes on tags, allow default colours ([#4822](https://github.com/Flagsmith/flagsmith/issues/4822)) ([a33633f](https://github.com/Flagsmith/flagsmith/commit/a33633f61f9eaf8efd4322af48a862dbebcbe682))
* prevent lock when adding FFAdmin.uuid column ([#4832](https://github.com/Flagsmith/flagsmith/issues/4832)) ([4a310b0](https://github.com/Flagsmith/flagsmith/commit/4a310b0c314de4499425fbb5584f94a1f77c640c))
* **project/realtime:** only allow enterprise to enable realtime ([#4843](https://github.com/Flagsmith/flagsmith/issues/4843)) ([9b21af7](https://github.com/Flagsmith/flagsmith/commit/9b21af7652ae2cb5758361c2809a345cc79209d3))
* **project/serializer:** limit edit to only fields that make sense ([#4846](https://github.com/Flagsmith/flagsmith/issues/4846)) ([86ba762](https://github.com/Flagsmith/flagsmith/commit/86ba762d0e93856209c4b975e7bdd1d207c8bfa8))
* replace alter field with adding a new field ([#4817](https://github.com/Flagsmith/flagsmith/issues/4817)) ([0d1c64a](https://github.com/Flagsmith/flagsmith/commit/0d1c64a3db04cd7d56f39b5b3fa0fee4340504eb))
* revert https://github.com/Flagsmith/flagsmith/pull/4817 ([#4850](https://github.com/Flagsmith/flagsmith/issues/4850)) ([793a110](https://github.com/Flagsmith/flagsmith/commit/793a110b031e89589e8e57734aa7fee8818c0812))

## [2.153.0](https://github.com/Flagsmith/flagsmith/compare/v2.152.0...v2.153.0) (2024-11-12)


Expand Down
1 change: 1 addition & 0 deletions api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@

SECURE_REDIRECT_EXEMPT = env.list("DJANGO_SECURE_REDIRECT_EXEMPT", default=[])
SECURE_REFERRER_POLICY = env.str("DJANGO_SECURE_REFERRER_POLICY", default="same-origin")
SECURE_CROSS_ORIGIN_OPENER_POLICY = env.str("DJANGO_SECURE_CROSS_ORIGIN_OPENER_POLICY", default="same-origin")
SECURE_SSL_HOST = env.str("DJANGO_SECURE_SSL_HOST", default=None)
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=False)

Expand Down
35 changes: 34 additions & 1 deletion api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@
CREATE_PROJECT,
MANAGE_USER_GROUPS,
)
from organisations.subscriptions.constants import CHARGEBEE, XERO
from organisations.subscriptions.constants import (
CHARGEBEE,
FREE_PLAN_ID,
SCALE_UP,
STARTUP,
XERO,
)
from permissions.models import PermissionModel
from projects.models import (
Project,
Expand Down Expand Up @@ -324,6 +330,33 @@ def enterprise_subscription(organisation: Organisation) -> Subscription:
return organisation.subscription


@pytest.fixture()
def startup_subscription(organisation: Organisation) -> Subscription:
Subscription.objects.filter(organisation=organisation).update(
plan=STARTUP, subscription_id="subscription-id"
)
organisation.refresh_from_db()
return organisation.subscription


@pytest.fixture()
def scale_up_subscription(organisation: Organisation) -> Subscription:
Subscription.objects.filter(organisation=organisation).update(
plan=SCALE_UP, subscription_id="subscription-id"
)
organisation.refresh_from_db()
return organisation.subscription


@pytest.fixture()
def free_subscription(organisation: Organisation) -> Subscription:
Subscription.objects.filter(organisation=organisation).update(
plan=FREE_PLAN_ID, subscription_id="subscription-id"
)
organisation.refresh_from_db()
return organisation.subscription


@pytest.fixture()
def project(organisation):
return Project.objects.create(name="Test Project", organisation=organisation)
Expand Down
15 changes: 15 additions & 0 deletions api/integrations/lead_tracking/hubspot/services.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import logging

from rest_framework.request import Request

from integrations.lead_tracking.hubspot.constants import HUBSPOT_COOKIE_NAME
from users.models import HubspotTracker

logger = logging.getLogger(__name__)


def register_hubspot_tracker(request: Request) -> None:
hubspot_cookie = request.COOKIES.get(HUBSPOT_COOKIE_NAME)

# TODO: Remove this temporary debugging logger statement.
logger.info(f"Request cookies for user {request.user.email}: {request.COOKIES}")

if hubspot_cookie:
logger.info(
f"Creating HubspotTracker instance for user {request.user.email} with cookie {hubspot_cookie}"
)

HubspotTracker.objects.update_or_create(
user=request.user,
defaults={
"hubspot_cookie": hubspot_cookie,
},
)
else:
logger.info(
f"Could not create HubspotTracker instance for user {request.user.email} since no cookie"
)
22 changes: 17 additions & 5 deletions api/projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class Meta:
"edge_v2_migration_status",
"minimum_change_request_approvals",
)
read_only_fields = (
"enable_dynamo_db",
"edge_v2_migration_status",
)

def get_migration_status(self, obj: Project) -> str:
if not settings.PROJECT_METADATA_TABLE_NAME_DYNAMO:
Expand All @@ -66,11 +70,9 @@ def get_use_edge_identities(self, obj: Project) -> bool:
)


class ProjectUpdateOrCreateSerializer(
ReadOnlyIfNotValidPlanMixin, ProjectListSerializer
):
class ProjectCreateSerializer(ReadOnlyIfNotValidPlanMixin, ProjectListSerializer):
invalid_plans_regex = r"^(free|startup.*|scale-up.*)$"
field_names = ("stale_flags_limit_days",)
field_names = ("stale_flags_limit_days", "enable_realtime_updates")

def get_subscription(self) -> typing.Optional[Subscription]:
view = self.context["view"]
Expand All @@ -85,11 +87,21 @@ def get_subscription(self) -> typing.Optional[Subscription]:
# Organisation should only have a single subscription
return Subscription.objects.filter(organisation_id=organisation_id).first()
elif view.action in ("update", "partial_update"):
# handle instance not being set
# When request comes from yasg2 (as part of schema generation)
if not self.instance:
return None
return getattr(self.instance.organisation, "subscription", None)

return None


class ProjectUpdateSerializer(ProjectCreateSerializer):
class Meta(ProjectCreateSerializer.Meta):
read_only_fields = ProjectCreateSerializer.Meta.read_only_fields + (
"organisation",
)


class ProjectRetrieveSerializer(ProjectListSerializer):
total_features = serializers.SerializerMethodField()
total_segments = serializers.SerializerMethodField()
Expand Down
22 changes: 22 additions & 0 deletions api/projects/tags/migrations/0007_alter_tag_color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.16 on 2024-11-08 18:14

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("tags", "0006_alter_tag_type"),
]

operations = [
migrations.AlterField(
model_name="tag",
name="color",
field=models.CharField(
default="#6837FC",
help_text="Hexadecimal value of the tag color",
max_length=10,
),
),
]
2 changes: 1 addition & 1 deletion api/projects/tags/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TagType(models.Choices):
class Tag(AbstractBaseExportableModel):
label = models.CharField(max_length=100)
color = models.CharField(
max_length=10, help_text="Hexadecimal value of the tag color"
max_length=10, help_text="Hexadecimal value of the tag color", default="#6837FC"
)
description = models.CharField(max_length=512, blank=True, null=True)
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="tags")
Expand Down
15 changes: 9 additions & 6 deletions api/projects/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@
CreateUpdateUserProjectPermissionSerializer,
ListUserPermissionGroupProjectPermissionSerializer,
ListUserProjectPermissionSerializer,
ProjectCreateSerializer,
ProjectListSerializer,
ProjectRetrieveSerializer,
ProjectUpdateOrCreateSerializer,
ProjectUpdateSerializer,
)


Expand Down Expand Up @@ -71,11 +72,13 @@ class ProjectViewSet(viewsets.ModelViewSet):
permission_classes = [ProjectPermissions]

def get_serializer_class(self):
if self.action == "retrieve":
return ProjectRetrieveSerializer
elif self.action in ("create", "update", "partial_update"):
return ProjectUpdateOrCreateSerializer
return ProjectListSerializer
serializers = {
"retrieve": ProjectRetrieveSerializer,
"create": ProjectCreateSerializer,
"update": ProjectUpdateSerializer,
"partial_update": ProjectUpdateSerializer,
}
return serializers.get(self.action, ProjectListSerializer)

pagination_class = None

Expand Down
6 changes: 4 additions & 2 deletions api/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ def organisation_with_persist_trait_data_disabled(organisation):


@pytest.fixture()
def dynamo_enabled_project(admin_client, organisation):
def dynamo_enabled_project(
admin_client: APIClient, organisation: Organisation, settings: SettingsWrapper
):
settings.EDGE_ENABLED = True
project_data = {
"name": "Test Project",
"organisation": organisation,
"enable_dynamo_db": True,
}
url = reverse("api-v1:projects:project-list")
response = admin_client.post(url, data=project_data)
Expand Down
52 changes: 46 additions & 6 deletions api/tests/unit/projects/test_unit_projects_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ def test_can_update_project(
assert response.json()["stale_flags_limit_days"] == new_stale_flags_limit_days


def test_can_not_update_project_organisation(
admin_client: APIClient,
project: Project,
organisation: Organisation,
organisation_two: Organisation,
) -> None:
# Given
new_name = "New project name"

data = {
"name": new_name,
"organisation": organisation_two.id,
}
url = reverse("api-v1:projects:project-detail", args=[project.id])

# When
response = admin_client.put(url, data=data)

# Then
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == new_name
assert response.json()["organisation"] == organisation.id


@pytest.mark.parametrize(
"client",
[(lazy_fixture("admin_master_api_key_client")), (lazy_fixture("admin_client"))],
Expand Down Expand Up @@ -686,11 +710,20 @@ def test_get_project_by_uuid(client, project, mocker, settings, organisation):


@pytest.mark.parametrize(
"client",
[(lazy_fixture("admin_master_api_key_client")), (lazy_fixture("admin_client"))],
"subscription, can_update_realtime",
[
(lazy_fixture("free_subscription"), False),
(lazy_fixture("startup_subscription"), False),
(lazy_fixture("scale_up_subscription"), False),
(lazy_fixture("enterprise_subscription"), True),
],
)
def test_can_enable_realtime_updates_for_project(
client, project, mocker, settings, organisation
def test_can_enable_realtime_updates_for_enterprise(
admin_client: APIClient,
project: Project,
organisation: Organisation,
subscription: Subscription,
can_update_realtime: bool,
):
# Given
url = reverse("api-v1:projects:project-detail", args=[project.id])
Expand All @@ -702,12 +735,12 @@ def test_can_enable_realtime_updates_for_project(
}

# When
response = client.put(url, data=data)
response = admin_client.put(url, data=data)

# Then
assert response.status_code == status.HTTP_200_OK
assert response.json()["uuid"] == str(project.uuid)
assert response.json()["enable_realtime_updates"] is True
assert response.json()["enable_realtime_updates"] is can_update_realtime


@pytest.mark.parametrize(
Expand All @@ -723,6 +756,9 @@ def test_update_project(client, project, mocker, settings, organisation):
"organisation": organisation.id,
"only_allow_lower_case_feature_names": False,
"feature_name_regex": feature_name_regex,
# read only fields should not be updated
"enable_dynamo_db": not project.enable_dynamo_db,
"edge_v2_migration_status": project.edge_v2_migration_status + "random-string",
}

# When
Expand All @@ -732,6 +768,10 @@ def test_update_project(client, project, mocker, settings, organisation):
assert response.status_code == status.HTTP_200_OK
assert response.json()["only_allow_lower_case_feature_names"] is False
assert response.json()["feature_name_regex"] == feature_name_regex
assert response.json()["enable_dynamo_db"] == project.enable_dynamo_db
assert (
response.json()["edge_v2_migration_status"] == project.edge_v2_migration_status
)


@pytest.mark.parametrize(
Expand Down
53 changes: 0 additions & 53 deletions api/tests/unit/users/test_unit_users_migrations.py

This file was deleted.

Loading

0 comments on commit 502e448

Please sign in to comment.