Skip to content

feat: add evidences to the audit action plan export#3924

Open
martinzerty wants to merge 6 commits into
mainfrom
SUP-1175-add-applied-controls-to-proofs
Open

feat: add evidences to the audit action plan export#3924
martinzerty wants to merge 6 commits into
mainfrom
SUP-1175-add-applied-controls-to-proofs

Conversation

@martinzerty
Copy link
Copy Markdown
Contributor

@martinzerty martinzerty commented Apr 14, 2026

This PR adds the applied controls' evidences and their attachments to the document when exporting the audit's action plan.

How to test

  1. Create an audit
  2. Add applied controls to requirement assessments
  3. Create evidences within some of these applied controls
  4. Go to the audit page and export the action plan

Summary by CodeRabbit

  • New Features
    • Action plan exports (CSV & XLSX) now include two new columns—"Associated evidences" and "Evidence attachments"—showing evidence entries and attachment filenames; multi-line values are supported and Excel wrapping is applied.
    • API responses for compliance action plans now include a read-only attachments list for evidences so filenames are available for display and export.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a read-only evidence_attachments field to ComplianceAssessmentActionPlanSerializer and extended action-plan CSV/XLSX exports to include “Associated evidences” and “Evidence attachments”, prefetching evidences__revisions and escaping spreadsheet content.

Changes

Cohort / File(s) Summary
Serializer: new attachment field
backend/core/serializers.py
Added evidence_attachments = serializers.SerializerMethodField() and get_evidence_attachments(self, obj) to iterate obj.evidences.all(), skip evidences without filenames, and return list of dicts with id (string), str (string), and filename. Added "evidence_attachments" to Meta.fields.
Exports: CSV & XLSX updates
backend/core/views.py
Prefetch evidences__revisions on AppliedControl queryset; extended action_plan_csv and action_plan_xlsx to output “Associated evidences”/associated_evidences and “Evidence attachments”/evidence_attachments columns. Values join item["evidences"][*]["str"] and item["evidence_attachments"][*]["filename"] with \n, filter empties, apply escape_excel_formula and add new XLSX wrap-text columns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I hop through rows and tidy up the trail,
Filenames tucked safe within my tale.
CSVs and sheets now neat and bright,
Attachments snug, each cell just right.
A rabbit hums and pats the night 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding evidences to the audit action plan export, which is reflected in both the serializer and views modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SUP-1175-add-applied-controls-to-proofs

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
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/core/serializers.py`:
- Line 1385: The serializer is calling the Evidence.filename as a function which
will raise; update all occurrences in the evidence attachment handling to access
the property (remove the parentheses) — e.g., inside the SerializerMethodField
handler (evidence_attachments / the method that builds attachments) replace
evidence.filename() with evidence.filename and similarly fix every occurrence in
the block that builds attachment dicts (the segments around the 1402-1415 and
1438 usages) so they read the filename property directly.

In `@backend/core/views.py`:
- Around line 11712-11721: The exported evidence text and filenames are written
raw into CSV/XLSX cells and need spreadsheet-formula escaping: create a small
helper like escape_spreadsheet_formula(value) that returns the original string
unless it starts with one of the formula-triggering characters ('=', '+', '-',
'@'), in which case it returns the string prefixed with a single quote ('). Then
apply this helper to each evidence.get("str") and evidence.get("filename")
before joining (the expressions that build the "\n".join(...) for
item.get("evidences", []) and item.get("evidence_attachments", [])), and also
update the analogous block around lines 11765-11774 so all exported
user-controlled cell values are escaped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81387276-dc2e-4d5e-8bdc-0c50a527b030

📥 Commits

Reviewing files that changed from the base of the PR and between bfc0bda and d7acce1.

📒 Files selected for processing (2)
  • backend/core/serializers.py
  • backend/core/views.py

Comment thread backend/core/serializers.py
Comment thread backend/core/views.py
Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
backend/core/serializers.py (1)

1402-1415: ⚠️ Potential issue | 🔴 Critical

Fix filename property access to prevent export-time crash.

Line [1405] calls evidence.filename(); this should be property access (evidence.filename). As written, it can raise at runtime and break action-plan export serialization.

Proposed fix
     def get_evidence_attachments(self, obj):
         attachments = []
         for evidence in obj.evidences.all():
-            filename = evidence.filename()
+            filename = evidence.filename
             if filename:
                 attachments.append(
                     {
                         "id": str(evidence.id),
                         "str": str(evidence),
                         "filename": filename,
                     }
                 )
         return attachments
#!/bin/bash
set -euo pipefail

# Verify how `filename` is defined on Evidence/EvidenceRevision model(s)
rg -n -C3 --type=py 'class\s+Evidence\b|class\s+EvidenceRevision\b|@property|def\s+filename\b' backend/core/models.py

# Verify remaining callable usages in serializers
rg -nP --type=py '\bevidence\.filename\s*\(' backend/core/serializers.py
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/core/serializers.py` around lines 1402 - 1415, In
get_evidence_attachments (serializer method) change the callable access
evidence.filename() to property access evidence.filename to avoid raising at
export-time; update the code that assigns filename = evidence.filename() to
filename = evidence.filename, keep the rest of the loop intact and ensure any
other occurrences in backend/core/serializers.py using evidence.filename(...)
are similarly converted to property access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@backend/core/serializers.py`:
- Around line 1402-1415: In get_evidence_attachments (serializer method) change
the callable access evidence.filename() to property access evidence.filename to
avoid raising at export-time; update the code that assigns filename =
evidence.filename() to filename = evidence.filename, keep the rest of the loop
intact and ensure any other occurrences in backend/core/serializers.py using
evidence.filename(...) are similarly converted to property access.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bef65f6e-91df-4cd9-b822-2ecb759dd553

📥 Commits

Reviewing files that changed from the base of the PR and between d7acce1 and c321a88.

📒 Files selected for processing (2)
  • backend/core/serializers.py
  • backend/core/views.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/core/views.py

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/core/views.py`:
- Around line 11715-11727: The exported requirement strings from
requirement_assessments and covered_requirements are not being sanitized,
leaving a CSV/XLSX formula-injection risk; update both occurrences where you
currently do "\n".join([ra.get("str") for ra in
item.get("requirement_assessments")]) and the similar covered_requirements join
to call self.escape_spreadsheet_cell(...) for each entry (and likewise ensure
covered_requirements uses escape_spreadsheet_cell on each requirement string),
matching how evidences and evidence_attachments are escaped; keep the
newline-joining behavior but wrap each get("str")/requirement value with
self.escape_spreadsheet_cell to close the gap.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a475f2f1-df7e-4f6f-935b-b35f8d405cbf

📥 Commits

Reviewing files that changed from the base of the PR and between c321a88 and 1880d64.

📒 Files selected for processing (1)
  • backend/core/views.py

Comment thread backend/core/views.py
@nas-tabchiche nas-tabchiche changed the title feat: add evidences in the audit actionplans export feat: add evidences to the audit action plan export Apr 15, 2026
@nas-tabchiche
Copy link
Copy Markdown
Collaborator

Not sure of the usefulness of having the evidence filename in the csv exports for the action plan. Maybe we should consider a zip export, like we do for audit exports. Thoughts @ab-smith?

@martinzerty martinzerty requested a review from ab-smith April 16, 2026 07:31
@martinzerty
Copy link
Copy Markdown
Contributor Author

Not sure of the usefulness of having the evidence filename in the csv exports for the action plan. Maybe we should consider a zip export, like we do for audit exports. Thoughts @ab-smith?

As a client feature request, I though it was probably useful (at least for some users) to have them but I don't know what other information than only the name and the attachment are that important to be exported in the audit's action plan.

@nas-tabchiche
Copy link
Copy Markdown
Collaborator

nas-tabchiche commented Apr 16, 2026

I just re-read the ticket, and it's unclear whether we are talking about the name of the Evidence object, or the filename of the attachment. Worth asking them to clarify that bit.
That being said, having both name and filename is okay, I'm not brushing it away. My point was more about adding a zip export, like that of audits so that we can have fully self-contained exports for action plans. But that's out of scope for this PR.

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/core/views.py`:
- Around line 10260-10264: The current escape_spreadsheet_cell implementation
misses values with leading whitespace and doesn't reuse existing hardening
logic; replace its body to delegate to the existing escape_excel_formula helper
so it benefits from its None handling, string coercion and leading-whitespace
stripping; locate the staticmethod escape_spreadsheet_cell and have it
call/return escape_excel_formula(value) (or the equivalent public helper)
instead of doing the manual first-char check against ("=", "+", "-", "@").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecff7b31-1d2a-4147-9134-834baf2ddf70

📥 Commits

Reviewing files that changed from the base of the PR and between 1880d64 and 18c1d50.

📒 Files selected for processing (2)
  • backend/core/serializers.py
  • backend/core/views.py

Comment thread backend/core/views.py Outdated
@ab-smith
Copy link
Copy Markdown
Collaborator

consider something like .prefetch_related("evidences__revisions").distinct() to avoid perf overhead, also move escape_spreadsheet_cell to a common part

Copy link
Copy Markdown
Collaborator

@ab-smith ab-smith left a comment

Choose a reason for hiding this comment

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

comment above

Copy link
Copy Markdown
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.

Caution

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

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

10787-10850: ⚠️ Potential issue | 🟠 Major

Apply IAM filtering before exporting evidence metadata.

These two actions now write item["evidences"] and item["evidence_attachments"] straight into the export, but this path only checks that the user can read the audit. Unless the serializer is already filtering by readable Evidence IDs, any audit reader can learn evidence names and attachment filenames for linked controls they are not allowed to inspect. Please intersect the exported evidence lists with RoleAssignment.get_accessible_object_ids(..., Evidence) or move that filtering into ComplianceAssessmentActionPlanSerializer so CSV/XLSX stay aligned with normal object-level IAM behavior.

Based on learnings, only aggregate analytics endpoints in backend/core/views.py intentionally skip IAM filtering for controls/evidence; object-level exports should remain filtered.

Also applies to: 10869-10907

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/core/views.py` around lines 10787 - 10850, The export currently
writes item["evidences"] and item["evidence_attachments"] without IAM filtering;
restrict these lists to only evidence the requesting user can access by
intersecting their IDs with
RoleAssignment.get_accessible_object_ids(request.user, Evidence) (or move that
filtering into ComplianceAssessmentActionPlanSerializer so CSV/XLSX output
matches normal object-level IAM). Locate the export loop in views.py that
iterates serializer.data and, before joining evidence strings/filenames, filter
each evidence/attachment by presence in the accessible IDs set (apply same fix
for the other occurrence around the 10869-10907 block) so only permitted
evidence names/filenames are written.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/core/views.py`:
- Around line 10787-10850: The export currently writes item["evidences"] and
item["evidence_attachments"] without IAM filtering; restrict these lists to only
evidence the requesting user can access by intersecting their IDs with
RoleAssignment.get_accessible_object_ids(request.user, Evidence) (or move that
filtering into ComplianceAssessmentActionPlanSerializer so CSV/XLSX output
matches normal object-level IAM). Locate the export loop in views.py that
iterates serializer.data and, before joining evidence strings/filenames, filter
each evidence/attachment by presence in the accessible IDs set (apply same fix
for the other occurrence around the 10869-10907 block) so only permitted
evidence names/filenames are written.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f167423-0b3b-43af-9db3-8ed7ba5798e8

📥 Commits

Reviewing files that changed from the base of the PR and between 18c1d50 and d981b6a.

📒 Files selected for processing (1)
  • backend/core/views.py

@martinzerty martinzerty requested a review from ab-smith May 19, 2026 13:10
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.

3 participants