Skip to content

Commit

Permalink
feat: add group owners to features (#2908)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachaysan authored Nov 6, 2023
1 parent a09436d commit 493f0e5
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 13 deletions.
19 changes: 19 additions & 0 deletions api/features/migrations/0060_feature_group_owners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.20 on 2023-10-30 20:15

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('users', '0034_add_user_permission_group_membership_through_model'),
('features', '0059_fix_feature_type'),
]

operations = [
migrations.AddField(
model_name='feature',
name='group_owners',
field=models.ManyToManyField(blank=True, related_name='owned_features', to='users.UserPermissionGroup'),
),
]
4 changes: 4 additions & 0 deletions api/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ class Feature(
owners = models.ManyToManyField(
"users.FFAdminUser", related_name="owned_features", blank=True
)
group_owners = models.ManyToManyField(
"users.UserPermissionGroup", related_name="owned_features", blank=True
)

is_server_key_only = models.BooleanField(default=False)

history_record_class_path = "features.models.HistoricalFeature"
Expand Down
2 changes: 2 additions & 0 deletions api/features/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"create": CREATE_FEATURE,
"add_owners": CREATE_FEATURE,
"remove_owners": CREATE_FEATURE,
"add_group_owners": CREATE_FEATURE,
"remove_group_owners": CREATE_FEATURE,
"update": CREATE_FEATURE,
"partial_update": CREATE_FEATURE,
}
Expand Down
20 changes: 19 additions & 1 deletion api/features/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
HideSensitiveFieldsSerializerMixin,
)
from projects.models import Project
from users.serializers import UserIdsSerializer, UserListSerializer
from users.serializers import (
UserIdsSerializer,
UserListSerializer,
UserPermissionGroupSummarySerializer,
)
from util.drf_writable_nested.serializers import (
DeleteBeforeUpdateWritableNestedModelSerializer,
)
Expand All @@ -36,8 +40,21 @@ def remove_users(self, feature: Feature):
feature.owners.remove(*user_ids)


class FeatureGroupOwnerInputSerializer(serializers.Serializer):
group_ids = serializers.ListField(child=serializers.IntegerField())

def add_group_owners(self, feature: Feature):
group_ids = self.validated_data["group_ids"]
feature.group_owners.add(*group_ids)

def remove_group_owners(self, feature: Feature):
group_ids = self.validated_data["group_ids"]
feature.group_owners.remove(*group_ids)


class ProjectFeatureSerializer(serializers.ModelSerializer):
owners = UserListSerializer(many=True, read_only=True)
group_owners = UserPermissionGroupSummarySerializer(many=True, read_only=True)

class Meta:
model = Feature
Expand All @@ -50,6 +67,7 @@ class Meta:
"default_enabled",
"type",
"owners",
"group_owners",
"is_server_key_only",
)
writeonly_fields = ("initial_value", "default_enabled")
Expand Down
39 changes: 38 additions & 1 deletion api/features/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from django.views.decorators.cache import cache_page
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import mixins, status, viewsets
from rest_framework import mixins, serializers, status, viewsets
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.generics import GenericAPIView, get_object_or_404
Expand All @@ -35,6 +35,7 @@
)
from projects.models import Project
from projects.permissions import VIEW_PROJECT
from users.models import UserPermissionGroup
from webhooks.webhooks import WebhookEventType

from .models import Feature, FeatureState
Expand All @@ -48,6 +49,7 @@
from .serializers import (
CreateSegmentOverrideFeatureStateSerializer,
FeatureEvaluationDataSerializer,
FeatureGroupOwnerInputSerializer,
FeatureInfluxDataSerializer,
FeatureOwnerInputSerializer,
FeatureQuerySerializer,
Expand Down Expand Up @@ -157,6 +159,41 @@ def get_serializer_context(self):

return context

@swagger_auto_schema(
request_body=FeatureGroupOwnerInputSerializer,
responses={200: ProjectFeatureSerializer},
)
@action(detail=True, methods=["POST"], url_path="add-group-owners")
def add_group_owners(self, request, *args, **kwargs):
feature = self.get_object()
data = request.data

serializer = FeatureGroupOwnerInputSerializer(data=data)
serializer.is_valid(raise_exception=True)

if not UserPermissionGroup.objects.filter(
id__in=serializer.validated_data["group_ids"],
organisation_id=feature.project.organisation_id,
).count() == len(serializer.validated_data["group_ids"]):
raise serializers.ValidationError("Some groups not found")

serializer.add_group_owners(feature)
response = Response(self.get_serializer(instance=feature).data)
return response

@swagger_auto_schema(
request_body=FeatureGroupOwnerInputSerializer,
responses={200: ProjectFeatureSerializer},
)
@action(detail=True, methods=["POST"], url_path="remove-group-owners")
def remove_group_owners(self, request, *args, **kwargs):
feature = self.get_object()
serializer = FeatureGroupOwnerInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.remove_group_owners(feature)
response = Response(self.get_serializer(instance=feature).data)
return response

@swagger_auto_schema(
request_body=FeatureOwnerInputSerializer,
responses={200: ProjectFeatureSerializer},
Expand Down
2 changes: 1 addition & 1 deletion api/import_export/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def export_features(organisation_id: int) -> typing.List[dict]:
_EntityExportConfig(
Feature,
Q(project__organisation__id=organisation_id),
exclude_fields=["owners"],
exclude_fields=["owners", "group_owners"],
),
_EntityExportConfig(
MultivariateFeatureOption,
Expand Down
96 changes: 91 additions & 5 deletions api/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pip-tools = "~6.13.0"
pytest-cov = "~4.1.0"
datamodel-code-generator = "~0.22"
requests-mock = "^1.11.0"
pdbpp = "^0.10.3"

[build-system]
requires = ["poetry-core>=1.5.0"]
Expand Down
Loading

3 comments on commit 493f0e5

@vercel
Copy link

@vercel vercel bot commented on 493f0e5 Nov 6, 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 493f0e5 Nov 6, 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 493f0e5 Nov 6, 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.flagsmith.com
docs-flagsmith.vercel.app
docs.bullet-train.io
docs-git-main-flagsmith.vercel.app

Please sign in to comment.