Skip to content

feat: refactor framework questions #1671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 66 commits into
base: main
Choose a base branch
from

Conversation

Mohamed-Hacene
Copy link
Collaborator

@Mohamed-Hacene Mohamed-Hacene commented Mar 13, 2025

Summary by CodeRabbit

  • New Features

    • Added support for multiple questions per requirement and multiple answers per assessment.
    • Enhanced UI components to handle various question types including multiple choice, unique choice, text, and date.
  • Improvements

    • Updated answer display and editing interfaces to support multiple answers keyed by question URNs.
    • Added localization for new question types in multiple languages.
    • Refined data validation and synchronization for questions and answers.
  • Bug Fixes

    • Fixed answer rendering to correctly handle multiple answers and their types.
  • Documentation

    • Revised data model and schema documentation to reflect the new questions and answers structure.
  • Chores

    • Migrated existing question and answer data to the new pluralized format.
    • Updated schema version and test imports accordingly.

Th3ju and others added 22 commits February 27, 2025 09:03
* Create docker-compose.ps1

This is the initialization script for Windows user

* translate doc-pol in French

* Create cjis.yaml

create mapping for cjis update
fix error message

* Avoid reckless error localization lookups

* Remove is_open_for_signup method override

The signup check is already done in the SAML views

* chore: Organize imports

* chore: Some cleaning

* Modèle ANSSI

ajout de nouveaux framework et homogénéisation des références des bibliothèques

* Modèles ANSSI

ajout de nouveaux framework et homogénéisation des références des bibliothèques

* Fix objects_meta always counting 1 object even when there is more than that

* Fix null name for implementation groups

* Add extra log useful for mapping issues

* Update utils.py

fix and rationalize messages

* Update utils.py

* add GL on costs and losses

* Add migration to fix the wrong objects_meta counters

* Formatter

* quick cleanup

* Fix reference controls french translation

It translates to "Mesures de référence", not "Contrôles de référence"

* Remove debug code

* Remove unnecessary click handler

* Change all backend endpoint URLs not ending with a slash to stop useless redirections when requesting the API

* Fix broken dependency links for loaded library detail views

* Backend formatter

* Update data-model.md

* Simplify post token generation flow

This essentially removes redundant or irrelevant checks from allauth.
As we will most certainly only have a single social account adapter for the
entire lifespan of the project, these changes will only save us from headaches.

* Render IdP-initiated SSO rejected error

* Create enum for SAML-related auth errors

* Internationalize auth errors

* Use FieldsRelatedField to serialize dependencies

* chore: Run formatting

* chore: Organize imports

* Bump django from 5.0.7 to 5.0.8 in /backend

Bumps [django](https://github.com/django/django) from 5.0.7 to 5.0.8.
- [Commits](django/django@5.0.7...5.0.8)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

* TPRM model

Leverage compliance assessment.

* fix translation spec

* Update data-model.md

clarify solution.
small fixes.

* Update data-model.md

Consider contract as a subset of applied controls.
A domain can be owned by one entity at most.

* Update id and new template anssi framework

* Update id and new template anssi framework

* TPRM

* Update data-model.md

* Update startup-tests.yml

try fix docker test error

* Update startup-tests.yml

try fix docker test

* 724: improve framework naming rendering

* Small ui improvement

* also improve library view

* Add RTS DORA on JET, on OVS

* Add PART-IS

* fix: typo in quality checks

* chore: update translations with Fink 🐦

* Update libraries.test.ts

try fix functional tests

* try fix functional tests

- une name instead of ref to get a lib.
- adjust library name for NIST CSF, so that lib name=referential name (assumption for tests)

* Update page-content.ts

prettier

* Update README.md

* fix: allow empty link

* fix French translation for iso27005 matrix

* Update README.md

* fix: revert evidence link field schema

* chore: add missing comma

* Render authentication errors

* feat: raise urn max length

* chore: format migration

* Add Romanian translation

* update the excel sheet

* generate the new yaml with annotations and levels

* Update README.md

Add in 1.6.6

* Update README.md

* Update README.md

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: Fabrizio Di Carlo <[email protected]>
Co-authored-by: eric-intuitem <[email protected]>
Co-authored-by: Nassim Tabchiche <[email protected]>
Co-authored-by: protocolpaladin <[email protected]>
Co-authored-by: Nassim <[email protected]>
Co-authored-by: monsieurswag <[email protected]>
Co-authored-by: Abder <[email protected]>
Co-authored-by: ImanABS <[email protected]>
Co-authored-by: monsieurswag <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed-Hacene <[email protected]>
Co-authored-by: Mohamed-Hacene <[email protected]>
I have aranged, using the ciso assistant tools, two italian frameworks.
Copy link
Contributor

coderabbitai bot commented Mar 13, 2025

Walkthrough

This update transitions the data model and related logic from supporting a single question and answer per requirement to supporting multiple questions and answers. The migration includes backend model, serializer, utility, and template changes, as well as frontend component and localization updates, and introduces a database migration script and YAML migration tool to facilitate the new structure.

Changes

Files/Paths Change Summary
backend/core/models.py, backend/core/migrations/0071_*.py, backend/core/helpers.py, backend/core/serializers.py, backend/library/utils.py, backend/ciso_assistant/meta.py Refactored models and serializers to use plural questions and answers fields instead of singular question and answer. Added data migration to convert old formats to new. Updated helper, utility, and meta version.
backend/core/templates/snippets/req_node.html, backend/core/templatetags/core_extras.py Updated template and added new template filters to support rendering multiple questions and answers, including filters for type checking and answer retrieval.
backend/app_tests/api/test_api_requirement_assessments.py Modified tests to validate the presence and structure of the new questions field in API responses.
documentation/architecture/data-model.md, tools/README.md, tools/convert_library.py, tools/ccf/convert_ccf.py Updated documentation and conversion scripts to use new field names (questions, answers, type, choices). Refactored answer/question handling in scripts.
tools/migrate_yaml_questions.py Added a new script to migrate YAML files from the old single-question format to the new multiple-questions structure.
frontend/src/lib/components/Forms/Question.svelte, frontend/src/lib/utils/schemas.ts Refactored frontend form component and schema to handle multiple questions and answers, supporting various question types and answer formats.
frontend/src/routes/(app)/(internal)/frameworks/[id=uuid]/TreeViewItemContent.svelte, frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/TreeViewItemContent.svelte, frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte, frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte, frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte Updated frontend components and pages to render, edit, and display multiple questions and answers per requirement, updating logic for question access, answer display, and editing.
frontend/messages/cs.json, frontend/messages/de.json, frontend/messages/en.json, frontend/messages/es.json, frontend/messages/fr.json, frontend/messages/id.json, frontend/messages/it.json, frontend/messages/nl.json, frontend/messages/pt.json, frontend/messages/ro.json Added new localization keys for question types ("multipleChoice", "uniqueChoice", "text", "date") in multiple languages.
.gitignore, backend/.gitignore Adjusted .gitignore patterns for .bak files: removed from root, added to backend.
frontend/tests/functional/domain-import.test.ts Changed test to use a new sample backup file reflecting schema changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Backend
    participant Database

    User->>Frontend: Open requirement assessment
    Frontend->>Backend: Fetch requirement node (with questions)
    Backend->>Database: Query RequirementNode (questions)
    Database-->>Backend: Return node with questions object
    Backend-->>Frontend: Return node data (questions: {urn: {...}, ...})
    Frontend->>User: Render multiple questions

    User->>Frontend: Submit answers to questions
    Frontend->>Backend: Send answers object (answers: {urn: value/list})
    Backend->>Database: Store answers in RequirementAssessment
    Database-->>Backend: Confirm save
    Backend-->>Frontend: Acknowledge update
    Frontend->>User: Display saved answers
Loading

Suggested reviewers

  • ab-smith

Poem

🐇
Hopping through questions, now plural and bright,
Answers in bundles, all stored just right.
From models to forms, the changes cascade,
With scripts and migrations, the groundwork is laid.
In many tongues, new choices appear—
A warren of queries, for all to revere!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83025b1 and d54ce0d.

📒 Files selected for processing (1)
  • frontend/messages/en.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/messages/en.json
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: functional-tests (3.12, chromium)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: enterprise-functional-tests (3.12, chromium)
  • GitHub Check: test (3.12)
  • GitHub Check: build (3.12)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Nitpick comments (6)
documentation/architecture/data-model.md (6)

1145-1145: Clarify JSON form reference

The phrase "as a JSON form" may be ambiguous—consider linking to the "Question and answer format" section or specifying the expected schema.


1287-1288: Standardize JSON casing and phrasing

Update "json" to "JSON" and refine the description, for example:

- - answers: a json corresponding to the answers of the requirement node questions.
+ - answers: a JSON object representing the answers to the requirement node questions.

1309-1309: Standardize JSON casing and phrasing

Similarly, update the questions line to use uppercase "JSON" and clarify:

- - questions: a json corresponding to the optional questions of the requirement node.
+ - questions: a JSON object defining the optional questions for the requirement node.

1311-1313: Improve heading and introductory sentence

Consider rephrasing for clarity and to remove redundancy:

- ### Question and answer format
- The format for questions and answers json fields will evolve over time. The initial format is the following:
+ ### Question and answer format
+ The format for the `questions` and `answers` JSON fields will evolve; the initial format is as follows:
🧰 Tools
🪛 LanguageTool

[uncategorized] ~1311-~1311: Possible missing preposition found.
Context: ...t The format for questions and answers json fields will evolve over time. The initi...

(AI_HYDRA_LEO_MISSING_OF)


[style] ~1311-~1311: This phrase is redundant. Consider writing “evolve”.
Context: ... questions and answers json fields will evolve over time. The initial format is the following: ...

(EVOLVE_OVER_TIME)


1315-1339: Validate JSON example syntax

Ensure the sample JSON is valid (e.g., remove trailing commas after the last property) and wrap it in a proper code block:

{
  "urn:intuitem:risk:req_node:example:a.1:question:1": {
    "type": "unique_choice",
    "choices": [
      { "urn": "...", "value": "yes" },
      { "urn": "...", "value": "no" },
      { "urn": "...", "value": "n/a" }
    ],
    "text": "Question title"
  },
  ...
}

1341-1352: Validate answers JSON snippet

The answers example should be valid JSON and illustrate both array and scalar cases. For clarity, separate the two cases or include a note explaining when each is used:

{
  "urn:intuitem:risk:req_node:example:a.1:question:1": [
    "urn:intuitem:risk:framework:example:answer01:choice:1",
    "urn:intuitem:risk:framework:example:answer01:choice:2"
  ],
  "urn:intuitem:risk:req_node:example:a.1:question:2": "yes"
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 521cec3 and f4110f5.

📒 Files selected for processing (13)
  • backend/core/models.py (6 hunks)
  • backend/core/serializers.py (2 hunks)
  • documentation/architecture/data-model.md (3 hunks)
  • frontend/messages/cs.json (1 hunks)
  • frontend/messages/de.json (1 hunks)
  • frontend/messages/en.json (1 hunks)
  • frontend/messages/es.json (1 hunks)
  • frontend/messages/fr.json (1 hunks)
  • frontend/messages/id.json (1 hunks)
  • frontend/messages/nl.json (1 hunks)
  • frontend/messages/pt.json (1 hunks)
  • frontend/messages/ro.json (1 hunks)
  • frontend/src/lib/utils/schemas.ts (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • frontend/messages/nl.json
  • frontend/messages/id.json
  • frontend/messages/cs.json
  • frontend/messages/es.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • frontend/src/lib/utils/schemas.ts
  • frontend/messages/fr.json
  • backend/core/serializers.py
  • frontend/messages/de.json
  • frontend/messages/pt.json
  • frontend/messages/ro.json
  • backend/core/models.py
🧰 Additional context used
🪛 LanguageTool
documentation/architecture/data-model.md

[uncategorized] ~1311-~1311: Possible missing preposition found.
Context: ...t The format for questions and answers json fields will evolve over time. The initi...

(AI_HYDRA_LEO_MISSING_OF)


[style] ~1311-~1311: This phrase is redundant. Consider writing “evolve”.
Context: ... questions and answers json fields will evolve over time. The initial format is the following: ...

(EVOLVE_OVER_TIME)

🪛 Biome (1.9.4)
frontend/messages/en.json

[error] 1390-1390: The key date 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 (8)
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-functional-tests (3.12, chromium)
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: functional-tests (3.12, chromium)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
documentation/architecture/data-model.md (1)

1285-1285: Heading follows existing conventions

The new ### Requirement assessment heading aligns with other model headings and is consistent with the document structure.

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 (2)
backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (2)

24-27: Unused variable choices_maps can be removed

choices_maps is built but never referenced, creating dead code and minor overhead.

-    # Pre-build choices map for efficiency
-    choices_maps = {}
-    for idx, choice_text in enumerate(choices_texts):
-        choices_maps[idx] = {"urn": f"choice:{idx + 1}", "value": choice_text}

Simply delete these lines (or actually use the structure if you intended to).
Tidying this up avoids confusion for future maintainers.


69-83: Load only required fields when iterating to reduce memory footprint

Requirementnode.objects.iterator() pulls all columns for every row, which can be heavy if the table is large.
You only need id, urn, and question, so restrict the query:

-        for instance in Requirementnode.objects.iterator():
+        for instance in (
+            Requirementnode.objects.only("id", "urn", "question").iterator()
+        ):

This keeps each ORM object light and speeds up the migration.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4110f5 and dd4ee43.

📒 Files selected for processing (1)
  • backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (1)
backend/core/models.py (2)
  • RequirementAssessment (3761-3932)
  • RequirementNode (1096-1166)
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: functional-tests (3.12, chromium)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: build (3.12)
  • GitHub Check: enterprise-functional-tests (3.12, chromium)
  • GitHub Check: Analyze (python)
  • GitHub Check: test (3.12)
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines 155 to 166
# Handle different question types
if question_type in ["text", "date"]:
# For text and date types, keep the answer value as is
new_answers[question_urn] = answer_value
else:
# Handle single-choice answers
if isinstance(answer_value, str):
if answer_value in choices_map:
new_answers[question_urn] = choices_map[answer_value]
else:
# Keep free-text answers as they are
new_answers[question_urn] = answer_value
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Multiple‑choice answers containing lists are silently dropped

answer_value is assumed to be a string. For multiple‑choice questions the legacy data may be a list of texts.
Current logic skips these, so answers are lost.

Guard for lists and map each item:

-                    else:
-                        # Handle single-choice answers
-                        if isinstance(answer_value, str):
-                            if answer_value in choices_map:
-                                new_answers[question_urn] = choices_map[answer_value]
-                            else:
-                                # Keep free-text answers as they are
-                                new_answers[question_urn] = answer_value
+                    else:
+                        if isinstance(answer_value, list):
+                            new_answers[question_urn] = [
+                                choices_map.get(v, v) for v in answer_value
+                            ]
+                        elif isinstance(answer_value, str):
+                            new_answers[question_urn] = choices_map.get(
+                                answer_value, answer_value
+                            )

Please confirm the expected legacy shape and adjust accordingly.


#!/bin/bash
set -e

Locate the migration file

MIGRATION_FILE=$(find . -type f | rg '0069_remove_requirementassessment_answer_and_more.py')
echo "Migration file found at: $MIGRATION_FILE"
echo
echo "---- Snippet around lines 155-166 ----"
sed -n '140,180p' "$MIGRATION_FILE"
echo
echo "---- Checking for existing list handling of answer_value ----"
grep -En "isinstance\s*(\sanswer_value\s,\slist\s)" "$MIGRATION_FILE" || echo "No isinstance(..., list) checks found in this file."


🏁 Script executed:

#!/bin/bash
set -e
# Find the migration file
MIGRATION_FILE=$(fd --full-path 0069_remove_requirementassessment_answer_and_more.py)
echo "Migration file located at: $MIGRATION_FILE"
echo
# Show the context around the reported lines
echo "---- Lines 150-185 of the migration ----"
sed -n '150,185p' "$MIGRATION_FILE"
echo
# Check for any handling of list-type answer_value
echo "---- Checking for isinstance(answer_value, list) ----"
grep -En "isinstance\s*\(\s*answer_value\s*,\s*list\s*\)" "$MIGRATION_FILE" || echo "No list handling detected."

Length of output: 2520


Handle list‑type multiple‑choice answers

The current migration only checks for str values, so any legacy answers stored as lists (e.g. multiple‑choice questions) will be dropped. In backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (around lines 155–166), update the else‑block to something like:

                     else:
-                        # Handle single-choice answers
-                        if isinstance(answer_value, str):
-                            if answer_value in choices_map:
-                                new_answers[question_urn] = choices_map[answer_value]
-                            else:
-                                # Keep free-text answers as they are
-                                new_answers[question_urn] = answer_value
+                        # Handle multiple‑ and single‑choice answers
+                        if isinstance(answer_value, list):
+                            # Map each selected option, keep free‑text items
+                            new_answers[question_urn] = [
+                                choices_map.get(item, item) for item in answer_value
+                            ]
+                        elif isinstance(answer_value, str):
+                            new_answers[question_urn] = choices_map.get(
+                                answer_value, answer_value
+                            )

• Please confirm that legacy multiple‑choice answers indeed arrive as list[str] and adjust this logic if the shape differs.

📝 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
# Handle different question types
if question_type in ["text", "date"]:
# For text and date types, keep the answer value as is
new_answers[question_urn] = answer_value
else:
# Handle single-choice answers
if isinstance(answer_value, str):
if answer_value in choices_map:
new_answers[question_urn] = choices_map[answer_value]
else:
# Keep free-text answers as they are
new_answers[question_urn] = answer_value
# Handle different question types
if question_type in ["text", "date"]:
# For text and date types, keep the answer value as is
new_answers[question_urn] = answer_value
else:
# Handle multiple- and single-choice answers
if isinstance(answer_value, list):
# Map each selected option, keep free-text items
new_answers[question_urn] = [
choices_map.get(item, item) for item in answer_value
]
elif isinstance(answer_value, str):
# Map single-choice answers, or keep free-text
new_answers[question_urn] = choices_map.get(
answer_value, answer_value
)

Copy link
Contributor

@nas-tabchiche nas-tabchiche left a comment

Choose a reason for hiding this comment

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

It looks okay to me functionally, however there are a few changes to bring to this PR before it can be merged:

  • demo data cannot be loaded (as shown in functional tests, does not work on my machine either). The dummy-domain file might be out of date.
  • when incrementing schema version, keep old sample-domain-schema-{n}.bak file, and just add sample-domain-schema-{n+1}.bak
  • are you sure the data migration is running? I see no calls to migrations.RunPython on the migration file.

@ab-smith
Copy link
Contributor

Migration alignment needed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
backend/core/migrations/0070_remove_requirementassessment_answer_and_more.py (2)

10-15: Doc-string still describes the old single-question model

The doc-string speaks about a field named "question" and a list named "questions" in the legacy schema, which is misleading – until this migration there was only "question". Please update the description so future maintainers do not waste time chasing a non-existent questions attribute in the old model.


23-27: choices_maps is computed but never used

choices_maps is built on lines 23-27 and then ignored. Either remove the block or use the map inside the loop to avoid running a wasted O(N) loop per node.

-    # Pre-build choices map for efficiency
-    choices_maps = {}
-    for idx, choice_text in enumerate(choices_texts):
-        choices_maps[idx] = {"urn": f"choice:{idx + 1}", "value": choice_text}
documentation/architecture/data-model.md (1)

1145-1146: Pluralisation mismatch with the new schema

The bullet still says “a requirement node can include questions … corresponds to a JSON answer”.
To stay consistent with the code & migration, change “answer” → “answers”.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c888f2d and d13cf53.

📒 Files selected for processing (4)
  • backend/core/migrations/0070_remove_requirementassessment_answer_and_more.py (1 hunks)
  • documentation/architecture/data-model.md (3 hunks)
  • frontend/messages/en.json (1 hunks)
  • frontend/messages/fr.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/messages/en.json
  • frontend/messages/fr.json
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/core/migrations/0070_remove_requirementassessment_answer_and_more.py (1)
backend/core/models.py (2)
  • RequirementAssessment (3761-3932)
  • RequirementNode (1096-1166)
🪛 LanguageTool
documentation/architecture/data-model.md

[style] ~1311-~1311: This phrase is redundant. Consider writing “evolve”.
Context: ... questions and answers json fields will evolve over time. The initial format is the following: ...

(EVOLVE_OVER_TIME)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: enterprise-functional-tests (3.12, chromium)
  • GitHub Check: build (3.12)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: functional-tests (3.12, chromium)
  • GitHub Check: test (3.12)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines 208 to 229
operations = [
migrations.RemoveField(
model_name="requirementassessment",
name="answer",
),
migrations.RunPython(migrate_questions, reverse_migrations),
migrations.RemoveField(
model_name="requirementnode",
name="question",
),
migrations.AddField(
model_name="requirementassessment",
name="answers",
field=models.JSONField(blank=True, null=True, verbose_name="Answers"),
),
migrations.RunPython(migrate_answers_format, reverse_migrations),
migrations.AddField(
model_name="requirementnode",
name="questions",
field=models.JSONField(blank=True, null=True, verbose_name="Questions"),
),
]
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

Broken migration ordering – columns removed before data is copied

The current sequence:

  1. Remove answer
  2. Run migrate_questions
  3. Remove question
  4. Add answers
  5. Run migrate_answers_format
  6. Add questions

means the data you are trying to migrate no longer exists and the target columns are missing at migration time.

Re-order the operations so that new columns are created first, data is copied, and only then are the legacy columns dropped:

     operations = [
-        migrations.RemoveField(
-            model_name="requirementassessment",
-            name="answer",
-        ),
-        migrations.RunPython(migrate_questions, reverse_migrations),
-        migrations.RemoveField(
-            model_name="requirementnode",
-            name="question",
-        ),
-        migrations.AddField(
-            model_name="requirementassessment",
-            name="answers",
-            field=models.JSONField(blank=True, null=True, verbose_name="Answers"),
-        ),
-        migrations.RunPython(migrate_answers_format, reverse_migrations),
-        migrations.AddField(
-            model_name="requirementnode",
-            name="questions",
-            field=models.JSONField(blank=True, null=True, verbose_name="Questions"),
-        ),
+        # 1️⃣  Add new columns first
+        migrations.AddField(
+            model_name="requirementnode",
+            name="questions",
+            field=models.JSONField(blank=True, null=True, verbose_name="Questions"),
+        ),
+        migrations.AddField(
+            model_name="requirementassessment",
+            name="answers",
+            field=models.JSONField(blank=True, null=True, verbose_name="Answers"),
+        ),
+        # 2️⃣  Migrate data
+        migrations.RunPython(migrate_questions, reverse_migrations),
+        migrations.RunPython(migrate_answers_format, reverse_migrations),
+        # 3️⃣  Drop legacy columns
+        migrations.RemoveField(
+            model_name="requirementassessment",
+            name="answer",
+        ),
+        migrations.RemoveField(
+            model_name="requirementnode",
+            name="question",
+        ),
     ]

This preserves data and avoids runtime failures.

📝 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
operations = [
migrations.RemoveField(
model_name="requirementassessment",
name="answer",
),
migrations.RunPython(migrate_questions, reverse_migrations),
migrations.RemoveField(
model_name="requirementnode",
name="question",
),
migrations.AddField(
model_name="requirementassessment",
name="answers",
field=models.JSONField(blank=True, null=True, verbose_name="Answers"),
),
migrations.RunPython(migrate_answers_format, reverse_migrations),
migrations.AddField(
model_name="requirementnode",
name="questions",
field=models.JSONField(blank=True, null=True, verbose_name="Questions"),
),
]
operations = [
# 1️⃣ Add new columns first
migrations.AddField(
model_name="requirementnode",
name="questions",
field=models.JSONField(blank=True, null=True, verbose_name="Questions"),
),
migrations.AddField(
model_name="requirementassessment",
name="answers",
field=models.JSONField(blank=True, null=True, verbose_name="Answers"),
),
# 2️⃣ Migrate data
migrations.RunPython(migrate_questions, reverse_migrations),
migrations.RunPython(migrate_answers_format, reverse_migrations),
# 3️⃣ Drop legacy columns
migrations.RemoveField(
model_name="requirementassessment",
name="answer",
),
migrations.RemoveField(
model_name="requirementnode",
name="question",
),
]

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/core/migrations/0069_remove_requirementassessment_answer_and_more.py (1)

155-166: ⚠️ Potential issue

Retain multiple-choice answers stored as lists.

The current branch only handles str answers; legacy multiple-choice answers arriving as list[str] are discarded.
Apply the earlier review’s logic:

-                        # Handle single-choice answers
-                        if isinstance(answer_value, str):
-                            if answer_value in choices_map:
-                                new_answers[question_urn] = choices_map[answer_value]
-                            else:
-                                # Keep free-text answers as they are
-                                new_answers[question_urn] = answer_value
+                        # Handle multiple- and single-choice answers
+                        if isinstance(answer_value, list):
+                            new_answers[question_urn] = [
+                                choices_map.get(v, v) for v in answer_value
+                            ]
+                        elif isinstance(answer_value, str):
+                            new_answers[question_urn] = choices_map.get(
+                                answer_value, answer_value
+                            )

This preserves all user responses and aligns with the new schema.

🧹 Nitpick comments (1)
backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (1)

23-27: Remove unused choices_maps variable.

choices_maps is built but never referenced, creating dead code and a misleading hint that an optimisation exists.

-    # Pre-build choices map for efficiency
-    choices_maps = {}
-    for idx, choice_text in enumerate(choices_texts):
-        choices_maps[idx] = {"urn": f"choice:{idx + 1}", "value": choice_text}

Simply drop these lines or utilise the map in the loop below.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d13cf53 and 6bd300c.

📒 Files selected for processing (1)
  • backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (1)
backend/core/models.py (2)
  • RequirementAssessment (3761-3932)
  • RequirementNode (1096-1166)
🔇 Additional comments (2)
backend/core/migrations/0069_remove_requirementassessment_answer_and_more.py (2)

208-229: 👍 Operations order now preserves data before dropping legacy columns.

The sequence “add → migrate → remove” for both RequirementNode and RequirementAssessment mitigates data-loss risk flagged in earlier reviews.


195-201: Consider implementing a minimal rollback or explicit no_reverse_sql=True.

The stubbed reverse_migrations logs a warning but leaves the migration technically reversible in Django’s eyes.
Either:

  1. Implement an actual down-migration, or
  2. Mark it irreversible to avoid false expectations:
migrations.RunPython(migrate_questions, reverse_code=migrations.RunPython.noop)

or keep the helper but raise IrreversibleError.

@intuitem intuitem deleted a comment from coderabbitai bot Apr 28, 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: 1

🧹 Nitpick comments (3)
backend/core/migrations/0071_remove_requirementassessment_answer_and_more.py (3)

24-27: choices_maps is computed but never used – drop or repurpose it

The loop builds choices_maps, yet the variable is not referenced afterwards.
Either remove these four lines to avoid the needless O(n) work or reuse the pre-computed structure inside the later loop to avoid recomputing choices for every question.

-    # Pre-build choices map for efficiency
-    choices_maps = {}
-    for idx, choice_text in enumerate(choices_texts):
-        choices_maps[idx] = {"urn": f"choice:{idx + 1}", "value": choice_text}

67-83: Iterator chunk size is not honoured

batch_size = 1000, but Requirementnode.objects.iterator() defaults to 2 000.
Pass the desired chunk explicitly to keep memory usage predictable:

-        for instance in Requirementnode.objects.iterator():
+        for instance in Requirementnode.objects.iterator(chunk_size=batch_size):

195-201: One-way migration: consider at least a minimal reverse implementation

The stub reverse merely logs a warning. While one-way migrations are acceptable, Django will still show them as irreversible. Providing a minimal reverse (e.g., copying questions → question and answers → answer without complex mapping) allows teams to back out of the release if unexpected issues arise.

If full reversal is impractical, add elidable = True to the operation or mark the RunPython steps as elidable=True to suppress the Django warning.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adb221d and 558e451.

📒 Files selected for processing (14)
  • backend/core/migrations/0071_remove_requirementassessment_answer_and_more.py (1 hunks)
  • backend/core/models.py (6 hunks)
  • backend/core/serializers.py (2 hunks)
  • frontend/messages/cs.json (1 hunks)
  • frontend/messages/de.json (1 hunks)
  • frontend/messages/en.json (1 hunks)
  • frontend/messages/es.json (1 hunks)
  • frontend/messages/fr.json (1 hunks)
  • frontend/messages/id.json (1 hunks)
  • frontend/messages/it.json (1 hunks)
  • frontend/messages/nl.json (1 hunks)
  • frontend/messages/pt.json (1 hunks)
  • frontend/messages/ro.json (1 hunks)
  • frontend/src/lib/utils/schemas.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • frontend/messages/cs.json
  • frontend/messages/it.json
🚧 Files skipped from review as they are similar to previous changes (11)
  • frontend/src/lib/utils/schemas.ts
  • backend/core/serializers.py
  • frontend/messages/ro.json
  • frontend/messages/de.json
  • frontend/messages/nl.json
  • frontend/messages/id.json
  • frontend/messages/fr.json
  • frontend/messages/es.json
  • frontend/messages/en.json
  • frontend/messages/pt.json
  • backend/core/models.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/core/migrations/0071_remove_requirementassessment_answer_and_more.py (1)
backend/core/models.py (2)
  • RequirementAssessment (3819-3990)
  • RequirementNode (1097-1167)
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: startup-functional-test (3.12)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-startup-functional-test (3.12)
  • GitHub Check: functional-tests (3.12, chromium)
  • GitHub Check: Analyze (python)
  • GitHub Check: enterprise-functional-tests (3.12, chromium)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: test (3.12)
  • GitHub Check: build (3.12)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
backend/core/migrations/0071_remove_requirementassessment_answer_and_more.py (1)

37-52: Assumes one question_type per node – verify this matches legacy data

question_type is read once from old_data and applied to every question in the loop.
If the legacy schema ever allowed different types per question (e.g., a mix of text and choice questions under one node), all questions will inherit the same (possibly wrong) type here, causing downstream misclassification.

Please confirm the historical constraints; if heterogeneous types were allowed, move the type into each question entry in the original payload and derive it per-question instead.

Comment on lines +131 to +139
# Process assessments in batches
batch_size = 1000
assessments_to_update = []

for instance in RequirementAssessment.objects.iterator():
old_answer_data = getattr(instance, "answer", None)
new_answers = {}

if not old_answer_data or not isinstance(old_answer_data, dict):
Copy link
Contributor

@coderabbitai coderabbitai bot May 6, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Multi-choice answers are silently discarded – data loss risk

migrate_answers_format handles choice answers only when answer_value is a str.
For “multipleChoice” questions the legacy answer is typically a list/tuple of strings.
These will fail the isinstance(answer_value, str) check and be skipped, so the user’s answers disappear.

Proposed fix (inside the else branch):

-                        # Handle single-choice answers
-                        if isinstance(answer_value, str):
-                            if answer_value in choices_map:
-                                new_answers[question_urn] = choices_map[answer_value]
-                            else:
-                                # Keep free-text answers as they are
-                                new_answers[question_urn] = answer_value
+                        # Single-choice string or multi-choice list/tuple
+                        if isinstance(answer_value, str):
+                            new_answers[question_urn] = choices_map.get(
+                                answer_value, answer_value
+                            )
+                        elif isinstance(answer_value, (list, tuple)):
+                            new_answers[question_urn] = [
+                                choices_map.get(v, v) for v in answer_value
+                            ]
+                        else:
+                            # Fallback – store raw value to avoid data loss
+                            new_answers[question_urn] = answer_value

Without this, historical multiple-choice data will be irretrievably lost.

Also applies to: 155-167

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good but "multiple choices" was not an option before this PR

Copy link
Contributor

Choose a reason for hiding this comment

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

@Mohamed-Hacene You're absolutely right! After reviewing the code changes across the PR, I can see that "multiple choice" is a new question type being introduced in this refactoring. The migration doesn't need to handle arrays of answers since they didn't exist in the previous data model.

The current migration logic is correct since it only needs to handle the question types that existed before (text, date, and single-choice questions). Thanks for clarifying this point!

@nas-tabchiche nas-tabchiche self-requested a review May 7, 2025 16:19
Copy link
Collaborator

@eric-intuitem eric-intuitem left a comment

Choose a reason for hiding this comment

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

LGTM

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.

7 participants