Skip to content

Conversation

@monsieurswag
Copy link
Contributor

@monsieurswag monsieurswag commented Nov 3, 2025

Summary by CodeRabbit

  • New Features

    • Library detail page now shows a Related Objects section listing linked Risk Assessments, Risk Scenarios, Applied Controls, Compliance Assessments, and Libraries with clickable navigation.
    • Back-end now returns related-object counts so the UI can display and link these items.
  • Bug Fixes / UX

    • Risk matrix selection now displays a clear warning that it cannot be changed after selection.

@monsieurswag monsieurswag changed the title feat(UX): Display library related objects feat(ux): Display library related objects Nov 3, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 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 Django migration adjusting three ForeignKey fields, a LoadedLibrary.get_related_objects() method, a new backend related-objects API action, and frontend changes to fetch and display related objects on the loaded-library detail page (including ModelTable hrefKey support).

Changes

Cohort / File(s) Change Summary
Database Schema Updates
backend/core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py
Alters three ForeignKey fields: AppliedControl.reference_control (now null=True, blank=True, related_name='applied_controls', on_delete=CASCADE, verbose_name='Reference Control'), ComplianceAssessment.framework (related_name='compliance_assessments', on_delete=CASCADE), and RiskAssessment.risk_matrix (related_name='risk_assessments', on_delete=PROTECT, adds help_text).
Backend Model
backend/core/models.py
Adds LoadedLibrary.get_related_objects() to aggregate related Threats, RiskMatrices, ReferenceControls (via assessments), Frameworks, and LoadedLibraries; adds Count import and related_name attributes on several ForeignKeys.
Backend API
backend/library/views.py
Adds @action(detail=True, methods=["get"], url_path="related-objects") handler related_objects(self, request, pk) on LoadedLibraryViewSet that returns aggregated related objects (type, id, name, count).
Frontend — Library Detail page
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.svelte, frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+server.ts, frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+server.ts (updated fetching)
Adds RelatedObject types, getObjectHref utility, relatedObjectsTable TableSource, integrates data.relatedObjects into page, fetches related-objects in parallel and renders a ModelTable dropdown when present.
Frontend — ModelTable component
frontend/src/lib/components/ModelTable/ModelTable.svelte
Adds new public prop `hrefKey?: string
Frontend Minor Cleanup
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.server.ts, frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/tree/+server.ts
Removed unused fail import and removed unused url parameter from a GET handler signature respectively.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant FrontendServer
    participant BackendAPI
    participant Database

    User->>Browser: Open library detail
    Browser->>FrontendServer: GET /loaded-libraries/{id}
    FrontendServer->>BackendAPI: GET /api/loaded-libraries/{id}
    FrontendServer->>BackendAPI: GET /api/loaded-libraries/{id}/related-objects
    BackendAPI->>Database: LoadedLibrary.get_related_objects(): query related models
    Database-->>BackendAPI: return aggregated related objects
    BackendAPI-->>FrontendServer: related-objects JSON (count, list)
    FrontendServer-->>Browser: page + relatedObjects data
    Browser->>Browser: build relatedObjectsTable (getObjectHref)
    Browser->>Browser: render ModelTable (hrefKey -> use rowMetaData.href)
    User->>Browser: click related object row
    Browser->>Browser: navigate to rowMetaData.href
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify correctness and query efficiency of LoadedLibrary.get_related_objects() (complex aggregation and prefetching).
  • Validate migration changes for nullability and on_delete semantics and ensure no data migration gaps.
  • Check API action response shape and frontend getObjectHref mapping and ModelTable hrefKey behavior.

Possibly related PRs

Suggested labels

db-migration, ui

Suggested reviewers

  • eric-intuitem
  • nas-tabchiche
  • ab-smith

Poem

🐰 I hop through libraries, links in tow,
Gathering risks and controls in a row,
A click, a jump — hrefs lead the way,
Related objects brightening the day! ✨

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(ux): Display library related objects' directly and accurately summarizes the main change in the pull request, which adds functionality to display related objects for libraries across the backend and frontend.

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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/core/models.py (1)

4350-4356: Same collision note for RiskAssessment.risk_matrix related_name

This sets related_name="risk_assessments" on RiskMatrix, which collides with RiskMatrix.risk_assessments property. Align with the resolution proposed earlier (rename property or pick a different related_name and adjust annotate/prefetch here).

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

905-973: Type annotation and duplication risk

  • The return type hints don’t match actual objects returned (RiskScenario, RiskAssessment, AppliedControl, ComplianceAssessment, LoadedLibrary). Update the annotation or return model IDs only.
  • Possible duplicates across buckets (e.g., same AppliedControl referenced by RA and CA). Consider deduplication.

Example dedupe and lighter payload building:

-        related_objects = []
+        related = []
         for threat in threats:
-            related_objects.extend(threat.risk_scenarios.all())
+            related.extend(threat.risk_scenarios.all())
         for risk_matrix in risk_matrices:
-            related_objects.extend(risk_matrix.risk_assessments.all())
+            related.extend(risk_matrix.risk_assessments.all())
         for reference_control in risk_assessment_reference_controls:
-            related_objects.extend(reference_control.applied_controls.all())
+            related.extend(reference_control.applied_controls.all())
         for framework in frameworks:
-            related_objects.extend(framework.compliance_assessments.all())
+            related.extend(framework.compliance_assessments.all())
         for reference_control in compliance_assessment_reference_controls:
-            related_objects.extend(reference_control.applied_controls.all())
+            related.extend(reference_control.applied_controls.all())
-        related_objects.extend(libraries)
-        return related_objects
+        related.extend(libraries)
+        # Deduplicate by model+id
+        seen, unique = set(), []
+        for obj in related:
+            key = (obj._meta.label_lower, obj.pk)
+            if key not in seen:
+                seen.add(key)
+                unique.append(obj)
+        return unique
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+server.ts (2)

12-21: Guard against non‑JSON error bodies

Backend returns plain text for some 4xx paths. Calling relatedObjectsRes.json() in an error branch can throw. Use text() fallback or try/catch.

Example:

- if (!relatedObjectsRes.ok)
-     error(relatedObjectsRes.status as NumericRange<400, 599>, await relatedObjectsRes.json());
+ if (!relatedObjectsRes.ok) {
+     const body = await relatedObjectsRes.text().catch(() => '');
+     error(relatedObjectsRes.status as NumericRange<400, 599>, body || 'Error');
+ }

23-28: Optional: avoid materializing full objects when only ids/names are needed

Since the API only needs id/name/type, consider having the backend return that projection directly (as it already does), or in future, fetch only what’s necessary to keep payloads lean.

frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.svelte (1)

60-74: Simplify exhaustive switch with explicit return type.

The switch statement exhaustively handles all cases in the RelatedObject['type'] union, making the final return null at line 73 unreachable. TypeScript's exhaustiveness checking can be leveraged to remove this dead code.

Apply this diff:

-	function getObjectHref(obj: RelatedObject): string | null {
+	function getObjectHref(obj: RelatedObject): string {
 		switch (obj.type) {
 			case 'RiskAssessment':
 				return `/risk-assessments/${obj.id}`;
 			case 'RiskScenario':
 				return `/risk-scenarios/${obj.id}`;
 			case 'AppliedControl':
 				return `/applied-controls/${obj.id}`;
 			case 'LoadedLibrary':
 				return `/loaded-libraries/${obj.id}?loaded`;
 			case 'ComplianceAssessment':
 				return `/compliance-assessments/${obj.id}`;
 		}
-		return null;
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d13a668 and 9c4464a.

📒 Files selected for processing (8)
  • backend/core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py (1 hunks)
  • backend/core/models.py (5 hunks)
  • backend/library/views.py (1 hunks)
  • frontend/src/lib/components/ModelTable/ModelTable.svelte (3 hunks)
  • frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.server.ts (1 hunks)
  • frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.svelte (3 hunks)
  • frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+server.ts (1 hunks)
  • frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/tree/+server.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/tree/+server.ts (3)
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+server.ts (1)
  • GET (6-34)
frontend/src/routes/(app)/(internal)/stored-libraries/[id=uuid]/tree/+server.ts (1)
  • GET (6-19)
frontend/src/routes/(app)/(internal)/stored-libraries/[id=uuid]/+server.ts (1)
  • GET (6-25)
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+server.ts (2)
frontend/src/lib/utils/constants.ts (1)
  • BASE_API_URL (4-8)
backend/library/views.py (2)
  • content (194-200)
  • content (457-463)
backend/library/views.py (1)
backend/core/models.py (2)
  • LoadedLibrary (856-1035)
  • get_related_objects (905-972)
backend/core/models.py (2)
backend/library/views.py (1)
  • related_objects (486-503)
backend/core/tests/fixtures.py (1)
  • applied_controls (58-69)
🪛 GitHub Actions: Backend Linters
backend/core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py

[error] 1-1: Command 'ruff format --check .' failed: 1 file would be reformatted (core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py). Run 'ruff format' 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). (7)
  • GitHub Check: build_enterprise_frontend
  • GitHub Check: build_community_frontend
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: Analyze (python)
🔇 Additional comments (4)
frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/tree/+server.ts (1)

6-9: LGTM

Signature cleanup is fine; behavior unchanged.

frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.server.ts (1)

4-4: LGTM

Type‑only import is appropriate; no behavior change.

backend/core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py (1)

1-29: Fix the incorrect review claims and verify pre-existing reverse accessor collision

The formatting claim is incorrect: ruff format succeeded (output: "1 file reformatted").

The reverse accessor collision is real but pre-existing: RiskAssessment has two ForeignKeys both using related_name="risk_assessments" (lines 4355 to RiskMatrix and 4369 to EbiosRMStudy), and RiskMatrix already defines a @property risk_assessments. This conflict exists in the model layer regardless of this migration. Verify whether this collision causes runtime issues in your environment and resolve by renaming one of the conflicting related_names or the property.

frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.svelte (1)

21-34: LGTM! Well-defined type interfaces.

The RelatedObject and RelatedObjectResponse interfaces are clearly structured with an exhaustive union type for object types. This provides good type safety for the related objects feature.

Comment on lines +918 to +922
RiskMatrix.objects.filter(library=self)
.annotate(risk_assessment_count=Count("risk_assessments"))
.filter(risk_assessment_count__gt=0)
.prefetch_related("risk_assessments")
)
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 | 🔴 Critical

🧩 Analysis chain

Blocking: related_name conflicts with existing property RiskMatrix.risk_assessments

You introduced related_name="risk_assessments" (see RiskAssessment.risk_matrix), but RiskMatrix already defines a property named risk_assessments. Django will raise a reverse accessor clash at startup; annotate/prefetch on "risk_assessments" will also be affected.

Two viable fixes:

  • Prefer removing/renaming the RiskMatrix.risk_assessments property, and rely on the reverse manager, or
  • Rename the related_name (and adjust usages here to match).

Example (property rename to avoid the clash):

-    @property
-    def risk_assessments(self) -> list:
-        return RiskAssessment.objects.filter(risk_matrix=self)
+    # Avoid clashing with the reverse accessor name
+    @property
+    def risk_assessments_qs(self):
+        return RiskAssessment.objects.filter(risk_matrix=self)

If you keep the related_name as "risk_assessments", update any call sites of the property accordingly.


🏁 Script executed:

#!/bin/bash
# Verify clashes and usages
rg -nP --type=py -C2 '\bclass\s+RiskMatrix\b.*?@property\s+def\s+risk_assessments\b' backend/core/models.py
rg -nP --type=py '\.risk_assessments\b(?!\()'

Length of output: 419


</thinking>
I need to gather more information about the actual model definitions and the conflict. Let me search for the relevant code.

---

🏁 Script executed:

```shell
#!/bin/bash
# Find RiskAssessment model and check for related_name on ForeignKey fields
rg -nP --type=py -B5 -A5 'class RiskAssessment' backend/core/models.py | head -50

# Look for ForeignKey pointing to RiskMatrix with related_name
rg -nP --type=py 'ForeignKey.*RiskMatrix.*related_name' backend/core/models.py

# Find RiskMatrix class and check for risk_assessments property
rg -nP --type=py -A30 'class RiskMatrix' backend/core/models.py | grep -A10 '@property' | head -30

Length of output: 672


🏁 Script executed:

#!/bin/bash
# More targeted: Find the exact @property definition
rg -nP --type=py '@property' backend/core/models.py | grep -A2 'risk_assessments'

# Get context around line 960 to understand what object risk_matrix is
sed -n '950,970p' backend/core/models.py

# Get context around line 3923
sed -n '3915,3930p' backend/core/models.py

Length of output: 1202


🏁 Script executed:

#!/bin/bash
# Find RiskAssessment model definition and ForeignKey fields
rg -nP --type=py -B2 -A20 'class RiskAssessment' backend/core/models.py

# Find RiskMatrix property definition
rg -nP --type=py -B2 -A8 'def risk_assessments' backend/core/models.py | head -40

Length of output: 1907


🏁 Script executed:

#!/bin/bash
# Get context around the usage lines
sed -n '955,965p' backend/core/models.py

sed -n '3918,3928p' backend/core/models.py

sed -n '6770,6780p' backend/core/models.py

Length of output: 1026


🏁 Script executed:

#!/bin/bash
# Check if risk_matrix field in RiskAssessment has related_name specified
rg -nP --type=py 'risk_matrix\s*=\s*ForeignKey' backend/core/models.py -A2

Length of output: 0


Blocking: related_name="risk_assessments" conflicts with RiskMatrix.risk_assessments property

The conflict is confirmed. RiskAssessment.risk_matrix has related_name="risk_assessments" (line 4355), but RiskMatrix defines a property with the same name (line 1560). Django will raise a reverse accessor clash error at startup.

This must be resolved by either:

  • Removing or renaming the RiskMatrix.risk_assessments property (line 1560), or
  • Renaming the related_name and updating usages

The code snippet at lines 918-922 relies on the related_name for .prefetch_related("risk_assessments"), so the related_name should be kept. Rename the property:

-    @property
-    def risk_assessments(self) -> list:
-        return RiskAssessment.objects.filter(risk_matrix=self)
+    @property
+    def risk_assessments_list(self) -> list:
+        return RiskAssessment.objects.filter(risk_matrix=self)

Update line 960 accordingly: risk_matrix.risk_assessments.all()risk_matrix.risk_assessments_list.all() (or use the reverse manager directly).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/core/models.py around lines 918–922 there is a reverse accessor
clash: RiskAssessment.risk_matrix uses related_name="risk_assessments" (see
~line 4355) while RiskMatrix defines a property named risk_assessments (~line
1560); keep the related_name and rename the RiskMatrix property to avoid Django
startup errors (suggestion: rename to risk_assessments_list), update the
property name definition at ~line 1560 and change all its usages (for example
update the call at ~line 960 from risk_matrix.risk_assessments.all() to
risk_matrix.risk_assessments_list.all()), and run tests / grep the repo to
ensure no other references to the old property remain.

Comment on lines 942 to 951
compliance_assessment_reference_controls = (
ReferenceControl.objects.filter(library=self)
.annotate(
compliance_assessment_count=Count(
"applied_controls__requirement_assessments__compliance_assessment"
)
)
.filter(compliance_assessment_count=0)
.prefetch_related("applied_controls")
)
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 | 🔴 Critical

Bug: wrong filter operator (should be > 0, not == 0)

compliance_assessment_reference_controls filters compliance_assessment_count=0, which returns RCs NOT linked to any compliance assessment. Given the goal (display related objects), this should be compliance_assessment_count__gt=0.

Apply:

-            .filter(compliance_assessment_count=0)
+            .filter(compliance_assessment_count__gt=0)
📝 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
compliance_assessment_reference_controls = (
ReferenceControl.objects.filter(library=self)
.annotate(
compliance_assessment_count=Count(
"applied_controls__requirement_assessments__compliance_assessment"
)
)
.filter(compliance_assessment_count=0)
.prefetch_related("applied_controls")
)
compliance_assessment_reference_controls = (
ReferenceControl.objects.filter(library=self)
.annotate(
compliance_assessment_count=Count(
"applied_controls__requirement_assessments__compliance_assessment"
)
)
.filter(compliance_assessment_count__gt=0)
.prefetch_related("applied_controls")
)
🤖 Prompt for AI Agents
In backend/core/models.py around lines 942 to 951, the queryset filters
ReferenceControl objects with compliance_assessment_count=0 which returns
controls NOT linked to any compliance assessment; change the filter to
compliance_assessment_count__gt=0 so it returns only ReferenceControls that have
one or more linked compliance assessments. Update the .filter(...) call
accordingly (use __gt=0) and keep the annotate and prefetch_related as-is.

Comment on lines +3681 to 3682
related_name="applied_controls",
)
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 | 🔴 Critical

🧩 Analysis chain

Reverse accessor renamed; update call sites using appliedcontrol_set

Changing related_name to "applied_controls" breaks any usages of reference_control.appliedcontrol_set. There’s at least one in this file:

  • ReferenceControl.is_deletable uses self.appliedcontrol_set.exists()

Please update to self.applied_controls.exists().


🏁 Script executed:

#!/bin/bash
# Find broken reverse accessor usages
rg -nP --type=py '\bappliedcontrol_set\b'

Length of output: 102



---

🏁 Script executed:

```shell
#!/bin/bash
# Get context around line 1511 to verify method and class membership
sed -n '1480,1530p' backend/core/models.py | head -60

Length of output: 1452


🏁 Script executed:

#!/bin/bash
# Also search for the is_deletable method to confirm its location and content
rg -nP --type=py -B5 -A15 'def is_deletable' backend/core/models.py

Length of output: 1942


🏁 Script executed:

#!/bin/bash
# Verify the related_name change at lines 3681-3682
sed -n '3675,3690p' backend/core/models.py

Length of output: 504


Update reverse accessor at line 1511

The change to related_name="applied_controls" requires updating the reverse accessor usage in ReferenceControl.is_deletable() at line 1511:

Change self.appliedcontrol_set.exists() to self.applied_controls.exists()

🤖 Prompt for AI Agents
In backend/core/models.py around lines 3681-3682 (and update
ReferenceControl.is_deletable at line 1511), the reverse accessor was changed to
related_name="applied_controls", so replace any usage of
self.appliedcontrol_set.exists() with self.applied_controls.exists() (and update
similar appliedcontrol_set references to applied_controls where present) to
match the new related_name.

Comment on lines 485 to 504
@action(detail=True, methods=["get"], url_path="related-objects")
def related_objects(self, request, pk):
try:
key = "urn" if pk.startswith("urn:") else "id"
lib = LoadedLibrary.objects.get(**{key: pk})
except:
return Response("Library not found.", status=HTTP_404_NOT_FOUND)

related_objects = lib.get_related_objects()
serialized_related_objects = [
{"type": type(obj).__name__, "id": obj.id, "name": obj.name}
for obj in related_objects
]
return Response(
{
"count": len(serialized_related_objects),
"objects": serialized_related_objects,
}
)

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

Return JSON errors and avoid bare except

The action returns plain strings on errors; frontend calls res.json(), which will throw. Return JSON consistently and catch specific exceptions.

Apply:

-        except:
-            return Response("Library not found.", status=HTTP_404_NOT_FOUND)
+        except LoadedLibrary.DoesNotExist:
+            return Response({"error": "libraryNotFound"}, status=HTTP_404_NOT_FOUND)

Also consider enforcing a uniform error shape across content/tree endpoints for consistency.

🤖 Prompt for AI Agents
In backend/library/views.py around lines 485 to 504, the method uses a bare
except and returns plain string errors; change to catch the specific exception
(LoadedLibrary.DoesNotExist) and return a JSON response with a consistent error
shape (e.g. {"error": "Library not found."} or {"detail": "Library not found."})
and HTTP_404_NOT_FOUND status; ensure any other predictable failures are caught
explicitly (e.g. ValueError if parsing pk logic is added) and return the same
JSON error shape so this endpoint matches the content/tree endpoints' uniform
error format.

baseEndpoint?: string;
detailQueryParameter?: string;
fields?: string[];
hrefKey?: string | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading prop name and type.

The hrefKey prop is typed as string | null and named as if it's a key to look up an href in metadata, but the implementation only checks its truthiness as a boolean flag (line 174). The actual href is always read from rowMetaData.href.

Consider renaming for clarity:

-		hrefKey?: string | null;
+		useMetadataHref?: boolean;
-		hrefKey = null,
+		useMetadataHref = false,

Then update the usage at line 174:

-		if (hrefKey) {
+		if (useMetadataHref) {

Also applies to: 127-127

🤖 Prompt for AI Agents
In frontend/src/lib/components/ModelTable/ModelTable.svelte around lines 78, 127
and 174, the prop named hrefKey is misleading and typed as string | null but
only used as a boolean flag; rename it to enableRowHref (or isHrefEnabled) and
change its type to boolean (or boolean | undefined), update all prop
references/prop export accordingly, and replace the truthiness checks at line
174 (and the usage at 127) to check the new boolean prop; alternatively, if you
intend to support a dynamic key, keep hrefKey as a string and change the
implementation at line 174 to read the href from rowMetaData[hrefKey] instead of
rowMetaData.href—pick one approach and make consistent changes to the prop
declaration and all usages.

Comment on lines +173 to +179
if (hrefKey) {
console.log(rowMetaData);
const href = rowMetaData.href;
if (href) goto(href);
return;
}
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

Remove debug logging and improve error handling.

Two issues with this navigation logic:

  1. Line 175 contains a console.log that should be removed before merging to production.
  2. When href is falsy, the function returns silently without feedback to the user or fallback navigation.

Apply this diff:

 		if (hrefKey) {
-			console.log(rowMetaData);
 			const href = rowMetaData.href;
-			if (href) goto(href);
+			if (href) {
+				goto(href);
+			} else {
+				console.warn('Navigation href missing from row metadata', rowMetaData);
+			}
 			return;
 		}
📝 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
if (hrefKey) {
console.log(rowMetaData);
const href = rowMetaData.href;
if (href) goto(href);
return;
}
if (hrefKey) {
const href = rowMetaData.href;
if (href) {
goto(href);
} else {
console.warn('Navigation href missing from row metadata', rowMetaData);
}
return;
}
🤖 Prompt for AI Agents
In frontend/src/lib/components/ModelTable/ModelTable.svelte around lines 173 to
179, remove the debug console.log(rowMetaData) and replace the silent return
when href is falsy with a clear fallback: either navigate to a safe default
route (e.g., a model details list) or show user feedback (toast/notification)
indicating the link is unavailable; ensure the function neither logs debug info
nor returns silently but handles the missing href path explicitly.

objects: RelatedObject[];
}
const libraryRelatedObjects: RelatedObjectResponse = data?.library?.relatedObjects ?? [];
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 | 🔴 Critical

Fix type mismatch in default value.

The default value [] doesn't match the RelatedObjectResponse type, which expects an object with count and objects properties. This will cause runtime errors when the data is missing and the code tries to access libraryRelatedObjects.count (line 189) or libraryRelatedObjects.objects (lines 81-82).

Apply this diff:

-	const libraryRelatedObjects: RelatedObjectResponse = data?.library?.relatedObjects ?? [];
+	const libraryRelatedObjects: RelatedObjectResponse = data?.library?.relatedObjects ?? { count: 0, objects: [] };
📝 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 libraryRelatedObjects: RelatedObjectResponse = data?.library?.relatedObjects ?? [];
const libraryRelatedObjects: RelatedObjectResponse = data?.library?.relatedObjects ?? { count: 0, objects: [] };
🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.svelte
around line 36, the default value for libraryRelatedObjects is an empty array
which mismatches the RelatedObjectResponse type; replace the default with an
object matching that type (e.g. { count: 0, objects: [] }) so accesses to .count
and .objects are safe and keep the declared type RelatedObjectResponse.

Comment on lines +76 to +85
const relatedObjectsTable: TableSource = {
head: {
type: 'type',
name: 'name'
},
body: tableSourceMapper(libraryRelatedObjects.objects, ['type', 'name']),
meta: libraryRelatedObjects.objects.map((obj) => {
return { href: getObjectHref(obj) };
})
};
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 | 🔴 Critical

🧩 Analysis chain

Integration logic is sound.

The relatedObjectsTable construction correctly maps related objects to table rows with href metadata, and the conditional rendering based on count is appropriate. Once the hrefKey prop in ModelTable is renamed (as suggested in the ModelTable review), update line 197 accordingly.

Verify that the href values generated by getObjectHref correctly navigate to existing routes in the application:

Also applies to: 189-205


🏁 Script executed:

#!/bin/bash
# Check that target routes exist for related object types

echo "Checking route files for related object types..."

# Check for risk-assessments route
fd -t f "\+page.svelte" frontend/src/routes | rg "risk-assessments/\[id"

# Check for risk-scenarios route  
fd -t f "\+page.svelte" frontend/src/routes | rg "risk-scenarios/\[id"

# Check for applied-controls route
fd -t f "\+page.svelte" frontend/src/routes | rg "applied-controls/\[id"

# Check for loaded-libraries route
fd -t f "\+page.svelte" frontend/src/routes | rg "loaded-libraries/\[id"

# Check for compliance-assessments route
fd -t f "\+page.svelte" frontend/src/routes | rg "compliance-assessments/\[id"

Length of output: 1004


The web search results don't contain information about this specific codebase's getObjectHref function. Let me search the codebase directly for this function.


🏁 Script executed:

#!/bin/bash
# Find getObjectHref function definition

echo "Searching for getObjectHref function..."
rg -n "getObjectHref" frontend/src

echo -e "\n\nSearching for function definition..."
rg -nA 20 "function getObjectHref|const getObjectHref|export.*getObjectHref" frontend/src

echo -e "\n\nSearching for 'applied-controls' references..."
rg -n "applied-controls" frontend/src --type ts --type js

echo -e "\n\nSearching for libraryRelatedObjects to understand what types it returns..."
rg -nA 5 "libraryRelatedObjects" frontend/src/routes/

Length of output: 13364


I found a critical issue. Let me verify whether AppliedControl objects can actually appear in the libraryRelatedObjects response.


🏁 Script executed:

#!/bin/bash
# Find RelatedObject and RelatedObjectResponse type definitions

echo "Searching for RelatedObject and RelatedObjectResponse type definitions..."
rg -nA 10 "type RelatedObject|interface RelatedObject|type RelatedObjectResponse|interface RelatedObjectResponse" frontend/src

echo -e "\n\nChecking what types are actually returned from the API..."
rg -nB 5 -A 10 "relatedObjects" frontend/src/lib --type ts --type js | head -100

Length of output: 2468


🏁 Script executed:

#!/bin/bash
# Verify applied-controls has NO detail page route
echo "Confirming absence of applied-controls detail route..."
find frontend/src/routes -path "*applied-controls*" -name "+page.svelte" | grep -v flash-mode
echo "If empty above, no detail page exists for applied-controls"

Length of output: 113


Applied-controls detail route is missing and will cause 404 errors.

The RelatedObject type explicitly includes 'AppliedControl' as a valid type, and getObjectHref returns /applied-controls/${obj.id} for that case. However, no route exists at frontend/src/routes/(app)/(internal)/applied-controls/[id=uuid]/+page.svelte. When AppliedControl objects appear in related objects, users will encounter broken links.

Either remove 'AppliedControl' from the RelatedObject type if it shouldn't appear in related objects, or create the missing detail page route.

🤖 Prompt for AI Agents
In frontend/src/routes/(app)/(internal)/loaded-libraries/[id=uuid]/+page.svelte
around lines 76 to 85, links for RelatedObject may point to
/applied-controls/${obj.id} but the route
frontend/src/routes/(app)/(internal)/applied-controls/[id=uuid]/+page.svelte
does not exist, causing 404s; either remove 'AppliedControl' from the
RelatedObject union/type (and update any upstream code that supplies those
related objects) so getObjectHref will never generate that path, or add the
missing route file
frontend/src/routes/(app)/(internal)/applied-controls/[id=uuid]/+page.svelte
that accepts the id param and renders the AppliedControl detail (fetching data
by id and handling not-found), and ensure getObjectHref and any navigation use
the exact path and param name so links resolve correctly.

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

♻️ Duplicate comments (1)
backend/library/views.py (1)

489-490: Return JSON error response instead of plain string.

The error response returns a plain string, which will cause the frontend's .json() parsing to fail. This is consistent with the concern raised in previous reviews.

Apply this diff to return a consistent JSON error response:

-        if lib is None:
-            return Response("Library not found.", status=HTTP_404_NOT_FOUND)
+        if lib is None:
+            return Response({"error": "Library not found."}, status=HTTP_404_NOT_FOUND)
🧹 Nitpick comments (1)
backend/library/views.py (1)

493-496: Consider using DRF serializers for consistency.

The manual serialization works but bypasses DRF's serialization benefits (validation, consistent formatting, field control). Consider creating a lightweight serializer for related objects.

Example approach:

class RelatedObjectSerializer(serializers.Serializer):
    type = serializers.SerializerMethodField()
    id = serializers.UUIDField()
    name = serializers.CharField()
    
    def get_type(self, obj):
        return type(obj).__name__

Then use it in the view:

-    serialized_related_objects = [
-        {"type": type(obj).__name__, "id": obj.id, "name": obj.name}
-        for obj in related_objects
-    ]
+    serialized_related_objects = RelatedObjectSerializer(related_objects, many=True).data
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c4464a and 196720e.

📒 Files selected for processing (2)
  • backend/core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py (1 hunks)
  • backend/library/views.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/core/migrations/0110_alter_appliedcontrol_reference_control_and_more.py
🧰 Additional context used
🧬 Code graph analysis (1)
backend/library/views.py (1)
backend/core/models.py (2)
  • LoadedLibrary (856-1035)
  • get_related_objects (905-972)
⏰ 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). (26)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/detailed/settings/sso.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/detailed/login.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/enterprise/settings/client.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/detailed/settings/general.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/detailed/mappings.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/detailed/ebios-rm.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/user-route.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/detailed/analytics.test.ts)
  • GitHub Check: enterprise-functional-tests (3.12, chromium, tests/functional/startup.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/login.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/incidents.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/nav.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/business-impact-analysis.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/common.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/settings/sso.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/libraries.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/mappings.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/ebios-rm.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/findings-assessments.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/compliance-assessments.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/settings/general.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/startup.test.ts)
  • GitHub Check: functional-tests (3.12, chromium, tests/functional/detailed/analytics.test.ts)
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-startup-docker-compose-test
🔇 Additional comments (2)
backend/library/views.py (2)

485-486: LGTM!

The action decorator and method signature are correctly configured.


492-492: Line number correction needed; verify filter semantics for compliance_assessment_reference_controls.

The filter logic is at line 949 (not 948 as stated). The query filters compliance_assessment_reference_controls with compliance_assessment_count=0, which differs from the frameworks query at line 938 that uses compliance_assessment_count__gt=0. These filters select opposite sets of data (reference controls without assessments vs. frameworks with assessments). Confirm whether this semantic difference is intentional or whether both should use count__gt=0 to be consistent.

Comment on lines 485 to 502
@action(detail=True, methods=["get"], url_path="related-objects")
def related_objects(self, request, pk):
key = "urn" if pk.startswith("urn:") else "id"
lib = LoadedLibrary.objects.filter(**{key: pk}).first()
if lib is None:
return Response("Library not found.", status=HTTP_404_NOT_FOUND)

related_objects = lib.get_related_objects()
serialized_related_objects = [
{"type": type(obj).__name__, "id": obj.id, "name": obj.name}
for obj in related_objects
]
return Response(
{
"count": len(serialized_related_objects),
"objects": serialized_related_objects,
}
)
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 permission check for security.

This endpoint lacks permission validation, unlike other similar methods in this class (e.g., retrieve() at line 420). Users without proper permissions could access library relationship data.

Add a permission check at the beginning of the method:

 @action(detail=True, methods=["get"], url_path="related-objects")
 def related_objects(self, request, pk):
+    if "view_loadedlibrary" not in request.user.permissions:
+        return Response(status=HTTP_403_FORBIDDEN)
     key = "urn" if pk.startswith("urn:") else "id"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/library/views.py around lines 485 to 502, the related_objects action
is missing an object-level permission check; after you load the library (keep
the existing key/lookup and 404 return if lib is None), call
self.check_object_permissions(request, lib) before accessing or serializing
related objects so the method enforces the same permissions as retrieve() and
prevents unauthorized access.

@monsieurswag monsieurswag marked this pull request as draft November 3, 2025 18:46
@monsieurswag monsieurswag force-pushed the feat/show_library_related_objects branch from e967a3d to e95effd Compare November 3, 2025 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants