-
Notifications
You must be signed in to change notification settings - Fork 594
feat: add feature flag for campaigns #2944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ 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. Comment |
nas-tabchiche
left a comment
There was a problem hiding this 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
There was a problem hiding this 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: SimplifyGlobalSettingsSerializerFactoryand avoid unused parameters
GlobalSettingsSerializerFactorymostly duplicatesSerializerFactory:
__init__is identical to the base implementation; you can simply delegate tosuper().__init__(*modules)and avoid repeating the logic/comment.get_serializer(self, base_name: str, action: str = "default")ignores theactionargument 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’sget_serializer/context and guard against invalid module defaultsA few points around
FeatureFlagsViewSet:
In
retrieve/updateyou’re doingself.get_serializer_class()(instance, ...)directly. This bypasses DRF’sget_serializer, so:
- No
context(e.g.{"request": request, "view": self}) is passed.- You also skip
perform_update()inupdate.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,GlobalSettingsSerializerFactorywould receive a non‑string module andimportlib.import_modulewould fail. Either ensure"serializers"is always defined inMODULE_PATHSor filter out falsy/non‑string values before passing them into the factory.Since your
GlobalSettingsSerializerFactory.get_serializerignores itsactionargument, passingkwargs.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
📒 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 correctlyImporting
GlobalSettingsand aliasing the communityFeatureFlagsSerializerfor 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/bashFirst, let's find the file and examine the serializer class
find . -name "serializers.py" -path "/enterprise_core/" | head -5
</function_calls>
#!/bin/bashGet line count and view the relevant section of the file
wc -l enterprise/backend/enterprise_core/serializers.py
</function_calls>
<function_calls>
#!/bin/bashView the FeatureFlagsSerializer and CommunityFeatureFlagSerializer
sed -n '100,200p' enterprise/backend/enterprise_core/serializers.py
</function_calls>
<function_calls>
#!/bin/bashFind 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/bashSearch 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/bashFind 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/bashSearch 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/bashView lines 1-50 to see imports and parent class
sed -n '1,50p' enterprise/backend/enterprise_core/serializers.py
</function_calls><function_calls>
#!/bin/bashView 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/bashFind GlobalSettings model
find . -name "models.py" -path "/enterprise_core/" | xargs grep -l "GlobalSettings"
</function_calls><function_calls>
#!/bin/bashView 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/bashSearch 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/bashGet 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/bashSearch for GlobalSettings definition more thoroughly
find . -type f -name "*.py" | xargs grep -n "class GlobalSettings" 2>/dev/null
</function_calls><function_calls>
#!/bin/bashView 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/bashGet 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/bashList all python files in enterprise_core
find enterprise/backend/enterprise_core -name "*.py" -type f
</function_calls><function_calls>
#!/bin/bashView 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/bashSearch for GlobalSettings in models
grep -n "class GlobalSettings" enterprise/backend/enterprise_core/models.py
</function_calls><function_calls>
#!/bin/bashGet GlobalSettings class definition
sed -n '1,100p' enterprise/backend/enterprise_core/models.py | grep -A 50 "class GlobalSettings"
</function_calls><function_calls>
#!/bin/bashLet'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/bashCheck 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/bashContinue viewing
echo "=== Lines 60-140 ==="
sed -n '60,140p' enterprise/backend/enterprise_core/serializers.py
</function_calls><function_calls>
#!/bin/bashView 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/bashGet 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/bashView 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/bashCheck 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/bashLet 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/bashCheck 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/bashSearch 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/bashCheck 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/bashSearch 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/bashCheck 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/bashSearch for any places where FeatureFlagsSerializer is instantiated/used
rg "FeatureFlagsSerializer" --type py -B 3 -A 3
</function_calls><function_calls>
#!/bin/bashCheck if there are tests for this serializer
find . -path "test" -name "*.py" | xargs grep -l "FeatureFlagsSerializer" 2>/dev/null
</function_calls><function_calls>
#!/bin/bashLet'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/bashCheck 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_fieldsmismatch and verify campaigns defaultTwo issues found:
read_only_fields = ["name"]but"name"is not infields. The parentCommunityFeatureFlagSerializerincludes"name"in its fields, but this subclass excludes it. For aModelSerializer, DRF expects any field inread_only_fieldsto also be infields. Fix by adding"name"tofields: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", ]
campaignshasdefault=True, which means the feature is enabled by default when the key is absent fromvalue. Confirm this rollout strategy is intentional.Additionally, to avoid field duplication with
CommunityFeatureFlagSerializer, consider subclassing itsMetaand appending only"campaigns"to the parent fields list.Likely an incorrect or invalid review comment.
There was a problem hiding this 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 callingsuper().__init__()and removing the unusedactionparameter.
The
__init__method duplicates the parent class's logic. SinceGlobalSettingsSerializerFactoryextendsSerializerFactory, you can delegate to the parent constructor.The
actionparameter inget_serializeris 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
📒 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, Djangosettings, andstructlogare correctly added to support the new dynamic serializer resolution pattern.frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte (1)
26-26: Newcampaignsflag entry is consistentThe 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 thecampaignsflag is wired through the schema/backend (which the rest of the PR appears to cover), this looks good.
frontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.svelte
Show resolved
Hide resolved
There was a problem hiding this 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
📒 Files selected for processing (6)
backend/global_settings/views.pyfrontend/messages/en.jsonfrontend/messages/fr.jsonfrontend/src/lib/components/Forms/ModelForm/FeatureFlagsSettingForm.sveltefrontend/src/lib/utils/schemas.tsfrontend/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
| 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") |
There was a problem hiding this comment.
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.
| 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
There was a problem hiding this 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: NewcampaignsDescriptionlooks 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
📒 Files selected for processing (20)
frontend/messages/ar.jsonfrontend/messages/cs.jsonfrontend/messages/da.jsonfrontend/messages/de.jsonfrontend/messages/el.jsonfrontend/messages/es.jsonfrontend/messages/hi.jsonfrontend/messages/hr.jsonfrontend/messages/hu.jsonfrontend/messages/id.jsonfrontend/messages/it.jsonfrontend/messages/nl.jsonfrontend/messages/pl.jsonfrontend/messages/pt.jsonfrontend/messages/ro.jsonfrontend/messages/sv.jsonfrontend/messages/tr.jsonfrontend/messages/uk.jsonfrontend/messages/ur.jsonfrontend/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
campaignsDescriptiontranslation 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
campaignsDescriptiontranslation 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
libraryFilteringLabelsmaintains 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
campaignsDescriptiontranslation 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
campaignsDescriptiontranslation 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
libraryFilteringLabelsis appropriate sincecampaignsDescriptionis 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
campaignsDescriptiontranslation 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>Descriptionpattern used throughout this file.frontend/messages/el.json (1)
1763-1764: LGTM!The new
campaignsDescriptiontranslation 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
campaignsDescriptionwith 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 + newcampaignsDescriptionkey are correct.
This keeps the object valid while adding the new translation entry.frontend/messages/it.json (1)
1443-1445: LGTM:campaignsDescriptionadded (IT).frontend/messages/ur.json (1)
1034-1035: Looks good: comma +campaignsDescriptionkeep JSON valid (UR).frontend/messages/ar.json (1)
1034-1035: Looks good: comma +campaignsDescriptionkeep JSON valid (AR).frontend/messages/es.json (1)
2931-2932:campaignsDescriptiontranslation 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.
Summary by CodeRabbit
New Features
Enhancements
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.