Skip to content

Conversation

@Axxiar
Copy link
Contributor

@Axxiar Axxiar commented Nov 27, 2025

Summary by CodeRabbit

  • New Features

    • Added a configurable "campaigns" feature flag exposed in settings, sidebar visibility and runtime flags; translations added for many locales.
  • Enhancements

    • Settings UI shows feature-flag options relevant to the current page.
    • Activity logs include actor, action and content-type details; sensitive data (passwords) are masked.
  • Refactor

    • Backend serializer handling improved for more flexible feature‑flag serialization and extensibility.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

Adds a new "campaigns" feature flag across frontend and backend, updates translations and sidebar mapping, extends feature-flag schema and loader, introduces a GlobalSettings serializer factory and resolver, and exposes campaigns via a GlobalSettings-backed FeatureFlagsSerializer.

Changes

Cohort / File(s) Summary
Frontend: forms, flags & schema
frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte, frontend/src/lib/feature-flags.ts, frontend/src/lib/utils/schemas.ts
Import page in the Svelte form; add campaigns field entry in the form; add campaigns to the feature-flag loader and to FeatureFlagsSchema.
Frontend: sidebar config
frontend/src/lib/utils/sidebar-config.ts
Add campaigns: boolean to backend/frontend sidebar key types and map campaigns: featureFlags?.campaigns ?? false in getSidebarVisibleItems.
Frontend: i18n
frontend/messages/*.json (many locales; e.g., frontend/messages/en.json, .../fr.json, .../de.json, .../zh.json, etc.)
Add campaignsDescription localized entries across multiple locale files and adjust trailing commas where needed for valid JSON.
Backend: global settings views
backend/global_settings/views.py
Add GlobalSettingsSerializerFactory, serializers_module attribute, get_serializer_class resolver with debug logging; change FeatureFlagsViewSet.retrieve and update to instantiate serializer classes via the factory resolver.
Enterprise: serializers
enterprise/backend/enterprise_core/serializers.py
Add FeatureFlagsSerializer backed by GlobalSettings exposing campaigns (and other flags) as BooleanFields; extend/enhance LogEntrySerializer fields and masking logic.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant ViewSet as FeatureFlagsViewSet
    participant Factory as GlobalSettingsSerializerFactory
    participant Serializer as FeatureFlagsSerializer
    participant DB as GlobalSettings

    Client->>ViewSet: GET /global-settings/feature-flags
    ViewSet->>Factory: resolve serializer class for FeatureFlags
    Factory-->>ViewSet: FeatureFlagsSerializer class
    ViewSet->>DB: fetch GlobalSettings instance
    ViewSet->>Serializer: instantiate with GlobalSettings instance
    Serializer->>DB: read value (e.g., value.campaigns)
    DB-->>Serializer: return feature flag payload
    Serializer-->>ViewSet: serialized data
    ViewSet-->>Client: 200 OK + serialized feature flags
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇
I found a new flag named "campaigns" in the loam,
Poked it in forms, and gave it a home.
Sidebars glow and locales cheer,
Serializers whisper the flag is here.
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add feature flag for campaigns' directly and clearly describes the main change—adding a new 'campaigns' feature flag across frontend and backend systems.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Axxiar Axxiar marked this pull request as ready for review November 27, 2025 15:26
Copy link
Collaborator

@nas-tabchiche nas-tabchiche left a comment

Choose a reason for hiding this comment

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

Campaigns are a pro-only feature, the feature flag must not be visible in community

@Axxiar Axxiar added the pro label Nov 28, 2025
@Axxiar Axxiar marked this pull request as draft November 28, 2025 13:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
backend/global_settings/views.py (2)

7-8: Simplify GlobalSettingsSerializerFactory and avoid unused parameters

GlobalSettingsSerializerFactory mostly duplicates SerializerFactory:

  • __init__ is identical to the base implementation; you can simply delegate to super().__init__(*modules) and avoid repeating the logic/comment.
  • get_serializer(self, base_name: str, action: str = "default") ignores the action argument entirely, which makes the signature misleading.

You could reduce surface area and future confusion with something like:

class GlobalSettingsSerializerFactory(SerializerFactory):
    def __init__(self, *modules: str):
        super().__init__(*modules)

    def get_serializer(self, base_name: str) -> type:
        return self._get_serializer_class(f"{base_name}Serializer")

or even drop this subclass and use SerializerFactory + a small helper where you need it.

Also applies to: 18-21, 23-35


64-80: Use DRF’s get_serializer/context and guard against invalid module defaults

A few points around FeatureFlagsViewSet:

  • In retrieve/update you’re doing self.get_serializer_class()(instance, ...) directly. This bypasses DRF’s get_serializer, so:

    • No context (e.g. {"request": request, "view": self}) is passed.
    • You also skip perform_update() in update.

    It’s safer and more idiomatic to keep DRF’s flow and only customize which class is used. For example:

    def get_serializer_class(self, **kwargs):
        factory = GlobalSettingsSerializerFactory(
            self.serializers_module,
            settings.MODULE_PATHS.get("serializers"),
        )
        return factory.get_serializer("FeatureFlags")
    
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
    
    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(serializer.data)
  • settings.MODULE_PATHS.get("serializers", []) uses [] as a default, which is not a valid module path. If the key were ever missing, GlobalSettingsSerializerFactory would receive a non‑string module and importlib.import_module would fail. Either ensure "serializers" is always defined in MODULE_PATHS or filter out falsy/non‑string values before passing them into the factory.

  • Since your GlobalSettingsSerializerFactory.get_serializer ignores its action argument, passing kwargs.get("action", "default") is unnecessary noise; consider dropping that argument for clarity.

These changes would keep the dynamic enterprise/community resolution while staying aligned with DRF conventions and making the behavior more robust.

Also applies to: 84-85, 89-90

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 054c9f7 and f2bb934.

📒 Files selected for processing (3)
  • backend/global_settings/views.py (3 hunks)
  • enterprise/backend/enterprise_core/serializers.py (2 hunks)
  • frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
backend/global_settings/views.py (3)
backend/core/serializers.py (9)
  • SerializerFactory (30-62)
  • get_serializer (41-49)
  • _get_serializer_class (51-62)
  • update (75-83)
  • update (306-315)
  • update (455-467)
  • update (836-852)
  • update (1254-1268)
  • update (1505-1512)
backend/global_settings/models.py (1)
  • GlobalSettings (7-29)
backend/core/views.py (10)
  • get_serializer_class (498-513)
  • get_serializer_class (8545-8546)
  • get (669-676)
  • get (6247-6248)
  • retrieve (838-839)
  • retrieve (5106-5107)
  • update (578-606)
  • update (4679-4691)
  • update (4939-4952)
  • update (8440-8443)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (python)
🔇 Additional comments (2)
enterprise/backend/enterprise_core/serializers.py (2)

10-14: Enterprise feature-flag imports are wired correctly

Importing GlobalSettings and aliasing the community FeatureFlagsSerializer for extension in the enterprise layer looks consistent with the new usage below.


137-177: I'll help you verify the concerns raised in this review comment. Let me start by examining the codebase to understand the context better.
<function_calls>


#!/bin/bash

First, let's find the file and examine the serializer class

find . -name "serializers.py" -path "/enterprise_core/" | head -5


</function_calls>


#!/bin/bash

Get line count and view the relevant section of the file

wc -l enterprise/backend/enterprise_core/serializers.py


</function_calls>
<function_calls>


#!/bin/bash

View the FeatureFlagsSerializer and CommunityFeatureFlagSerializer

sed -n '100,200p' enterprise/backend/enterprise_core/serializers.py


</function_calls>
<function_calls>


#!/bin/bash

Find CommunityFeatureFlagSerializer definition

rg -n "class CommunityFeatureFlagSerializer" enterprise/backend/


</function_calls>

Let me search for the parent serializer and understand the full context:
<function_calls>


#!/bin/bash

Search for CommunityFeatureFlagSerializer in the entire repo

rg -n "CommunityFeatureFlagSerializer" --type py -A 20 | head -80


</function_calls>

Now let me check the GlobalSettings model to understand if "name" is actually a field:
<function_calls>


#!/bin/bash

Find GlobalSettings model definition

rg -n "class GlobalSettings" --type py -A 30 | head -60


</function_calls>

Let me also check how DRF handles the read_only_fields configuration:
<function_calls>


#!/bin/bash

Search for any tests or usages of FeatureFlagsSerializer

rg -n "FeatureFlagsSerializer" --type py -B 2 -A 5


</function_calls>

Let me get a clearer view of the complete serializers file around this area:
<function_calls>


#!/bin/bash

View lines 1-50 to see imports and parent class

sed -n '1,50p' enterprise/backend/enterprise_core/serializers.py


</function_calls>

<function_calls>


#!/bin/bash

View more of the file to find CommunityFeatureFlagSerializer

sed -n '50,140p' enterprise/backend/enterprise_core/serializers.py


</function_calls>

Now let me check what fields are actually on the GlobalSettings model:
<function_calls>


#!/bin/bash

Find GlobalSettings model

find . -name "models.py" -path "/enterprise_core/" | xargs grep -l "GlobalSettings"


</function_calls>

<function_calls>


#!/bin/bash

View the GlobalSettings model

rg -n "class GlobalSettings" --type py -A 50 | head -100


</function_calls>

Let me check if there are any actual import-time errors or test failures related to this:
<function_calls>


#!/bin/bash

Search for test files that might test this serializer

find . -path "/test" -name "*.py" -type f | xargs grep -l "FeatureFlagsSerializer|GlobalSettings" 2>/dev/null | head -5


</function_calls>

Let me view the complete serializer class definition more carefully:
<function_calls>


#!/bin/bash

Get the full context around lines 137-177

sed -n '130,185p' enterprise/backend/enterprise_core/serializers.py


</function_calls>

Now let me verify the exact field definitions on GlobalSettings:
<function_calls>


#!/bin/bash

Search for GlobalSettings definition more thoroughly

find . -type f -name "*.py" | xargs grep -n "class GlobalSettings" 2>/dev/null


</function_calls>

<function_calls>


#!/bin/bash

View the models.py file containing GlobalSettings

rg -n "class GlobalSettings" -A 60 --type py | head -100


</function_calls>

Now let me check the CommunityFeatureFlagSerializer to understand the parent class:
<function_calls>


#!/bin/bash

Get full CommunityFeatureFlagSerializer

rg -n "class CommunityFeatureFlagSerializer" -A 80 --type py


</function_calls>

Let me search more specifically in the enterprise_core directory:
<function_calls>


#!/bin/bash

List all python files in enterprise_core

find enterprise/backend/enterprise_core -name "*.py" -type f


</function_calls>

<function_calls>


#!/bin/bash

View models.py to find GlobalSettings

wc -l enterprise/backend/enterprise_core/models.py
sed -n '1,50p' enterprise/backend/enterprise_core/models.py


</function_calls>

<function_calls>


#!/bin/bash

Search for GlobalSettings in models

grep -n "class GlobalSettings" enterprise/backend/enterprise_core/models.py


</function_calls>

<function_calls>


#!/bin/bash

Get GlobalSettings class definition

sed -n '1,100p' enterprise/backend/enterprise_core/models.py | grep -A 50 "class GlobalSettings"


</function_calls>

<function_calls>


#!/bin/bash

Let's get the full serializers file to understand the complete context

head -300 enterprise/backend/enterprise_core/serializers.py | tail -200


</function_calls>

Let me try a different approach to see the full serializer file:
<function_calls>


#!/bin/bash

Check file size and view strategic sections

wc -l enterprise/backend/enterprise_core/serializers.py
echo "=== Lines 1-60 ==="
head -60 enterprise/backend/enterprise_core/serializers.py


</function_calls>

<function_calls>


#!/bin/bash

Continue viewing

echo "=== Lines 60-140 ==="
sed -n '60,140p' enterprise/backend/enterprise_core/serializers.py


</function_calls>

<function_calls>


#!/bin/bash

View the specific section mentioned in the review

echo "=== Lines 137-185 ==="
sed -n '137,185p' enterprise/backend/enterprise_core/serializers.py


</function_calls>

Now let me check the GlobalSettings model:
<function_calls>


#!/bin/bash

Get info about GlobalSettings

echo "=== Searching for GlobalSettings ==="
grep -n "class GlobalSettings|name.*=" enterprise/backend/enterprise_core/models.py | head -20


</function_calls>

<function_calls>


#!/bin/bash

View the GlobalSettings model definition with more context

wc -l enterprise/backend/enterprise_core/models.py
sed -n '1,150p' enterprise/backend/enterprise_core/models.py


</function_calls>

Let me also check if there are any issues or tests currently failing:
<function_calls>


#!/bin/bash

Check for imports related to GlobalSettings and FeatureFlagsSerializer

python3 << 'EOF'

Let's check if we can understand the structure by examining the file directly

with open('enterprise/backend/enterprise_core/serializers.py', 'r') as f:
content = f.read()

Find CommunityFeatureFlagSerializer

import re
community_match = re.search(r'class CommunityFeatureFlagSerializer.*?(?=\nclass |\Z)', content, re.DOTALL)
if community_match:
print("=== CommunityFeatureFlagSerializer ===")
print(community_match.group(0)[:1500])

print("\n=== Looking for 'name' field references ===")
for i, line in enumerate(content.split('\n')[130:190], start=131):
print(f"{i}: {line}")
EOF


</function_calls>

<function_calls>


#!/bin/bash

Let me read the entire serializers file to understand the class hierarchy

cat enterprise/backend/enterprise_core/serializers.py


</function_calls>

Perfect! Now I have all the context. Let me verify the GlobalSettings model:
<function_calls>


#!/bin/bash

Check GlobalSettings model

cat enterprise/backend/enterprise_core/models.py | head -200


</function_calls>

Let me verify if this configuration actually causes errors:
<function_calls>


#!/bin/bash

Search for test files that import this serializer

find . -path "/test" -name "*.py" -type f | xargs grep -l "FeatureFlagsSerializer" 2>/dev/null


</function_calls>

<function_calls>


#!/bin/bash

Check if there are any validation or assertion errors in DRF when using this config

Let's search for related issues or documentation

rg -n "read_only_fields" enterprise/backend/enterprise_core/serializers.py


</function_calls>

Let me check if there are other serializers with similar patterns to understand the intent:
<function_calls>


#!/bin/bash

Search for similar patterns in other serializers

rg -B 5 "read_only_fields.=.[" enterprise/backend/enterprise_core/serializers.py


</function_calls>

Let me verify DRF's behavior with a Python check:
<function_calls>


#!/bin/bash

Check Django REST Framework documentation/behavior on this

python3 << 'EOF'

DRF will NOT raise an error immediately, but it WILL cause issues

Let's document what happens:

print("=== DRF ModelSerializer read_only_fields behavior ===")
print()
print("When a field is in read_only_fields but NOT in fields:")
print("- DRF will attempt to create the field automatically")
print("- If the model field exists, DRF will auto-generate it as read-only")
print("- However, this is undocumented behavior and can lead to issues")
print()
print("The proper pattern is:")
print("1. Include 'name' in fields if you want it read-only")
print("2. OR remove 'name' from read_only_fields if you don't want to expose it")
print()
print("Current code in FeatureFlagsSerializer:")
print("- Excludes 'name' from fields (unlike parent CommunityFeatureFlagSerializer)")
print("- Includes 'name' in read_only_fields (same as parent)")
print("- This is inconsistent and will cause DRF to handle it unpredictably")
EOF


</function_calls>

Let me check the actual behavior by looking for usages:
<function_calls>


#!/bin/bash

Search for any places where FeatureFlagsSerializer is instantiated/used

rg "FeatureFlagsSerializer" --type py -B 3 -A 3


</function_calls>

<function_calls>


#!/bin/bash

Check if there are tests for this serializer

find . -path "test" -name "*.py" | xargs grep -l "FeatureFlagsSerializer" 2>/dev/null


</function_calls>

<function_calls>


#!/bin/bash

Let's check what the actual parent CommunityFeatureFlagSerializer fields are

python3 << 'EOF'

From the serializers.py we saw, let's extract the exact fields

import re

with open('enterprise/backend/enterprise_core/serializers.py', 'r') as f:
content = f.read()

Find CommunityFeatureFlagSerializer class

community_pattern = r'class CommunityFeatureFlagSerializer.?class\s+\w+Serializer'
match = re.search(community_pattern, content, re.DOTALL)
if match:
community_class = match.group(0)
# Extract fields from Meta
fields_match = re.search(r'fields\s
=\s*[(.*?)]', community_class, re.DOTALL)
if fields_match:
print("=== CommunityFeatureFlagSerializer.Meta.fields ===")
fields_str = fields_match.group(1)
fields = [f.strip().strip('"').strip("'") for f in fields_str.split(',') if f.strip()]
for field in fields:
print(f" - {field}")
print(f"Total: {len(fields)} fields")
print(f"Contains 'name': {'name' in fields}")

print("\n=== FeatureFlagsSerializer.Meta.fields ===")
feature_pattern = r'class FeatureFlagsSerializer.?read_only_fields'
match = re.search(feature_pattern, content, re.DOTALL)
if match:
feature_class = match.group(0)
fields_match = re.search(r'fields\s
=\s*[(.*?)]', feature_class, re.DOTALL)
if fields_match:
fields_str = fields_match.group(1)
fields = [f.strip().strip('"').strip("'") for f in fields_str.split(',') if f.strip()]
for field in fields:
print(f" - {field}")
print(f"Total: {len(fields)} fields")
print(f"Contains 'name': {'name' in fields}")
EOF


</function_calls>

Excellent! Now I have enough information. Let me verify one more thing about the campaigns default:
<function_calls>


#!/bin/bash

Check if campaigns field defaults are used anywhere or documented

rg "campaigns" enterprise/backend/enterprise_core/ -B 2 -A 2


</function_calls>

Based on my thorough verification of the codebase, I can now provide the final rewritten review comment:

Fix Meta.fields / read_only_fields mismatch and verify campaigns default

Two issues found:

  • read_only_fields = ["name"] but "name" is not in fields. The parent CommunityFeatureFlagSerializer includes "name" in its fields, but this subclass excludes it. For a ModelSerializer, DRF expects any field in read_only_fields to also be in fields. Fix by adding "name" to fields:

         fields = [
  •      "name",
         "xrays",
         "incidents",
         "tasks",
         "risk_acceptances",
         "exceptions",
         "follow_up",
         "ebiosrm",
         "scoring_assistant",
         "vulnerabilities",
         "compliance",
         "tprm",
         "privacy",
         "experimental",
         "inherent_risk",
         "organisation_objectives",
         "organisation_issues",
         "quantitative_risk_studies",
         "terminologies",
         "bia",
         "project_management",
         "contracts",
         "reports",
         "validation_flows",
         "campaigns",
     ]
    
    
    
  • campaigns has default=True, which means the feature is enabled by default when the key is absent from value. Confirm this rollout strategy is intentional.

Additionally, to avoid field duplication with CommunityFeatureFlagSerializer, consider subclassing its Meta and appending only "campaigns" to the parent fields list.

Likely an incorrect or invalid review comment.

@Axxiar Axxiar marked this pull request as ready for review December 1, 2025 10:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
backend/global_settings/views.py (1)

23-34: Consider calling super().__init__() and removing the unused action parameter.

  1. The __init__ method duplicates the parent class's logic. Since GlobalSettingsSerializerFactory extends SerializerFactory, you can delegate to the parent constructor.

  2. The action parameter in get_serializer is never used, which is misleading. If a single serializer is intentionally used for all actions, remove the parameter to clarify intent.

 class GlobalSettingsSerializerFactory(SerializerFactory):
     """Factory to get a serializer class from a list of modules.
     Attributes:
     modules (list): List of module names to search for the serializer.
     """

-    def __init__(self, *modules: str):
-        # Reverse to prioritize later modules
-        self.modules = list(reversed(modules))
+    def __init__(self, *modules: str):
+        super().__init__(*modules)

-    def get_serializer(self, base_name: str, action: str = "default"):
+    def get_serializer(self, base_name: str):
         return self._get_serializer_class(f"{base_name}Serializer")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2bb934 and a24e5dc.

📒 Files selected for processing (3)
  • backend/global_settings/views.py (3 hunks)
  • frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte (3 hunks)
  • frontend/src/lib/utils/schemas.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/lib/utils/schemas.ts
🧰 Additional context used
🧬 Code graph analysis (1)
backend/global_settings/views.py (2)
backend/core/serializers.py (9)
  • SerializerFactory (30-62)
  • get_serializer (41-49)
  • _get_serializer_class (51-62)
  • update (75-83)
  • update (306-315)
  • update (455-467)
  • update (836-852)
  • update (1254-1268)
  • update (1505-1512)
backend/core/views.py (10)
  • get_serializer_class (553-568)
  • get_serializer_class (8631-8632)
  • get (761-768)
  • get (6333-6334)
  • retrieve (930-931)
  • retrieve (5192-5193)
  • update (661-693)
  • update (4764-4776)
  • update (5025-5038)
  • update (8526-8529)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build (3.12)
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: build_community_frontend
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: test (3.12)
🔇 Additional comments (2)
backend/global_settings/views.py (1)

7-20: Imports look appropriate for the new functionality.

The imports for SerializerFactory, Django settings, and structlog are correctly added to support the new dynamic serializer resolution pattern.

frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte (1)

26-26: New campaigns flag entry is consistent

The new { field: 'campaigns', label: m.campaigns() } entry matches the existing feature-flag pattern (string key + localized label) and integrates cleanly into the config array. As long as the campaigns flag is wired through the schema/backend (which the rest of the PR appears to cover), this looks good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @backend/global_settings/views.py:
- Line 84: The serializer is being instantiated directly using
self.get_serializer_class()(instance) without serializer context; update these
to use DRF's context by either calling self.get_serializer(instance) (preferred)
or self.get_serializer_class()(instance, context=self.get_serializer_context())
so the serializer receives request/view/format information—apply the same change
for the other occurrence noted (the similar instantiation on the later line).
- Around line 23-34: GlobalSettingsSerializerFactory currently reimplements the
parent __init__ (duplicating SerializerFactory behavior) and declares an unused
action parameter on get_serializer; update this class to remove the redundant
__init__ (or call super().__init__(*modules) if you need reversal behavior
documented), eliminate the unused action parameter from get_serializer (or use
it to select serializers) and have get_serializer delegate to
_get_serializer_class(f"{base_name}Serializer"); also update the class docstring
to explain how this factory differs from SerializerFactory (e.g., module
precedence or naming convention) so the intent is clear.
- Around line 66-80: get_serializer_class currently passes an action to
GlobalSettingsSerializerFactory.get_serializer but the factory ignores it,
causing misleading behavior; fix by making
GlobalSettingsSerializerFactory.get_serializer accept and use the action
parameter (update its signature and resolution logic so it considers action when
building the serializer name for a base like "FeatureFlags", e.g. selecting
different serializer variants per action) and keep the logging of action in
get_serializer_class; alternatively, if no action-based serializers exist,
remove the action argument from the get_serializer call and from the logger to
avoid confusion—refer to get_serializer_class and
GlobalSettingsSerializerFactory.get_serializer when applying the change.

In @frontend/messages/en.json:
- Line 2904: The localization key "campaignsDescription" is defined only in
en.json (and fr.json) but missing from many other locale files; add
"campaignsDescription": "Audit campaigns management" (or the correct translated
string) to each missing locale file (ar.json, cs.json, da.json, de.json,
el.json, es.json, hi.json, hr.json, hu.json, id.json, it.json, nl.json, pl.json,
pt.json, ro.json, sv.json, tr.json, uk.json, ur.json, zh.json) or
implement/verify a fallback lookup that returns the English value when the key
is absent (ensure the i18n loader or getString function checks fallback locales
and unit tests cover the missing-key behavior).
🧹 Nitpick comments (2)
frontend/messages/en.json (1)

2904-2904: Wording is understandable, but slightly awkward in English.

Consider a more natural string like “Audit campaign management” or “Manage audit campaigns”, depending on the UI tone.

frontend/messages/fr.json (1)

2818-2818: FR translation is good; consider slightly more idiomatic phrasing.

If you want it to read more naturally: “Gestion des campagnes d’audit”.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bf6982 and 0d82104.

📒 Files selected for processing (6)
  • backend/global_settings/views.py
  • frontend/messages/en.json
  • frontend/messages/fr.json
  • frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte
  • frontend/src/lib/utils/schemas.ts
  • frontend/src/lib/utils/sidebar-config.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/lib/utils/schemas.ts
  • frontend/src/lib/utils/sidebar-config.ts
  • frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
backend/global_settings/views.py (2)
backend/core/serializers.py (9)
  • SerializerFactory (30-62)
  • get_serializer (41-49)
  • _get_serializer_class (51-62)
  • update (75-83)
  • update (306-315)
  • update (463-478)
  • update (877-893)
  • update (1305-1319)
  • update (1564-1580)
backend/core/views.py (10)
  • get_serializer_class (666-681)
  • get_serializer_class (9190-9191)
  • get (1157-1164)
  • get (6830-6831)
  • retrieve (1326-1327)
  • retrieve (5665-5666)
  • update (815-850)
  • update (5235-5247)
  • update (5497-5510)
  • update (9080-9083)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: test (3.12)
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
backend/global_settings/views.py (1)

7-7: LGTM! Imports align with the serializer factory pattern.

The imports correctly support the new GlobalSettingsSerializerFactory and logging infrastructure.

Also applies to: 16-16, 18-20

Comment on lines +23 to +34
class GlobalSettingsSerializerFactory(SerializerFactory):
"""Factory to get a serializer class from a list of modules.
Attributes:
modules (list): List of module names to search for the serializer.
"""

def __init__(self, *modules: str):
# Reverse to prioritize later modules
self.modules = list(reversed(modules))

def get_serializer(self, base_name: str, action: str = "default"):
return self._get_serializer_class(f"{base_name}Serializer")
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor to eliminate code duplication and clarify the factory's purpose.

The __init__ method duplicates the parent class logic exactly, and the action parameter in get_serializer is unused. Additionally, the docstring doesn't explain how this factory differs from the parent SerializerFactory.

♻️ Proposed refactor
 class GlobalSettingsSerializerFactory(SerializerFactory):
-    """Factory to get a serializer class from a list of modules.
-    Attributes:
-    modules (list): List of module names to search for the serializer.
-    """
-
-    def __init__(self, *modules: str):
-        # Reverse to prioritize later modules
-        self.modules = list(reversed(modules))
+    """Factory for GlobalSettings serializers.
+    
+    Unlike the parent SerializerFactory, this returns a single serializer
+    for all actions rather than separate Read/Write serializers.
+    """
 
-    def get_serializer(self, base_name: str, action: str = "default"):
+    def get_serializer(self, base_name: str, action: str = None):
         return self._get_serializer_class(f"{base_name}Serializer")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class GlobalSettingsSerializerFactory(SerializerFactory):
"""Factory to get a serializer class from a list of modules.
Attributes:
modules (list): List of module names to search for the serializer.
"""
def __init__(self, *modules: str):
# Reverse to prioritize later modules
self.modules = list(reversed(modules))
def get_serializer(self, base_name: str, action: str = "default"):
return self._get_serializer_class(f"{base_name}Serializer")
class GlobalSettingsSerializerFactory(SerializerFactory):
"""Factory for GlobalSettings serializers.
Unlike the parent SerializerFactory, this returns a single serializer
for all actions rather than separate Read/Write serializers.
"""
def get_serializer(self, base_name: str, action: str = None):
return self._get_serializer_class(f"{base_name}Serializer")
🤖 Prompt for AI Agents
In @backend/global_settings/views.py around lines 23 - 34,
GlobalSettingsSerializerFactory currently reimplements the parent __init__
(duplicating SerializerFactory behavior) and declares an unused action parameter
on get_serializer; update this class to remove the redundant __init__ (or call
super().__init__(*modules) if you need reversal behavior documented), eliminate
the unused action parameter from get_serializer (or use it to select
serializers) and have get_serializer delegate to
_get_serializer_class(f"{base_name}Serializer"); also update the class docstring
to explain how this factory differs from SerializerFactory (e.g., module
precedence or naming convention) so the intent is clear.

Comment on lines +66 to +80
def get_serializer_class(self, **kwargs):
serializer_factory = GlobalSettingsSerializerFactory(
self.serializers_module, settings.MODULE_PATHS.get("serializers", [])
)
serializer_class = serializer_factory.get_serializer(
"FeatureFlags", kwargs.get("action", "default")
)
logger.debug(
"Serializer class",
serializer_class=serializer_class,
action=kwargs.get("action", self.action),
viewset=self,
module_paths=settings.MODULE_PATHS,
)
return serializer_class
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading action parameter - it's passed but not used.

The action parameter is retrieved (line 71) and logged (line 76), which suggests it influences serializer selection. However, GlobalSettingsSerializerFactory.get_serializer ignores the action and always returns "{base_name}Serializer".

This creates confusion for future maintainers who might assume the action parameter affects serializer resolution.

💡 Suggested clarification
     def get_serializer_class(self, **kwargs):
         serializer_factory = GlobalSettingsSerializerFactory(
             self.serializers_module, settings.MODULE_PATHS.get("serializers", [])
         )
         serializer_class = serializer_factory.get_serializer(
-            "FeatureFlags", kwargs.get("action", "default")
+            "FeatureFlags"
         )
         logger.debug(
             "Serializer class",
             serializer_class=serializer_class,
-            action=kwargs.get("action", self.action),
             viewset=self,
             module_paths=settings.MODULE_PATHS,
         )
         return serializer_class
🤖 Prompt for AI Agents
In @backend/global_settings/views.py around lines 66 - 80, get_serializer_class
currently passes an action to GlobalSettingsSerializerFactory.get_serializer but
the factory ignores it, causing misleading behavior; fix by making
GlobalSettingsSerializerFactory.get_serializer accept and use the action
parameter (update its signature and resolution logic so it considers action when
building the serializer name for a base like "FeatureFlags", e.g. selecting
different serializer variants per action) and keep the logging of action in
get_serializer_class; alternatively, if no action-based serializers exist,
remove the action argument from the get_serializer call and from the logger to
avoid confusion—refer to get_serializer_class and
GlobalSettingsSerializerFactory.get_serializer when applying the change.

def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
serializer = self.get_serializer_class()(instance)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pass context when instantiating serializers.

The serializers are instantiated directly without passing context. DRF's get_serializer method typically provides context containing request, view, and format, which serializers may need for URL generation, permission checks, or accessing request-specific data.

This is inconsistent with GeneralSettingsViewSet (lines 109, 114), which uses self.get_serializer(instance).

🔧 Recommended fix to align with DRF patterns
     def retrieve(self, request, *args, **kwargs):
         instance = self.get_object()
-        serializer = self.get_serializer_class()(instance)
+        serializer = self.get_serializer_class()(
+            instance, context=self.get_serializer_context()
+        )
         return Response(serializer.data)
 
     def update(self, request, *args, **kwargs):
         instance = self.get_object()
-        serializer = self.get_serializer_class()(instance, data=request.data)
+        serializer = self.get_serializer_class()(
+            instance, data=request.data, context=self.get_serializer_context()
+        )
         serializer.is_valid(raise_exception=True)
         serializer.save()
         return Response(serializer.data)

Also applies to: 89-89

🤖 Prompt for AI Agents
In @backend/global_settings/views.py at line 84, The serializer is being
instantiated directly using self.get_serializer_class()(instance) without
serializer context; update these to use DRF's context by either calling
self.get_serializer(instance) (preferred) or
self.get_serializer_class()(instance, context=self.get_serializer_context()) so
the serializer receives request/view/format information—apply the same change
for the other occurrence noted (the similar instantiation on the later line).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/messages/de.json (1)

2932-2933: New campaignsDescription looks correct; consider German compounding if you want to be more idiomatic.
Current "Verwaltung von Audit-Kampagnen" is fine; an alternative could be "Verwaltung von Auditkampagnen" (stylistic preference).

Optional wording tweak
-	"campaignsDescription": "Verwaltung von Audit-Kampagnen"
+	"campaignsDescription": "Verwaltung von Auditkampagnen"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 042826c and 8cbadb4.

📒 Files selected for processing (20)
  • frontend/messages/ar.json
  • frontend/messages/cs.json
  • frontend/messages/da.json
  • frontend/messages/de.json
  • frontend/messages/el.json
  • frontend/messages/es.json
  • frontend/messages/hi.json
  • frontend/messages/hr.json
  • frontend/messages/hu.json
  • frontend/messages/id.json
  • frontend/messages/it.json
  • frontend/messages/nl.json
  • frontend/messages/pl.json
  • frontend/messages/pt.json
  • frontend/messages/ro.json
  • frontend/messages/sv.json
  • frontend/messages/tr.json
  • frontend/messages/uk.json
  • frontend/messages/ur.json
  • frontend/messages/zh.json
✅ Files skipped from review due to trivial changes (2)
  • frontend/messages/hi.json
  • frontend/messages/pl.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build_community_frontend
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: build (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: test (3.12)
  • GitHub Check: Analyze (python)
🔇 Additional comments (17)
frontend/messages/uk.json (1)

1581-1582: LGTM!

The Ukrainian translation "Керування кампаніями аудиту" is appropriate, and the JSON formatting with the trailing comma on the preceding entry is correct.

frontend/messages/hr.json (1)

2031-2032: LGTM!

The new campaignsDescription translation key and trailing comma adjustment are correctly implemented. The Croatian translation "Upravljanje kampanjama revizije" appropriately conveys "Management of audit campaigns" in alignment with the new campaigns feature flag.

frontend/messages/ro.json (1)

1063-1064: LGTM!

The new campaignsDescription translation key is correctly added with proper JSON syntax (trailing comma on the previous line). The Romanian translation "Gestionarea campaniilor de audit" is grammatically appropriate and follows the existing naming conventions for feature description keys.

frontend/messages/sv.json (1)

1057-1058: LGTM!

The Swedish translation for the new campaigns feature flag is correctly added. The trailing comma adjustment on libraryFilteringLabels maintains valid JSON structure, and the translation "Hantering av revisionskampanjer" (Management of audit campaigns) is appropriate.

frontend/messages/pt.json (1)

1066-1067: LGTM!

The new campaignsDescription translation key is correctly added with appropriate Portuguese text. The trailing comma adjustment on the preceding line maintains valid JSON syntax.

frontend/messages/tr.json (1)

1799-1800: LGTM!

The new campaignsDescription translation key is correctly added with proper JSON syntax. The Turkish translation "Denetim kampanyaları yönetimi" (Audit campaigns management) aligns with the campaigns feature flag being introduced in this PR.

frontend/messages/hu.json (1)

1066-1067: LGTM!

The Hungarian translation addition is correct and properly formatted. The trailing comma on libraryFilteringLabels is appropriate since campaignsDescription is now the last entry in the JSON object. The translation "Auditkampányok kezelése" (Manage audit campaigns) aligns well with the new campaigns feature flag being introduced.

frontend/messages/nl.json (1)

2931-2932: LGTM!

The new campaignsDescription translation key is correctly added with proper JSON syntax (trailing comma on the preceding entry). The Dutch translation "Beheer van auditcampagnes" is appropriate and the key naming follows the established <feature>Description pattern used throughout this file.

frontend/messages/el.json (1)

1763-1764: LGTM!

The new campaignsDescription translation entry is correctly added with proper Greek text, and the JSON structure is maintained with the appropriate trailing comma on the preceding line.

frontend/messages/zh.json (1)

2881-2881: LGTM!

The new translation key campaignsDescription with value 审计活动管理 (Audit campaign management) is consistent with existing campaign-related translations in the file (e.g., campaigns: "活动", campaign: "活动"). JSON syntax is correct.

frontend/messages/id.json (1)

1251-1252: Looks good: JSON comma placement + new campaignsDescription key are correct.
This keeps the object valid while adding the new translation entry.

frontend/messages/it.json (1)

1443-1445: LGTM: campaignsDescription added (IT).

frontend/messages/ur.json (1)

1034-1035: Looks good: comma + campaignsDescription keep JSON valid (UR).

frontend/messages/ar.json (1)

1034-1035: Looks good: comma + campaignsDescription keep JSON valid (AR).

frontend/messages/es.json (1)

2931-2932: campaignsDescription translation looks good and consistent.

frontend/messages/cs.json (1)

1054-1055: LGTM!

The Czech translation for the new campaigns feature flag description is appropriate, and the JSON syntax is correct.

frontend/messages/da.json (1)

1346-1347: LGTM!

The Danish translation for the new campaigns feature flag description is appropriate, and the JSON syntax is correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants