Skip to content

Conversation

@ab-smith
Copy link
Contributor

@ab-smith ab-smith commented Nov 7, 2025

  • support is_critical on assets
  • show reverse link to solutions on assets
  • add legal identifiers to entities (LEI, EUID, etc.)
  • contract object (linking entities, solutions and evidences)
  • define our own legal identifiers
  • extra fields on Entity, Solution and Contract
  • b_01.01
  • b_01.02
  • b_01.03
  • b_02.01
  • b_02.02
  • b_02.03
  • b_03.01
  • b_04.01
  • b_03.02
  • b_03.03
  • b_05.01
  • b_05.02
  • b_06.01
  • b_07.01
  • b_99.01
  • backend engine for the zip generation
  • UI for report generation
  • Add capability to the data wizard to import entities, including legal identifiers
  • Add checks to legal identifier repetition
  • Add capability to import ROI on the data wizard

Summary by CodeRabbit

  • New Features

    • Contract management: create/list contracts with status, dates, owners, entities, evidences, solutions, UI, filters, status endpoint, and report tiles; DORA ROI export (ZIP).
    • Assets: mark as critical (is_critical), link to solutions, new asset filters and detail fields.
    • Entities: add/display legal identifiers with a dedicated form component; filter entities by contract.
    • i18n: added translations for contracts, legal identifiers, isCritical, and reports.
  • Permissions

    • Role permissions expanded to include contract CRUD and view access.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a Contract model and related migrations, extends Asset with is_critical and solutions linkage, adds Entity legal_identifiers and DORA fields, updates serializers, views, URLs, permissions, and many frontend forms, schemas, translations, lists, and a reports UI including DORA ROI export.

Changes

Cohort / File(s) Summary
Migrations
backend/core/migrations/0112_asset_is_critical.py, backend/tprm/migrations/0008_entity_legal_identifiers.py, backend/tprm/migrations/0009_contract.py, backend/tprm/migrations/0010_contract_annual_expense_contract_currency_and_more.py, backend/privacy/migrations/0010_alter_datacontractor_country_and_more.py
Add Asset.is_critical BooleanField; add Entity.legal_identifiers JSONField; create Contract model and add multiple Contract/Entity/Solution DORA-related fields and choices; alter privacy country fields to use expanded country choices.
Backend models & constants
backend/core/models.py, backend/tprm/models.py, backend/core/constants.py, backend/core/dora.py
Add is_critical to Asset (and Type); add DORA-related fields and Contract model; add CURRENCY_CHOICES and many country/currency choice expansions; new module core.dora with DORA choice lists.
Backend serializers
backend/core/serializers.py, backend/tprm/serializers.py
Expose/write solutions on Asset serializers; add contracts to Entity/Solution serializers; add ContractReadSerializer and ContractWriteSerializer; get_legal_identifiers formatter.
Backend views / urls / startup
backend/core/views.py, backend/tprm/views.py, backend/core/urls.py, backend/core/startup.py
Register ContractViewSet route; add contract filters and is_critical filter; add EntityViewSet actions (generate_dora_roi, choice endpoints) and ContractViewSet choice actions; extend role permission lists with contract codenames.
Frontend components & forms
frontend/src/lib/components/Forms/LegalIdentifierField.svelte, .../ModelForm/ContractForm.svelte, .../ModelForm/AssetForm.svelte, .../ModelForm/EntityForm.svelte, .../ModelForm.svelte, .../SolutionForm.svelte
New LegalIdentifierField component; new ContractForm; AssetForm adds is_critical checkbox and solutions AutocompleteSelect; EntityForm integrates legal identifiers and DORA fields; SolutionForm adds DORA ICT field.
Frontend schemas, CRUD, tables & types
frontend/src/lib/utils/schemas.ts, frontend/src/lib/utils/crud.ts, frontend/src/lib/utils/table.ts, frontend/src/lib/utils/types.ts
Add contractSchema and map; add is_critical and solutions to Asset schema; extend Entity/EntityAssessment schemas with legal_identifiers and DORA fields; add contracts model mapping and list/table filters (ASSET_IS_CRITICAL_FILTER, CONTRACT_STATUS_FILTER); include 'contracts' in URL models.
Frontend routes, pages & UI
frontend/messages/en.json, frontend/src/lib/components/SideBar/navData.ts, frontend/src/routes/(app)/(internal)/reports/+page.server.ts, .../+page.svelte, .../ReportTile.svelte, .../dora-roi/+server.ts, frontend/src/lib/components/DetailView/DetailView.svelte
Add translations for contracts, legal identifiers, DORA fields, isCritical; add Reports and Contracts nav items; new Reports page with tiles and DORA ROI download endpoint; adjust DetailView edit visibility.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant ContractModel
    participant RelatedModels
    rect rgba(76,175,80,0.06)
    User->>Frontend: Open Contracts list / apply filters
    Frontend->>API: GET /contracts/?...
    API->>ContractModel: Query contracts (with prefetch related)
    ContractModel->>RelatedModels: Load entities, evidences, solutions, owner
    RelatedModels-->>ContractModel: Return related data
    ContractModel-->>API: Return serialized contracts
    API-->>Frontend: 200 OK (contracts)
    Frontend-->>User: Render contracts table
    end
Loading
sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant AssetModel
    participant SolutionModel
    rect rgba(33,150,243,0.06)
    User->>Frontend: Edit Asset (is_critical, solutions)
    Frontend->>API: PATCH /assets/{id}/ { is_critical, solutions }
    API->>AssetModel: Update is_critical and M2M
    API->>SolutionModel: Sync Asset↔Solution links
    AssetModel-->>API: Return updated Asset
    API-->>Frontend: 200 OK (updated Asset)
    Frontend-->>User: Show updated asset
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • Cross-app migration ordering and FK/M2M dependencies (tprm ↔ core ↔ iam ↔ AUTH_USER_MODEL).
    • Serializer M2M handling for Asset.solutions and Contract relations (write/read symmetry, nested vs PK lists).
    • EntityViewSet.generate_dora_roi memory/stream and Content-Disposition filename extraction.
    • Frontend schema ⇄ form bindings (cacheLock/formDataCache) for legal_identifiers, is_critical, solutions, and new contract form fields.
    • Large choice lists (country/currency) correctness and i18n keys alignment.

Possibly related PRs

Suggested labels

db-migration

Suggested reviewers

  • eric-intuitem

"I hopped through code and dug a hole,
Contracts and IDs now have a role,
Assets flagged in a blink,
Solutions link with a wink,
A rabbit cheers — schemas whole! 🐇"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: contracts management and additional concepts for DORA reports' clearly and specifically summarizes the main changes, accurately reflecting the contract management feature and DORA-related additions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

@ab-smith ab-smith changed the title dora reports feat: contracts management and DORA reports Nov 7, 2025
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: 2

🧹 Nitpick comments (4)
backend/core/models.py (1)

2152-2153: Use translated verbose_name (and optional help_text); consider a filtered index if heavily queried.

Prefer i18n like other fields and add concise help text. Indexing can wait unless you filter on it a lot.

Apply this change:

-    is_critical = models.BooleanField("is_critical", default=False)
+    is_critical = models.BooleanField(
+        verbose_name=_("Is critical"),
+        default=False,
+        help_text=_("Marks assets that are critical to operations"),
+    )

Optionally (PostgreSQL): add a partial index in a follow-up migration if WHERE is_critical = TRUE is common.

frontend/src/lib/components/SideBar/navData.ts (1)

284-288: Add explicit permissions to the contracts nav item for consistency.

Most gated items specify permissions; suggest ['view_contract'] here to align behavior.

 {
   name: 'contracts',
   fa_icon: 'fa-solid fa-file-contract',
-  href: '/contracts'
+  href: '/contracts',
+  permissions: ['view_contract']
 },
frontend/src/lib/utils/schemas.ts (1)

626-629: Tighten contract relation validation to UUIDs

Other schemas in this file validate relationship IDs with z.string().uuid().optional().array().optional(). Here we only enforce “string”, so a malformed ID (or even an empty string injected via dev tools) will pass client-side checks and fail later against the API. Please align these arrays with the established UUID pattern so the form catches bad inputs early.

-	owner: z.array(z.string().optional()).optional(),
-	entities: z.array(z.string().optional()).optional(),
-	evidences: z.array(z.string().optional()).optional(),
-	solutions: z.array(z.string().optional()).optional(),
+	owner: z.string().uuid().optional().array().optional(),
+	entities: z.string().uuid().optional().array().optional(),
+	evidences: z.string().uuid().optional().array().optional(),
+	solutions: z.string().uuid().optional().array().optional(),
backend/core/views.py (1)

662-679: Asset boolean filter addition looks good

Adding is_critical to AssetFilter enables straightforward boolean filtering; no RBAC impact. Optionally, if this field will be queried often, consider adding it to asset CSV exports and/or a partial index (WHERE is_critical = TRUE) in a follow-up migration for large datasets.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd05d56 and 56df9de.

📒 Files selected for processing (22)
  • backend/core/migrations/0112_asset_is_critical.py (1 hunks)
  • backend/core/models.py (1 hunks)
  • backend/core/serializers.py (3 hunks)
  • backend/core/startup.py (10 hunks)
  • backend/core/urls.py (2 hunks)
  • backend/core/views.py (2 hunks)
  • backend/tprm/migrations/0008_entity_legal_identifiers.py (1 hunks)
  • backend/tprm/migrations/0009_contract.py (1 hunks)
  • backend/tprm/models.py (4 hunks)
  • backend/tprm/serializers.py (4 hunks)
  • backend/tprm/views.py (3 hunks)
  • frontend/messages/en.json (4 hunks)
  • frontend/src/lib/components/Forms/LegalIdentifierField.svelte (1 hunks)
  • frontend/src/lib/components/Forms/ModelForm.svelte (2 hunks)
  • frontend/src/lib/components/Forms/ModelForm/AssetForm.svelte (2 hunks)
  • frontend/src/lib/components/Forms/ModelForm/ContractForm.svelte (1 hunks)
  • frontend/src/lib/components/Forms/ModelForm/EntityForm.svelte (2 hunks)
  • frontend/src/lib/components/SideBar/navData.ts (2 hunks)
  • frontend/src/lib/utils/crud.ts (5 hunks)
  • frontend/src/lib/utils/schemas.ts (4 hunks)
  • frontend/src/lib/utils/table.ts (4 hunks)
  • frontend/src/lib/utils/types.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-12T21:53:47.742Z
Learnt from: ab-smith
Repo: intuitem/ciso-assistant-community PR: 2699
File: backend/core/serializers.py:414-422
Timestamp: 2025-10-12T21:53:47.742Z
Learning: In the asset hierarchy for CISO Assistant (backend/core/serializers.py), both PRIMARY and SUPPORT assets can have child_assets (supporting assets). PRIMARY assets are at the top of the hierarchy and cannot have parent_assets, while SUPPORT assets can have both parents and children.

Applied to files:

  • backend/core/serializers.py
🧬 Code graph analysis (7)
backend/tprm/views.py (2)
backend/tprm/models.py (4)
  • Entity (18-63)
  • Solution (136-170)
  • Contract (173-238)
  • Status (178-182)
backend/core/views.py (17)
  • get (440-447)
  • get (5255-5256)
  • BaseModelViewSet (233-430)
  • status (1234-1235)
  • status (1503-1504)
  • status (2439-2440)
  • status (5846-5847)
  • status (5967-5968)
  • status (6001-6002)
  • status (6114-6115)
  • status (7444-7445)
  • status (7941-7942)
  • status (8110-8111)
  • status (8570-8571)
  • status (8708-8709)
  • status (9480-9481)
  • status (9491-9492)
frontend/src/lib/utils/schemas.ts (1)
backend/core/base_models.py (1)
  • NameDescriptionMixin (119-132)
backend/tprm/migrations/0009_contract.py (1)
backend/iam/models.py (2)
  • Folder (68-316)
  • get_root_folder_id (80-85)
backend/tprm/serializers.py (2)
backend/tprm/models.py (3)
  • Entity (18-63)
  • Solution (136-170)
  • Contract (173-238)
backend/core/serializer_fields.py (1)
  • FieldsRelatedField (26-84)
backend/core/serializers.py (2)
backend/tprm/models.py (1)
  • Solution (136-170)
backend/core/serializer_fields.py (1)
  • FieldsRelatedField (26-84)
backend/core/urls.py (1)
backend/tprm/views.py (1)
  • ContractViewSet (162-172)
backend/tprm/models.py (2)
backend/core/models.py (6)
  • FilteringLabelMixin (191-197)
  • Meta (113-114)
  • Meta (168-169)
  • Meta (196-197)
  • Meta (201-203)
  • Meta (1368-1370)
backend/iam/models.py (1)
  • FolderMixin (319-338)
🪛 Biome (2.1.2)
frontend/messages/en.json

[error] 57-57: The key associatedEntities was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)


[error] 212-212: The key startDate was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)


[error] 213-213: The key endDate was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

⏰ 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 (3.12)
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: build_community_frontend
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: test (3.12)
  • GitHub Check: Analyze (python)
🔇 Additional comments (7)
frontend/src/lib/components/SideBar/navData.ts (1)

267-267: Now let me verify the Contract model's Meta class to ensure it uses Django's default permissions:

Verified: 'view_contract' permission codename is correct.

Django automatically creates add, delete, change, and view permissions for each model when django.contrib.auth is in INSTALLED_APPS. For a model, Django creates permissions with codenames like 'add_post', 'change_post', 'delete_post', 'view_post' following the pattern <action>_<modelname_lowercase>.

The Contract model exists at backend/tprm/models.py:173, so Django will automatically generate view_contract as the codename. Your frontend addition of 'view_contract' to the permissions array is correct and aligns with Django's default naming convention.

frontend/src/lib/utils/types.ts (1)

68-69: All infrastructure confirmed — 'contracts' model is fully integrated.

✓ Schema (contractSchema)
✓ CRUD config with foreign/reverse-foreign keys
✓ Table configuration
✓ Form component (ContractForm.svelte)
✓ Backend model (Contract class)
✓ Permissions (view_contract)
✓ Routes via dynamic [model=urlmodel] matcher

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

5-5: LGTM! Clean integration of the legal identifiers field.

The LegalIdentifierField component is properly imported and integrated following the same patterns as other form fields. The props are correctly passed and the two-way binding with cachedValue is consistent with the form's caching mechanism.

Also applies to: 61-66

backend/core/serializers.py (2)

463-463: LGTM! Consistent with other many-to-many read fields.

The solutions field in AssetReadSerializer follows the established pattern for many-to-many relationships, using FieldsRelatedField(many=True) just like other related fields in this serializer.


394-398: Solutions field is correctly handled by DRF's automatic many-to-many processing.

After verification, the solutions field requires no explicit handling in create() and update() methods. Unlike support_assets (which has source="child_assets" and therefore needs explicit pop+set logic), the solutions field has no source override, meaning its field name directly matches the Asset model's many-to-many attribute. Django REST Framework automatically manages such many-to-many fields during object creation and updates without requiring manual intervention.

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

1-113: LGTM! Well-structured contract form following established patterns.

The ContractForm component is cleanly implemented with:

  • Consistent prop handling and caching mechanism
  • Appropriate field types for each data type (AutocompleteSelect for relations, TextField for dates/text, Select for status)
  • Proper multiple selection for relationship fields (owner, entities, solutions, evidences, filtering_labels)
  • Extra fields displayed for context (provider_entity for solutions, folder for evidences)
  • Consistent i18n label usage
  • Standard form binding and cache management

The implementation aligns well with the backend Contract model and follows the same patterns as other form components in the codebase.

backend/core/views.py (1)

5786-5801: The contracts field exists on Evidence as a valid reverse relation—no action needed

The Contract model in backend/tprm/models.py defines a ManyToManyField to Evidence with related_name="contracts", which automatically creates a reverse relation on Evidence. This allows django-filter to correctly expose contracts in filterset_fields without runtime errors.

Likely an incorrect or invalid review comment.

"associatedRepresentatives": "Associated representatives",
"associatedSolutions": "Associated solutions",
"associatedContracts": "Associated contracts",
"associatedEntities": "Associated entities",
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

Remove duplicate translation keys

associatedEntities, startDate, and endDate are already defined later in this file. Re-declaring them here triggers Biome’s noDuplicateObjectKeys error and, because JSON parsers keep the last value only, it makes the earlier definitions meaningless. Please drop the newly added duplicates and rely on the existing entries.

   "associatedContracts": "Associated contracts",
-  "associatedEntities": "Associated entities",
@@
-  "startDate": "Start date",
-  "endDate": "End date",

Also applies to: 212-213

🧰 Tools
🪛 Biome (2.1.2)

[error] 57-57: The key associatedEntities was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In frontend/messages/en.json around line 57 (and also lines 212-213), you added
duplicate translation keys (associatedEntities, startDate, endDate) that
conflict with existing entries later in the file; remove the duplicate entries
at these locations so only the original definitions remain, ensuring the JSON
object has unique keys and passes Biome's noDuplicateObjectKeys check and JSON
validation.

Comment on lines +60 to +75
function updateIdentifierType(oldType: string, newType: string) {
if (oldType === newType) return;
const copy = { ...legalIdentifiers };
const value = copy[oldType] || '';
// Remove old key
delete copy[oldType];
// Add with new key if it doesn't already exist
if (!copy[newType]) {
copy[newType] = value;
}
legalIdentifiers = copy;
}
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

Prevent data loss when switching identifier types

If a user switches an identifier to a type that is already present, the old entry is deleted and never re-added, so the identifier value disappears silently. Please guard against clashes before deleting the old key, or surface a validation message, so data is not lost.

 function updateIdentifierType(oldType: string, newType: string) {
-	if (oldType === newType) return;
-
-	const copy = { ...legalIdentifiers };
-	const value = copy[oldType] || '';
-
-	// Remove old key
-	delete copy[oldType];
-
-	// Add with new key if it doesn't already exist
-	if (!copy[newType]) {
-		copy[newType] = value;
-	}
-
-	legalIdentifiers = copy;
+	if (oldType === newType) return;
+
+	if (legalIdentifiers[newType] !== undefined) {
+		return;
+	}
+
+	const copy = { ...legalIdentifiers };
+	const value = copy[oldType] ?? '';
+
+	delete copy[oldType];
+	copy[newType] = value;
+
+	legalIdentifiers = copy;
 }
📝 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
function updateIdentifierType(oldType: string, newType: string) {
if (oldType === newType) return;
const copy = { ...legalIdentifiers };
const value = copy[oldType] || '';
// Remove old key
delete copy[oldType];
// Add with new key if it doesn't already exist
if (!copy[newType]) {
copy[newType] = value;
}
legalIdentifiers = copy;
}
function updateIdentifierType(oldType: string, newType: string) {
if (oldType === newType) return;
if (legalIdentifiers[newType] !== undefined) {
return;
}
const copy = { ...legalIdentifiers };
const value = copy[oldType] ?? '';
delete copy[oldType];
copy[newType] = value;
legalIdentifiers = copy;
}

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/src/lib/components/Forms/ModelForm/EntityForm.svelte (1)

54-64: Guard against nullable object values.

If a parent ever passes object = null, accessing object.builtin will throw before the condition runs. Optional chaining keeps the guard null-safe without changing behavior.(developer.mozilla.org)

-{#if !object.builtin}
+{#if !object?.builtin}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56df9de and 6b01138.

📒 Files selected for processing (4)
  • frontend/src/lib/components/DetailView/DetailView.svelte (1 hunks)
  • frontend/src/lib/components/Forms/ModelForm.svelte (3 hunks)
  • frontend/src/lib/components/Forms/ModelForm/EntityForm.svelte (3 hunks)
  • frontend/src/lib/utils/table.ts (5 hunks)
⏰ 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 (3.12)
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: build_community_frontend
  • 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)
  • GitHub Check: Analyze (python)
🔇 Additional comments (5)
frontend/src/lib/components/DetailView/DetailView.svelte (1)

266-267: Now I need to search the codebase for backend permission handling on these models:

Ensure backend permission checks are enforced for terminologies and entities models.

The frontend change bypasses UI-level permission validation (canEditObject) for these models, allowing the edit button to display regardless of user authorization. While frontend UI control is cosmetic, permission checks must be enforced at the backend view level—the API endpoints must validate that users have proper authorization before allowing PUT/PATCH/DELETE operations.

Verify that the backend views for terminologies and entities models properly enforce permission validation and do not implicitly grant edit access. DRF generic views should explicitly call check_object_permissions(request, obj) to enforce object-level permissions; confirm this is implemented in your backend views for these models.

frontend/src/lib/utils/table.ts (4)

837-844: LGTM! Well-structured filter definition.

The ASSET_IS_CRITICAL_FILTER follows the established pattern for boolean filters and correctly uses YES_NO_OPTIONS.


1310-1311: LGTM! Proper filter integration.

The is_critical filter is correctly added to the assets configuration alongside the existing asset_class filter.


1511-1518: Backend model and serializer verified—all required fields are present.

The Contract model (backend/tprm/models.py:173) correctly exposes all fields expected by the frontend table:

  • ref_id, name, description (inherited/defined in model)
  • status (TextField with Status choices enum)
  • start_date, end_date (DateField instances)
  • entities (ManyToManyField to Entity)

The ContractReadSerializer properly exposes these fields via exclude = [], ensuring no serialization issues. Field naming and types align with frontend expectations.


1019-1029: Backend endpoint verified and properly implemented.

The contracts/status endpoint exists as a DRF action on the ContractViewSet (backend/tprm/views.py:170-172) and correctly returns status choices via the model's Status.choices. The frontend configuration will work as expected.

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: 9

♻️ Duplicate comments (1)
frontend/messages/en.json (1)

57-57: Remove duplicate translation keys (previously flagged)

These duplicate keys were already identified in a previous review but remain unfixed. associatedEntities (line 57), startDate (line 212), and endDate (line 213) are declared again later in the file (lines 936, 927, 1686 respectively). JSON parsers keep only the last value, making these earlier definitions meaningless and triggering linter errors.

Apply this diff to remove the duplicates:

   "associatedContracts": "Associated contracts",
-  "associatedEntities": "Associated entities",
   "associatedTaskTemplates": "Associated tasks",
   "dueDate": "Due date",
-  "startDate": "Start date",
-  "endDate": "End date",
   "attachment": "Attachment",

Also applies to: 212-213

🧹 Nitpick comments (5)
frontend/src/routes/(app)/(internal)/reports/dora-roi/+server.ts (1)

19-24: Consider streaming for large files.

The current implementation loads the entire ZIP file into memory with await res.blob(). For large reports, this could cause memory pressure.

If DORA ROI reports can be large, consider streaming the response:

-	return new Response(await res.blob(), {
+	return new Response(res.body, {
 		headers: {
 			'Content-Type': 'application/zip',
 			'Content-Disposition': `attachment; filename="${fileName}"`
 		}
 	});

This passes the stream directly without buffering the entire file.

backend/tprm/views.py (2)

112-112: Use timezone-aware datetime for consistency.

Line 112 uses datetime.now() which is timezone-naive. Django typically uses timezone-aware datetimes when USE_TZ = True.

Apply this diff:

+from django.utils import timezone
+
         # Prepare response
         zip_buffer.seek(0)
-        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+        timestamp = timezone.now().strftime("%Y%m%d_%H%M%S")
         filename = f"DORA_ROI_{timestamp}.zip"

74-100: Consider memory efficiency for large datasets.

The current implementation loads all entities into memory at once. For organizations with thousands of entities, this could consume significant memory and slow down the response.

Consider implementing pagination or batch processing if you expect large numbers of entities:

  • Use .iterator(chunk_size=500) for the queryset to fetch entities in batches
  • Or add a query parameter to limit results (e.g., by folder or relationship type)
frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte (1)

40-91: Refactor to eliminate code duplication

The anchor and button branches contain nearly identical markup (lines 45-63 vs 71-89). Consider extracting the shared content into a separate component or using a wrapper component pattern to avoid duplication.

Example refactor:

<script lang="ts">
  // ... existing props ...
  
  const Tag = href ? 'a' : 'button';
  const tagProps = href ? { href } : { type: 'button', onclick };
</script>

<svelte:element
  this={Tag}
  {...tagProps}
  class="{baseClasses} {getCategoryColor(category)}"
>
  <div class="flex items-start gap-4">
    <div class="flex-shrink-0">
      <div class="w-12 h-12 rounded-lg bg-white flex items-center justify-center shadow-sm">
        <i class="{icon} text-2xl {getCategoryIconColor(category)}"></i>
      </div>
    </div>
    <div class="flex-1 min-w-0">
      <h3 class="text-lg font-semibold text-gray-900 mb-2">
        {title}
      </h3>
      <p class="text-sm text-gray-600 leading-relaxed">
        {description}
      </p>
      <div class="mt-4 flex items-center text-sm font-medium {getCategoryIconColor(category)}">
        {m.generateReport ? m.generateReport() : 'Generate Report'}
        <i class="fas fa-arrow-right ml-2 text-xs"></i>
      </div>
    </div>
  </div>
</svelte:element>
frontend/src/lib/components/SideBar/navData.ts (1)

295-299: Consider adding permissions to gate access to contracts.

While other items in thirdPartyCategory lack explicit permissions, the tprmOverview item (line 278) requires view_contract permission. For consistency and proper access control, consider adding permissions: ['view_contract'] to this navigation item to prevent users without the appropriate permission from accessing the contracts route.

Apply this diff to add the permission:

 {
   name: 'contracts',
   fa_icon: 'fa-solid fa-file-contract',
-  href: '/contracts'
+  href: '/contracts',
+  permissions: ['view_contract']
 },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b01138 and 2b8b256.

📒 Files selected for processing (7)
  • backend/tprm/views.py (3 hunks)
  • frontend/messages/en.json (5 hunks)
  • frontend/src/lib/components/SideBar/navData.ts (3 hunks)
  • frontend/src/routes/(app)/(internal)/reports/+page.server.ts (1 hunks)
  • frontend/src/routes/(app)/(internal)/reports/+page.svelte (1 hunks)
  • frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte (1 hunks)
  • frontend/src/routes/(app)/(internal)/reports/dora-roi/+server.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/routes/(app)/(internal)/reports/dora-roi/+server.ts (1)
frontend/src/lib/utils/constants.ts (1)
  • BASE_API_URL (4-8)
backend/tprm/views.py (3)
backend/tprm/models.py (4)
  • Entity (18-63)
  • Solution (136-170)
  • Contract (173-238)
  • Status (178-182)
backend/iam/models.py (4)
  • RoleAssignment (807-1094)
  • get_accessible_object_ids (917-1015)
  • Folder (68-316)
  • get_root_folder (75-77)
backend/core/views.py (17)
  • get (440-447)
  • get (5255-5256)
  • BaseModelViewSet (233-430)
  • status (1234-1235)
  • status (1503-1504)
  • status (2439-2440)
  • status (5846-5847)
  • status (5967-5968)
  • status (6001-6002)
  • status (6114-6115)
  • status (7444-7445)
  • status (7941-7942)
  • status (8110-8111)
  • status (8570-8571)
  • status (8708-8709)
  • status (9480-9481)
  • status (9491-9492)
🪛 Biome (2.1.2)
frontend/messages/en.json

[error] 57-57: The key associatedEntities was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)


[error] 212-212: The key startDate was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)


[error] 213-213: The key endDate was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🪛 GitHub Actions: Frontend Linters
frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte

[warning] 1-1: Code style issues found. Run 'pnpm exec prettier --write .' to fix.

frontend/src/routes/(app)/(internal)/reports/+page.svelte

[warning] 1-1: Code style issues found. Run 'pnpm exec prettier --write .' to fix.

⏰ 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_enterprise_frontend
  • GitHub Check: build_community_frontend
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: test (3.12)
  • GitHub Check: build (3.12)
  • GitHub Check: Analyze (python)
🔇 Additional comments (8)
frontend/src/routes/(app)/(internal)/reports/dora-roi/+server.ts (1)

1-3: LGTM!

The imports are appropriate and minimal.

backend/tprm/views.py (4)

4-4: LGTM!

The imports are clean and appropriate for the CSV/ZIP generation functionality.

Also applies to: 12-17


33-39: LGTM!

The addition of "contracts" to filterset_fields is consistent with the Entity-Contract relationship.


238-238: LGTM!

Adding "contracts" to the filterset enables filtering solutions by their associated contracts, which aligns with the Solution-Contract relationship.


247-257: LGTM!

The ContractViewSet implementation follows the established patterns in the codebase and includes appropriate filterset fields and status choices endpoint.

frontend/src/routes/(app)/(internal)/reports/+page.server.ts (1)

1-9: LGTM!

The server load function is correctly implemented with proper typing and localization. It cleanly provides the user context and localized title to the reports page.

frontend/src/lib/components/SideBar/navData.ts (2)

278-278: LGTM!

Adding view_contract permission to the TPRM overview is appropriate and aligns with the PR's contract management feature.


29-39: Reports permissions are correct as configured.

Based on code review, the reports page only accesses entity data (e.g., /entities/generate_dora_roi/) and does not fetch assessment data. The analytics page, which requires view_assessment, accesses compliance-assessments and risk-assessments endpoints. The permission sets appropriately reflect each page's actual data access patterns. No changes needed.

Comment on lines 41 to 118
@action(detail=False, methods=["get"], name="Generate DORA ROI")
def generate_dora_roi(self, request):
"""
Generate DORA Register of Information (ROI) as a zip file containing CSV data.
"""
# Get viewable entities for the current user
(viewable_items, _, _) = RoleAssignment.get_accessible_object_ids(
folder=Folder.get_root_folder(),
user=request.user,
object_type=Entity,
)

# Create CSV in memory
csv_buffer = io.StringIO()
csv_writer = csv.writer(csv_buffer)

# Write CSV headers
csv_writer.writerow(
[
"Entity ID",
"Name",
"Description",
"Mission",
"Reference Link",
"Relationship Types",
"Legal Identifiers",
"Folder",
"Created At",
"Updated At",
]
)

# Write entity data
for entity in Entity.objects.filter(id__in=viewable_items).select_related(
"folder"
):
# Get relationship types as comma-separated string
relationships = ", ".join([str(rel) for rel in entity.relationship.all()])

# Format legal identifiers as string
legal_ids = (
", ".join([f"{k}: {v}" for k, v in entity.legal_identifiers.items()])
if entity.legal_identifiers
else ""
)

csv_writer.writerow(
[
str(entity.id),
entity.name,
entity.description or "",
entity.mission or "",
entity.reference_link or "",
relationships,
legal_ids,
entity.folder.name if entity.folder else "",
entity.created_at.strftime("%Y-%m-%d %H:%M:%S"),
entity.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
]
)

# Create zip file in memory
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
# Add CSV to zip
zip_file.writestr(
"entities_register.csv", csv_buffer.getvalue().encode("utf-8")
)

# Prepare response
zip_buffer.seek(0)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"DORA_ROI_{timestamp}.zip"

response = HttpResponse(zip_buffer.getvalue(), content_type="application/zip")
response["Content-Disposition"] = f'attachment; filename="{filename}"'

return response
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

Add error handling for CSV/ZIP generation.

The CSV and ZIP file generation lacks error handling. If any step fails (e.g., invalid data, I/O errors), the user will see a generic 500 error instead of a meaningful message.

Consider wrapping the generation logic in a try-except block:

@action(detail=False, methods=["get"], name="Generate DORA ROI")
def generate_dora_roi(self, request):
    """
    Generate DORA Register of Information (ROI) as a zip file containing CSV data.
    """
    try:
        # Get viewable entities for the current user
        (viewable_items, _, _) = RoleAssignment.get_accessible_object_ids(
            folder=Folder.get_root_folder(),
            user=request.user,
            object_type=Entity,
        )
        
        # ... rest of the implementation ...
        
        return response
    except Exception as e:
        logger.error("Failed to generate DORA ROI", error=str(e), user=request.user.id)
        return Response(
            {"error": "Failed to generate DORA ROI report"},
            status=500
        )
🤖 Prompt for AI Agents
backend/tprm/views.py around lines 41 to 118: the CSV/ZIP generation is
unprotected and will surface a generic 500 on any failure; wrap the entire
generation and response creation in a try/except block that catches Exception,
logs the error with context (include exception text and request.user.id), and
returns a DRF Response with a clear error message and HTTP 500; ensure any
opened buffers are properly closed (use existing context managers or a finally
to close csv_buffer and zip_buffer) and add necessary imports for logging and
Response if not present.

Comment on lines 74 to 78
for entity in Entity.objects.filter(id__in=viewable_items).select_related(
"folder"
):
# Get relationship types as comma-separated string
relationships = ", ".join([str(rel) for rel in entity.relationship.all()])
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

N+1 query: prefetch relationships to avoid multiple database hits.

Line 78 calls entity.relationship.all() inside the loop, triggering one query per entity. Add prefetch_related("relationship") to the query on line 74.

Apply this diff:

         # Write entity data
-        for entity in Entity.objects.filter(id__in=viewable_items).select_related(
-            "folder"
-        ):
+        for entity in Entity.objects.filter(id__in=viewable_items).select_related(
+            "folder"
+        ).prefetch_related("relationship"):
             # Get relationship types as comma-separated string
             relationships = ", ".join([str(rel) for rel in entity.relationship.all()])
📝 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
for entity in Entity.objects.filter(id__in=viewable_items).select_related(
"folder"
):
# Get relationship types as comma-separated string
relationships = ", ".join([str(rel) for rel in entity.relationship.all()])
for entity in Entity.objects.filter(id__in=viewable_items).select_related(
"folder"
).prefetch_related("relationship"):
# Get relationship types as comma-separated string
relationships = ", ".join([str(rel) for rel in entity.relationship.all()])
🤖 Prompt for AI Agents
In backend/tprm/views.py around lines 74 to 78, the loop calls
entity.relationship.all() which triggers an N+1 query; update the queryset to
prefetch the relationship relation (e.g., chain
.prefetch_related("relationship") alongside the existing
.select_related("folder")) so relationships for all entities are fetched in a
single query and the loop no longer issues per-entity database hits.

Comment on lines 34 to 52
title: m.complianceSummaryReport ? m.complianceSummaryReport() : 'Compliance Summary Report',
description: m.complianceSummaryReportDesc ? m.complianceSummaryReportDesc() : 'Generate comprehensive compliance status across all frameworks',
icon: 'fa-solid fa-clipboard-check',
category: 'compliance'
},
{
id: 'incident-analysis',
title: m.incidentAnalysisReport ? m.incidentAnalysisReport() : 'Incident Analysis Report',
description: m.incidentAnalysisReportDesc ? m.incidentAnalysisReportDesc() : 'Comprehensive analysis of security incidents and trends',
icon: 'fa-solid fa-bug',
category: 'operations'
},
{
id: 'asset-inventory',
title: m.assetInventoryReport ? m.assetInventoryReport() : 'Asset Inventory Report',
description: m.assetInventoryReportDesc ? m.assetInventoryReportDesc() : 'Complete inventory of assets and their risk profiles',
icon: 'fa-solid fa-gem',
category: 'assets'
}
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

Add missing translation keys to complete i18n

The code uses several translation functions (m.complianceSummaryReport, m.complianceSummaryReportDesc, m.incidentAnalysisReport, m.incidentAnalysisReportDesc, m.assetInventoryReport, m.assetInventoryReportDesc) with fallback strings. These keys should be added to frontend/messages/en.json to complete the internationalization implementation.

Add these keys to frontend/messages/en.json:

"complianceSummaryReport": "Compliance Summary Report",
"complianceSummaryReportDesc": "Generate comprehensive compliance status across all frameworks",
"incidentAnalysisReport": "Incident Analysis Report",
"incidentAnalysisReportDesc": "Comprehensive analysis of security incidents and trends",
"assetInventoryReport": "Asset Inventory Report",
"assetInventoryReportDesc": "Complete inventory of assets and their risk profiles"
🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/reports/+page.svelte around lines 34 to
52, several translation keys used (complianceSummaryReport,
complianceSummaryReportDesc, incidentAnalysisReport, incidentAnalysisReportDesc,
assetInventoryReport, assetInventoryReportDesc) are missing from the English
messages file; add these six keys with the provided English strings to
frontend/messages/en.json so the m.* lookups resolve without falling back to
hardcoded defaults and ensure JSON is valid (comma placement, quoting).

if (tile.onClick) {
tile.onClick();
} else {
// Default action - will be implemented with backend
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

Add missing translation keys for UI text

The m.generateReport, m.aboutReports, and m.aboutReportsDescription functions are used but their keys are missing from frontend/messages/en.json. Add these to complete the i18n coverage.

Add these keys to frontend/messages/en.json:

"generateReport": "Generate Report",
"aboutReports": "About Reports",
"aboutReportsDescription": "Reports provide simple tools to generate specialized reports useful for key insights or required by authorities for specific standards. More specialized capabilities will be added as we identify specific cases."

Also applies to: 93-96

🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/reports/+page.svelte around lines 59 and
again 93-96, the view references translation keys m.generateReport,
m.aboutReports, and m.aboutReportsDescription that are missing from
frontend/messages/en.json; add these keys to that file with the following
values: "generateReport": "Generate Report", "aboutReports": "About Reports",
and "aboutReportsDescription": "Reports provide simple tools to generate
specialized reports useful for key insights or required by authorities for
specific standards. More specialized capabilities will be added as we identify
specific cases."

Comment on lines +8 to +11
const res = await fetch(endpoint);
if (!res.ok) {
error(400, 'Error generating DORA ROI file');
}
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

Preserve backend error status and details.

The current error handling always returns a 400 status regardless of the actual backend error. This loses important context about whether the issue is a client error (4xx) or server error (5xx), and doesn't include any error details from the backend response.

Apply this diff to preserve the backend status and error details:

 	const res = await fetch(endpoint);
 	if (!res.ok) {
-		error(400, 'Error generating DORA ROI file');
+		const errorMessage = await res.text().catch(() => 'Error generating DORA ROI file');
+		error(res.status, errorMessage);
 	}
📝 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
const res = await fetch(endpoint);
if (!res.ok) {
error(400, 'Error generating DORA ROI file');
}
const res = await fetch(endpoint);
if (!res.ok) {
const errorMessage = await res.text().catch(() => 'Error generating DORA ROI file');
error(res.status, errorMessage);
}
🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/reports/dora-roi/+server.ts around lines
8–11, the code unconditionally throws a 400 error on any non-OK backend
response, losing the original HTTP status and backend error details; change the
branch to preserve and rethrow the backend status and message by reading the
response body (try JSON then fallback to text), extract a useful error message
from that body (e.g., message or error fields or raw text), and call
error(res.status, extractedMessage) so the original status code and backend
error details are propagated.

Comment on lines +13 to +17
// Extract filename from Content-Disposition header if available
const contentDisposition = res.headers.get('Content-Disposition');
const fileName = contentDisposition
? contentDisposition.split('filename=')[1]?.replace(/"/g, '')
: `DORA_ROI_${new Date().toISOString().split('T')[0]}.zip`;
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

Improve Content-Disposition parsing and sanitize filename.

The current filename extraction has two concerns:

  1. Fragile parsing: The split('filename=')[1] approach doesn't handle all valid Content-Disposition formats (e.g., filename*, single quotes, or parameters before filename).

  2. Security: The filename from the backend response should be sanitized to prevent potential path traversal attacks, even though browsers typically handle attachment safely.

Consider this more robust approach:

 	// Extract filename from Content-Disposition header if available
 	const contentDisposition = res.headers.get('Content-Disposition');
-	const fileName = contentDisposition
-		? contentDisposition.split('filename=')[1]?.replace(/"/g, '')
-		: `DORA_ROI_${new Date().toISOString().split('T')[0]}.zip`;
+	let fileName = `DORA_ROI_${new Date().toISOString().split('T')[0]}.zip`;
+	if (contentDisposition) {
+		const match = contentDisposition.match(/filename[^;=\n]*=["]?([^";\n]*)["]?/);
+		if (match?.[1]) {
+			// Sanitize filename to prevent path traversal
+			fileName = match[1].replace(/[/\\]/g, '_').trim();
+		}
+	}
📝 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
// Extract filename from Content-Disposition header if available
const contentDisposition = res.headers.get('Content-Disposition');
const fileName = contentDisposition
? contentDisposition.split('filename=')[1]?.replace(/"/g, '')
: `DORA_ROI_${new Date().toISOString().split('T')[0]}.zip`;
// Extract filename from Content-Disposition header if available
const contentDisposition = res.headers.get('Content-Disposition');
let fileName = `DORA_ROI_${new Date().toISOString().split('T')[0]}.zip`;
if (contentDisposition) {
const match = contentDisposition.match(/filename[^;=\n]*=["]?([^";\n]*)["]?/);
if (match?.[1]) {
// Sanitize filename to prevent path traversal
fileName = match[1].replace(/[/\\]/g, '_').trim();
}
}
🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/reports/dora-roi/+server.ts around lines
13 to 17, the current Content-Disposition parsing is fragile and the extracted
filename isn't sanitized; update the code to robustly parse Content-Disposition
by first checking for a filename* parameter (RFC 5987) and decoding
percent-encoding, then falling back to filename with support for double or
single quotes and parameters in any order using a regex that captures either
form; after extraction, sanitize the resulting filename by stripping any
directory components or path separators, removing/control-normalizing dangerous
characters (e.g., .., slashes, nulls, control chars), limiting length to a safe
maximum, and falling back to the existing DORA_ROI_YYYY-MM-DD.zip default if
parsing yields nothing safe.

{description}
</p>
<div class="mt-4 flex items-center text-sm font-medium {getCategoryIconColor(category)}">
{m.generateReport ? m.generateReport() : 'Generate Report'}
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

Add missing translation key

The m.generateReport function is used but the key is missing from frontend/messages/en.json. This is also needed by the parent +page.svelte file.

Add this key to frontend/messages/en.json:

"generateReport": "Generate Report"

Also applies to: 85-85

🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte around lines
59 and 85, the template calls m.generateReport() but the translation key is
missing from frontend/messages/en.json; add the key "generateReport": "Generate
Report" to frontend/messages/en.json (ensuring valid JSON comma placement) so
both this component and the parent +page.svelte can resolve the translation.

@ab-smith ab-smith marked this pull request as draft November 10, 2025 16:15
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: 2

♻️ Duplicate comments (6)
frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte (1)

59-62: Missing i18n key generateReport (already flagged)

m.generateReport() is used but the key is absent in frontend/messages/en.json. Add "generateReport": "Generate Report". See prior bot comment.

Also applies to: 96-99

backend/tprm/views.py (2)

93-106: N+1 on entity.relationship (already flagged)

Prefetch relationship on the queryset to avoid per-entity queries in the ROI export loop.

-        for entity in Entity.objects.filter(id__in=viewable_items).select_related(
-            "folder"
-        ):
+        for entity in (
+            Entity.objects.filter(id__in=viewable_items)
+            .select_related("folder")
+            .prefetch_related("relationship")
+        ):

55-151: Wrap ROI generation in error handling (already flagged)

Add a try/except, log the error, and return a clear 500 response to avoid exposing generic errors to users.

frontend/messages/en.json (3)

2626-2640: Add missing translation for ReportTile CTA

Add the generateReport key used by ReportTile. Ensure valid comma placement.

   "impactRequirements": "All values must be provided and greater than 0. Values must increase from top to bottom (e.g., Minor: 5,000 → Major: 50,000 → Critical: 500,000).",
+  "generateReport": "Generate Report",
   "reports": "Reports",

56-58: Remove duplicate key

associatedEntities is already defined later; remove this earlier duplicate to satisfy Biome’s noDuplicateObjectKeys.

   "associatedSolutions": "Associated solutions",
-  "associatedContracts": "Associated contracts",
-  "associatedEntities": "Associated entities",
+  "associatedContracts": "Associated contracts",

212-213: Remove duplicate keys

startDate and endDate are already defined later; drop these duplicates here.

-  "startDate": "Start date",
-  "endDate": "End date",
🧹 Nitpick comments (5)
backend/tprm/views.py (2)

1-15: Clean up imports

Response is imported twice and date_format is unused. Remove the duplicate and unused import.

-from rest_framework.response import Response
+from rest_framework.response import Response
@@
-from rest_framework.response import Response
-
-from django.utils.formats import date_format
+from rest_framework.response import Response

208-260: Reduce N+1 queries in metrics

Prefetch solutions and reviewers, and select_related("entity", "compliance_assessment") to avoid repeated queries inside the loop.

-        for ea in EntityAssessment.objects.filter(id__in=viewable_items):
+        qs = (
+            EntityAssessment.objects.filter(id__in=viewable_items)
+            .select_related("entity", "compliance_assessment")
+            .prefetch_related("solutions", "reviewers")
+        )
+        for ea in qs:
backend/core/constants.py (1)

255-422: Consider documenting legacy currencies.

The CURRENCY_CHOICES list includes both current and legacy currencies (e.g., BYR "Belarussian Ruble (2000 Series)" at line 280, LTL "Lithuanian Litas" at line 343, LVL "Latvian Lats" at line 344, ZMK at line 416). While this may be intentional for historical data support, consider adding a comment at the top of the list documenting which currencies are legacy/deprecated to help future maintainers.

backend/tprm/models.py (2)

51-96: Consider DecimalField for financial data.

Line 85-90: The dora_assets_value uses FloatField for financial data. Consider using DecimalField instead to avoid floating-point precision issues with monetary values. This is especially important for DORA compliance reporting where accuracy is critical.

Apply this diff:

-    dora_assets_value = models.FloatField(
+    dora_assets_value = models.DecimalField(
+        max_digits=20,
+        decimal_places=2,
         blank=True,
         null=True,
         verbose_name=_("DORA assets value"),
         help_text=_("Total assets value for DORA reporting"),
     )

Otherwise, the Entity model enhancements look well-structured with appropriate field types, proper help text, and good use of choices constraints.


302-307: Consider DecimalField for financial data.

Line 302-307: The annual_expense field uses FloatField for monetary values. For consistency with financial reporting standards and to avoid floating-point precision issues, consider using DecimalField instead:

-    annual_expense = models.FloatField(
+    annual_expense = models.DecimalField(
+        max_digits=15,
+        decimal_places=2,
         blank=True,
         null=True,
         verbose_name=_("Annual expense"),
         help_text=_("Annual expense amount for this contract"),
     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9fb505 and a0cc4e3.

📒 Files selected for processing (15)
  • backend/core/constants.py (5 hunks)
  • backend/core/dora.py (1 hunks)
  • backend/privacy/migrations/0010_alter_datacontractor_country_and_more.py (1 hunks)
  • backend/tprm/migrations/0010_contract_annual_expense_contract_currency_and_more.py (1 hunks)
  • backend/tprm/models.py (5 hunks)
  • backend/tprm/serializers.py (5 hunks)
  • backend/tprm/views.py (3 hunks)
  • frontend/messages/en.json (5 hunks)
  • frontend/src/lib/components/Forms/ModelForm/ContractForm.svelte (1 hunks)
  • frontend/src/lib/components/Forms/ModelForm/EntityForm.svelte (3 hunks)
  • frontend/src/lib/components/Forms/ModelForm/SolutionForm.svelte (2 hunks)
  • frontend/src/lib/utils/crud.ts (6 hunks)
  • frontend/src/lib/utils/schemas.ts (5 hunks)
  • frontend/src/routes/(app)/(internal)/reports/+page.svelte (1 hunks)
  • frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/tprm/migrations/0010_contract_annual_expense_contract_currency_and_more.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/routes/(app)/(internal)/reports/+page.svelte
  • frontend/src/lib/components/Forms/ModelForm/ContractForm.svelte
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-18T10:42:17.597Z
Learnt from: ab-smith
Repo: intuitem/ciso-assistant-community PR: 2548
File: frontend/src/lib/components/ModelTable/LibraryOverview.svelte:1-22
Timestamp: 2025-09-18T10:42:17.597Z
Learning: The frontend of the ciso-assistant-community project is based on Svelte 5, so Svelte 5 runes like $props(), $derived, $state, etc. are expected and should not be flagged as issues.

Applied to files:

  • frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte
📚 Learning: 2025-08-12T12:36:17.078Z
Learnt from: nas-tabchiche
Repo: intuitem/ciso-assistant-community PR: 2397
File: backend/privacy/migrations/0005_alter_datacontractor_country_and_more.py:12-214
Timestamp: 2025-08-12T12:36:17.078Z
Learning: In Django migrations, country choices should be hardcoded as static lists rather than referencing dynamic constants like COUNTRY_CHOICES from backend.core.constants, to ensure migrations remain stable over time and don't break if the referenced constants change.

Applied to files:

  • backend/core/constants.py
  • backend/privacy/migrations/0010_alter_datacontractor_country_and_more.py
🧬 Code graph analysis (5)
backend/tprm/serializers.py (2)
backend/tprm/models.py (7)
  • Entity (26-111)
  • Solution (184-225)
  • Contract (228-341)
  • Meta (100-102)
  • Meta (156-158)
  • Meta (223-225)
  • Meta (339-341)
backend/core/serializer_fields.py (1)
  • FieldsRelatedField (26-84)
backend/privacy/migrations/0010_alter_datacontractor_country_and_more.py (2)
backend/tprm/migrations/0010_contract_annual_expense_contract_currency_and_more.py (1)
  • Migration (7-1091)
backend/privacy/migrations/0005_alter_datacontractor_country_and_more.py (1)
  • Migration (6-418)
frontend/src/lib/utils/schemas.ts (1)
backend/core/base_models.py (1)
  • NameDescriptionMixin (119-132)
backend/tprm/models.py (3)
backend/core/models.py (16)
  • FilteringLabelMixin (191-197)
  • Status (1896-1902)
  • Status (3264-3270)
  • Status (3403-3408)
  • Status (3572-3578)
  • Status (4074-4079)
  • Status (4160-4167)
  • Status (4236-4241)
  • Status (5054-5059)
  • Status (5925-5929)
  • Status (6309-6319)
  • Meta (113-114)
  • Meta (168-169)
  • Meta (196-197)
  • Meta (201-203)
  • Meta (1368-1370)
backend/tprm/views.py (11)
  • country (153-154)
  • currency (157-158)
  • currency (331-332)
  • dora_entity_type (161-162)
  • dora_entity_hierarchy (165-166)
  • dora_ict_service_type (302-303)
  • status (201-202)
  • status (327-328)
  • dora_contractual_arrangement (335-336)
  • termination_reason (339-340)
  • governing_law_country (343-344)
backend/tprm/serializers.py (11)
  • Meta (36-38)
  • Meta (42-44)
  • Meta (51-68)
  • Meta (82-84)
  • Meta (209-211)
  • Meta (218-220)
  • Meta (277-279)
  • Meta (288-290)
  • Meta (294-296)
  • Meta (307-309)
  • Meta (313-315)
backend/tprm/views.py (2)
backend/tprm/models.py (6)
  • Entity (26-111)
  • Representative (161-181)
  • Solution (184-225)
  • EntityAssessment (114-158)
  • Contract (228-341)
  • Status (233-237)
backend/iam/models.py (4)
  • RoleAssignment (807-1094)
  • get_accessible_object_ids (917-1015)
  • Folder (68-316)
  • get_root_folder (75-77)
🪛 Biome (2.1.2)
frontend/messages/en.json

[error] 57-57: The key associatedEntities was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)


[error] 212-212: The key startDate was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)


[error] 213-213: The key endDate was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

⏰ 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: startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: build (3.12)
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: build_community_frontend
  • GitHub Check: test (3.12)
  • GitHub Check: Analyze (python)
🔇 Additional comments (18)
backend/privacy/migrations/0010_alter_datacontractor_country_and_more.py (2)

12-271: Static choices in migration: good

Hardcoding the country choices inside the migration avoids drift and runtime import issues. This aligns with prior guidance. Based on learnings.


12-531: No changes needed—blank/null parity is preserved.

Both datacontractor.country and datatransfer.country remain as required CharField fields in migration 0010, matching their state in migration 0005. Neither migration specifies blank or null parameters, meaning both adhere to Django's defaults (blank=False, null=False), maintaining consistent field semantics across the alteration.

frontend/src/lib/utils/schemas.ts (1)

307-310: Schemas align with backend additions

AssetSchema.is_critical, AssetSchema.solutions, entity DORA fields, solution DORA ICT, and contractSchema look consistent with models and view filters.

Also applies to: 573-581, 629-648, 1294-1295

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

71-85: LGTM — DORA ICT service type field

Dropdown + AutocompleteSelect wiring matches backend choices endpoint and i18n keys.

frontend/src/routes/(app)/(internal)/reports/ReportTile.svelte (1)

80-80: Bind click handler using Svelte syntax

Use on:click={onclick} instead of passing {onclick} to ensure reliable event binding.

-	<button type="button" class="{baseClasses} {getCategoryColor(category)}" {onclick}>
+	<button type="button" class="{baseClasses} {getCategoryColor(category)}" on:click={onclick}>
⛔ Skipped due to learnings
Learnt from: ab-smith
Repo: intuitem/ciso-assistant-community PR: 2610
File: frontend/src/routes/(app)/(internal)/applied-controls/flash-mode/+page.svelte:175-175
Timestamp: 2025-09-29T20:07:40.257Z
Learning: In Svelte 5, event handlers use DOM-style property syntax (onkeydown, onclick, oninput, etc.) instead of the Svelte 4 directive syntax (on:keydown, on:click, on:input, etc.). This applies to all event handlers including those on svelte:window and svelte:document.
backend/core/dora.py (1)

5-90: I can see the search returned references to the EBA's XBRL taxonomy but not the specific label mappings. Let me search for more targeted information about the official DORA labels.

Let me search for more specific information about accessing the official DORA taxonomy.

Let me search for the regulatory text that officially defines these codes and labels.

Based on my investigation, I found that the EBA publishes official DORA XBRL taxonomy packages and sample files containing the taxonomy mappings, and the technical package includes XBRL taxonomies addressing various reporting requirements, including cross-sectoral technical standards for ICT third-party service providers under DORA.

However, I was unable to retrieve the specific official label mappings for entity types (eba_CT codes) and ICT service types (eba_TA codes) through available web sources. The official taxonomy data is published primarily in XBRL format files rather than human-readable documentation.

One potential discrepancy identified:

  • The review mentions eba_CT:x304 as "Central securities depository" (plural), but the code snippet shows "Central security depository" (singular)

Without access to the complete official EBA/EIOPA DORA taxonomy definitions, I cannot definitively verify whether all labels in the code match the official taxonomy exactly. The review's suggestion is marked as optional and requesting verification—indicating inherent uncertainty about the extent of any required changes.

Verify the DORA taxonomy labels against the official EBA technical package (available at eba.europa.eu/activities/direct-supervision-and-oversight/digital-operational-resilience-act/preparation-dora-application) by downloading the current XBRL taxonomy and data point model, then comparing the eba_CT and eba_TA code-to-label mappings against the lists in backend/core/dora.py. Pay particular attention to capitalization and singular/plural variations (e.g., eba_CT:x304).

backend/core/constants.py (1)

1-253: LGTM! Comprehensive country list expansion.

The expanded COUNTRY_CHOICES list is well-structured with proper Unicode handling for special characters (Åland, Curaçao, Réunion, etc.) and includes a useful sentinel entry for "Other Countries".

frontend/src/lib/components/Forms/ModelForm/EntityForm.svelte (4)

5-7: LGTM! Component imports aligned with new form fields.

The new imports (NumberField, LegalIdentifierField, Dropdown) are properly added to support the enhanced entity form functionality.


48-58: LGTM! Proper conditional rendering for builtin entities.

The relationship field is correctly hidden for builtin entities, preventing unintended modifications to system entities.


59-83: LGTM! Clean organization of optional fields.

The LegalIdentifierField is properly integrated, and grouping reference_link and country in a collapsible "more" dropdown improves form UX by keeping optional fields accessible but not cluttering the main view.


85-129: LGTM! DORA compliance fields well-organized.

The DORA-specific fields are logically grouped in a separate dropdown with appropriate field types. The scale-balanced icon effectively represents the regulatory nature of these fields, and starting collapsed keeps the form manageable while ensuring compliance data is accessible.

frontend/src/lib/utils/crud.ts (4)

485-492: LGTM! Asset-solution relationships properly configured.

The bidirectional asset-solution relationship is correctly configured with disableCreate and disableDelete flags on the reverse relationship (line 503), ensuring link management is controlled from the assets side. The addition of is_critical and solutions to detailViewFields provides proper visibility of these important fields.

Also applies to: 503-517


763-793: LGTM! Entity model properly configured for DORA compliance.

The detailViewFields provide a focused view of core entity information, and the new selectFields (country, currency, dora_entity_type, dora_entity_hierarchy) align with the backend Entity model enhancements. The addition of contracts to reverseForeignKeyFields enables proper relationship tracking.


829-855: LGTM! Comprehensive contracts model configuration.

The new contracts model is well-structured with:

  • Proper relationship controls via disableCreate/disableDelete flags ensuring link management happens from the appropriate side
  • Self-referential overarching_contract field enabling hierarchical contract structures
  • Complete selectFields covering contract status, currency, DORA compliance fields, and governing law

This aligns well with the backend Contract model and supports the DORA reporting objectives.


821-827: LGTM! Solutions model updated for contract integration.

The addition of contracts to reverseForeignKeyFields and the dora_ict_service_type selectField properly extends the solutions model to support contract relationships and DORA ICT service classification.

backend/tprm/models.py (3)

213-219: LGTM! Solution model enhanced for DORA ICT classification.

The dora_ict_service_type field is properly configured with appropriate constraints and help text to support DORA ICT service classification requirements.


228-341: LGTM! Comprehensive Contract model for DORA compliance.

The Contract model is well-designed with:

  • Clear Status enum for contract lifecycle management
  • Proper many-to-many relationships to entities, solutions, and evidences with descriptive help text
  • Self-referential overarching_contract field enabling hierarchical contract structures
  • Complete DORA-specific fields for regulatory reporting
  • Appropriate use of mixins (FolderMixin, FilteringLabelMixin) for standard functionality

The model effectively supports the DORA reporting requirements outlined in the PR objectives. The only suggestion is to consider DecimalField for monetary values as noted separately.


362-365: LGTM! Audit logging properly configured.

The Contract model is correctly registered with auditlog using the common exclusion pattern, ensuring proper change tracking for compliance and audit purposes.

Comment on lines +26 to +34
legal_identifiers = serializers.SerializerMethodField()

def get_legal_identifiers(self, obj):
"""Format legal identifiers as a readable string for display"""
if not obj.legal_identifiers:
return ""
return "\n".join(
[f"{key}: {value}" for key, value in obj.legal_identifiers.items()]
)
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

Don’t override JSON field with a string in read serializer

legal_identifiers is a JSONField on the model, but the read serializer replaces it with a string via SerializerMethodField. This changes the API shape and can break clients expecting an object. Expose the pretty string under a different name and keep the raw JSON.

-    legal_identifiers = serializers.SerializerMethodField()
+    legal_identifiers_display = serializers.SerializerMethodField()
@@
-    def get_legal_identifiers(self, obj):
+    def get_legal_identifiers_display(self, obj):
         """Format legal identifiers as a readable string for display"""
         if not obj.legal_identifiers:
             return ""
         return "\n".join(
             [f"{key}: {value}" for key, value in obj.legal_identifiers.items()]
         )
📝 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
legal_identifiers = serializers.SerializerMethodField()
def get_legal_identifiers(self, obj):
"""Format legal identifiers as a readable string for display"""
if not obj.legal_identifiers:
return ""
return "\n".join(
[f"{key}: {value}" for key, value in obj.legal_identifiers.items()]
)
legal_identifiers_display = serializers.SerializerMethodField()
def get_legal_identifiers_display(self, obj):
"""Format legal identifiers as a readable string for display"""
if not obj.legal_identifiers:
return ""
return "\n".join(
[f"{key}: {value}" for key, value in obj.legal_identifiers.items()]
)
🤖 Prompt for AI Agents
In backend/tprm/serializers.py around lines 26 to 34, the serializer currently
overrides the model's JSONField legal_identifiers with a SerializerMethodField
that returns a string; instead keep the original JSON object in the serialized
output and expose the formatted string under a new field name (e.g.,
legal_identifiers_pretty). Remove or rename the existing SerializerMethodField
so it no longer replaces legal_identifiers, implement
get_legal_identifiers_pretty to return the "\n".join formatted string (or empty
string if none), and update the serializer's fields list to include the new
pretty field while leaving legal_identifiers as the raw JSON.

Comment on lines 299 to 309
class ContractReadSerializer(BaseModelSerializer):
folder = FieldsRelatedField()
owner = FieldsRelatedField(many=True)
entities = FieldsRelatedField(many=True)
evidences = FieldsRelatedField(many=True)
solutions = FieldsRelatedField(many=True)
overarching_contract = FieldsRelatedField()

class Meta:
model = Contract
exclude = []
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

Nullable relation should allow null representation

overarching_contract is optional; FieldsRelatedField() doesn’t handle None unless allowed. Set allow_null=True to avoid serialization errors when unset.

-class ContractReadSerializer(BaseModelSerializer):
+class ContractReadSerializer(BaseModelSerializer):
     folder = FieldsRelatedField()
     owner = FieldsRelatedField(many=True)
     entities = FieldsRelatedField(many=True)
     evidences = FieldsRelatedField(many=True)
     solutions = FieldsRelatedField(many=True)
-    overarching_contract = FieldsRelatedField()
+    overarching_contract = FieldsRelatedField(allow_null=True)
📝 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 ContractReadSerializer(BaseModelSerializer):
folder = FieldsRelatedField()
owner = FieldsRelatedField(many=True)
entities = FieldsRelatedField(many=True)
evidences = FieldsRelatedField(many=True)
solutions = FieldsRelatedField(many=True)
overarching_contract = FieldsRelatedField()
class Meta:
model = Contract
exclude = []
class ContractReadSerializer(BaseModelSerializer):
folder = FieldsRelatedField()
owner = FieldsRelatedField(many=True)
entities = FieldsRelatedField(many=True)
evidences = FieldsRelatedField(many=True)
solutions = FieldsRelatedField(many=True)
overarching_contract = FieldsRelatedField(allow_null=True)
class Meta:
model = Contract
exclude = []
🤖 Prompt for AI Agents
In backend/tprm/serializers.py around lines 299 to 309, the
ContractReadSerializer's overarching_contract field is optional but currently
declared as FieldsRelatedField() which will raise on None; update that field to
allow nulls by passing allow_null=True (e.g.,
FieldsRelatedField(allow_null=True)) so the serializer can represent unset
overarching_contract without error.

@ab-smith ab-smith changed the title feat: contracts management and DORA reports feat: contracts management and additional concepts for DORA reports Nov 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants