Skip to content

Commit dd9ba0e

Browse files
authored
Merge pull request #8 from baseVISION/feature/case-template-nested-note-directories
[IMP] Support nested note_directories in case templates
2 parents 7e0529f + 526500a commit dd9ba0e

5 files changed

Lines changed: 112 additions & 41 deletions

File tree

source/app/blueprints/pages/manage/manage_case_templates_routes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ def add_template_modal(caseid, url_redir):
116116
"title": "Note 1",
117117
"content": "Note 1 content"
118118
}
119+
],
120+
"note_directories": [
121+
{
122+
"title": "Sub directory 1",
123+
"notes": [
124+
{
125+
"title": "Sub note 1",
126+
"content": "Sub note 1 content"
127+
}
128+
]
129+
}
119130
]
120131
}
121132
]

source/app/blueprints/pages/manage/templates/modal_case_template.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ <h4>Field types</h4>
5050
<li>summary: content to prefill the summary.</li>
5151
<li>tags: A list of case tags.</li>
5252
<li>tasks: A list of dictionaries defining tasks. Tasks are defined by title (required), description, and list of tags.</li>
53-
<li>note_directories: A list of dictionaries defining note directories. Note directories are defined by title (required), and list of notes. Notes have title (required) and content</li>
53+
<li>note_directories: A list of dictionaries defining note directories. Each directory is defined by title (required), an optional list of notes (each with title (required) and optional content), and an optional nested <code>note_directories</code> list to create sub-directory trees of arbitrary depth.</li>
5454
</ul>
5555
</div>
5656
</div>
@@ -102,6 +102,17 @@ <h4>Field types</h4>
102102
"title": "Identify the compromised accounts",
103103
"content": "# Observations\n\n"
104104
}
105+
],
106+
"note_directories": [
107+
{
108+
"title": "Sub-investigation",
109+
"notes": [
110+
{
111+
"title": "Detailed findings",
112+
"content": "# Findings\n\n"
113+
}
114+
]
115+
}
105116
]
106117
},
107118
{

source/app/blueprints/pages/manage/templates/modal_upload_case_template.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ <h4>Upload case template</h4>
6464
"title": "Identify the compromised accounts",
6565
"content": "# Observations\n\n"
6666
}
67+
],
68+
"note_directories": [
69+
{
70+
"title": "Sub-investigation",
71+
"notes": [
72+
{
73+
"title": "Detailed findings",
74+
"content": "# Findings\n\n"
75+
}
76+
]
77+
}
6778
]
6879
},
6980
{
@@ -93,7 +104,7 @@ <h4>Field types</h4>
93104
<li>summary: content to prefill the summary.</li>
94105
<li>tags: A list of case tags.</li>
95106
<li>tasks: A list of dictionaries defining tasks. Tasks are defined by title (required), description, and list of tags.</li>
96-
<li>note_groups: A list of dictionaries defining note groups. Note groups are defined by title (required), and list of notes. Notes have title (required) and content</li>
107+
<li>note_directories: A list of dictionaries defining note directories. Each directory is defined by title (required), an optional list of notes (each with title (required) and optional content), and an optional nested <code>note_directories</code> list to create sub-directory trees of arbitrary depth.</li>
97108
</ul>
98109
</div>
99110
</div>

source/app/datamgmt/manage/manage_case_templates_db.py

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,37 @@ def delete_case_template_by_id(case_template_id: int):
8282
CaseTemplate.query.filter_by(id=case_template_id).delete()
8383

8484

85+
def _validate_note_dir_entry(note_dir: dict) -> Optional[str]:
86+
"""Recursively validate a note directory entry from a case template.
87+
88+
Args:
89+
note_dir (dict): The note directory entry to validate.
90+
91+
Returns:
92+
Optional[str]: An error message if validation fails, or None if successful.
93+
"""
94+
if not isinstance(note_dir, dict):
95+
return "Each note directory must be a dictionary."
96+
if "title" not in note_dir:
97+
return "Each note directory must have a 'title' field."
98+
if "notes" in note_dir:
99+
if not isinstance(note_dir["notes"], list):
100+
return "Notes must be a list."
101+
for note in note_dir["notes"]:
102+
if not isinstance(note, dict):
103+
return "Each note must be a dictionary."
104+
if "title" not in note:
105+
return "Each note must have a 'title' field."
106+
if "note_directories" in note_dir:
107+
if not isinstance(note_dir["note_directories"], list):
108+
return "Nested note_directories must be a list."
109+
for sub_dir in note_dir["note_directories"]:
110+
error = _validate_note_dir_entry(sub_dir)
111+
if error:
112+
return error
113+
return None
114+
115+
85116
def validate_case_template(data: dict, update: bool = False) -> Optional[str]:
86117
try:
87118
if not update:
@@ -139,18 +170,9 @@ def validate_case_template(data: dict, update: bool = False) -> Optional[str]:
139170
if not isinstance(data["note_directories"], list):
140171
return "Note directories must be a list."
141172
for note_dir in data["note_directories"]:
142-
if not isinstance(note_dir, dict):
143-
return "Each note directory must be a dictionary."
144-
if "title" not in note_dir:
145-
return "Each note directory must have a 'title' field."
146-
if "notes" in note_dir:
147-
if not isinstance(note_dir["notes"], list):
148-
return "Notes must be a list."
149-
for note in note_dir["notes"]:
150-
if not isinstance(note, dict):
151-
return "Each note must be a dictionary."
152-
if "title" not in note:
153-
return "Each note must have a 'title' field."
173+
error = _validate_note_dir_entry(note_dir)
174+
if error:
175+
return error
154176

155177
# If all checks succeeded, we return None to indicate everything is has been validated
156178
return None
@@ -243,36 +265,52 @@ def case_template_populate_notes(case: Cases, note_dir_template: dict, ng: NoteD
243265
return logs
244266

245267

246-
def case_template_populate_note_groups(case: Cases, case_template: CaseTemplate):
268+
def _create_note_directory_recursive(case: Cases, note_dir_template: dict, parent_id: Optional[int]) -> list:
269+
"""Recursively create a note directory and its children from a template entry.
270+
271+
Args:
272+
case (Cases): The target case.
273+
note_dir_template (dict): The template entry for this directory.
274+
parent_id (Optional[int]): The DB id of the parent directory, or None for root.
275+
276+
Returns:
277+
list: Any error messages encountered during creation.
278+
"""
247279
logs = []
248-
# Update case tasks
249-
if case_template.note_directories:
250-
case_template.note_directories = case_template.note_directories
280+
try:
281+
note_dir_schema = CaseNoteDirectorySchema()
251282

252-
for note_dir_template in case_template.note_directories:
253-
try:
254-
# validate before saving
255-
note_dir_schema = CaseNoteDirectorySchema()
283+
mapped_note_dir_template = {
284+
"name": note_dir_template['title'],
285+
"parent_id": parent_id,
286+
"case_id": case.case_id
287+
}
256288

257-
# Remap case task template fields
258-
# Set status to "To Do" which is ID 1
259-
mapped_note_dir_template = {
260-
"name": note_dir_template['title'],
261-
"parent_id": None,
262-
"case_id": case.case_id
263-
}
289+
note_dir = note_dir_schema.load(mapped_note_dir_template)
290+
db_create(note_dir)
264291

265-
note_dir = note_dir_schema.load(mapped_note_dir_template)
266-
db_create(note_dir)
292+
if not note_dir:
293+
logs.append("Unable to add note directory for internal reasons")
294+
return logs
267295

268-
if not note_dir:
269-
logs.append("Unable to add note group for internal reasons")
270-
break
296+
logs += case_template_populate_notes(case, note_dir_template, note_dir)
271297

272-
logs = case_template_populate_notes(case, note_dir_template, note_dir)
298+
for sub_dir_template in note_dir_template.get("note_directories", []):
299+
logs += _create_note_directory_recursive(case, sub_dir_template, note_dir.id)
273300

274-
except marshmallow.exceptions.ValidationError as e:
275-
logs.append(e.messages)
301+
except marshmallow.exceptions.ValidationError as e:
302+
logs.append(e.messages)
303+
304+
return logs
305+
306+
307+
def case_template_populate_note_groups(case: Cases, case_template: CaseTemplate):
308+
logs = []
309+
if case_template.note_directories:
310+
case_template.note_directories = case_template.note_directories
311+
312+
for note_dir_template in case_template.note_directories:
313+
logs += _create_note_directory_recursive(case, note_dir_template, None)
276314

277315
return logs
278316

ui/src/pages/manage.case.templates.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ function add_case_template() {
3434
{value: 'summary', score: 1, meta: 'summary of the case'},
3535
{value: 'tags', score: 1, meta: 'tags of the case or the tasks'},
3636
{value: 'tasks', score: 1, meta: 'tasks of the case'},
37-
{value: 'note_groups', score: 1, meta: 'groups of notes'},
38-
{value: 'title', score: 1, meta: 'title of the task or the note group or the note'},
37+
{value: 'note_directories', score: 1, meta: 'note directories (supports nested note_directories)'},
38+
{value: 'title', score: 1, meta: 'title of the task, note directory, or note'},
3939
{value: 'content', score: 1, meta: 'content of the note'},
4040
]);
4141
},
@@ -198,8 +198,8 @@ function case_template_detail(ctempl_id) {
198198
{value: 'summary', score: 1, meta: 'summary of the case'},
199199
{value: 'tags', score: 1, meta: 'tags of the case or the tasks'},
200200
{value: 'tasks', score: 1, meta: 'tasks of the case'},
201-
{value: 'note_groups', score: 1, meta: 'groups of notes'},
202-
{value: 'title', score: 1, meta: 'title of the task or the note group or the note'},
201+
{value: 'note_directories', score: 1, meta: 'note directories (supports nested note_directories)'},
202+
{value: 'title', score: 1, meta: 'title of the task, note directory, or note'},
203203
{value: 'content', score: 1, meta: 'content of the note'},
204204
]);
205205
},

0 commit comments

Comments
 (0)