|
21 | 21 | from db.models.api_test_case import ApiTestCaseModel # noqa E402 |
22 | 22 | from db.models.api_test_specification import ApiTestSpecificationModel # noqa E402 |
23 | 23 | from db.models.document import DocumentModel # noqa E402 |
| 24 | +from db.models.document_document import DocumentDocumentModel # noqa E402 |
24 | 25 | from db.models.justification import JustificationModel # noqa E402 |
25 | 26 | from db.models.sw_requirement import SwRequirementModel # noqa E402 |
26 | 27 | from db.models.sw_requirement_sw_requirement import SwRequirementSwRequirementModel # noqa E402 |
@@ -1052,6 +1053,52 @@ def addTestRun(self, test_run: TestRunModel = None, dbsession=None): |
1052 | 1053 | self.add_to_sbom(tr_annotation) |
1053 | 1054 | return tr_file |
1054 | 1055 |
|
| 1056 | + def addDocumentsNestedElements( |
| 1057 | + self, |
| 1058 | + api: ApiModel = None, |
| 1059 | + xdoc: Optional[Union[ApiDocumentModel, DocumentDocumentModel]] = None, |
| 1060 | + spdx_doc: SPDXFile = None, # SPDX object of Document from xdoc.document |
| 1061 | + dbsession=None, |
| 1062 | + ): |
| 1063 | + """In BASIL user can create a complex hierarchy of Documents. |
| 1064 | + Moreover we can assign other work items to each Document in the chain. |
| 1065 | + This method navigate the hierarchy and return all the work items |
| 1066 | +
|
| 1067 | + :param api: software component where the mapping is defined |
| 1068 | + :param xdoc: Document mapping model instance |
| 1069 | + :param spdx_doc: DocumentSPDX instance |
| 1070 | + :param dbi: Database interface instance |
| 1071 | + :return: |
| 1072 | + """ |
| 1073 | + if isinstance(xdoc, ApiDocumentModel): |
| 1074 | + mapping_field = f"{ApiDocumentModel.__tablename__}" |
| 1075 | + mapping_field_id = f"{mapping_field}_id" |
| 1076 | + elif isinstance(xdoc, DocumentDocumentModel): |
| 1077 | + mapping_field = f"{DocumentDocumentModel.__tablename__}" |
| 1078 | + mapping_field_id = f"{mapping_field}_id" |
| 1079 | + else: |
| 1080 | + return |
| 1081 | + |
| 1082 | + # DocumentDocumentModel |
| 1083 | + doc_docs = ( |
| 1084 | + dbsession.query(DocumentDocumentModel) |
| 1085 | + .filter(getattr(DocumentDocumentModel, mapping_field_id) == xdoc.id) |
| 1086 | + .all() |
| 1087 | + ) |
| 1088 | + for doc_doc in doc_docs: |
| 1089 | + spdx_doc_doc = self.addDocument(document=doc_doc.document, dbsession=dbsession) |
| 1090 | + self.addRelationship( |
| 1091 | + from_element=spdx_doc, |
| 1092 | + to=[spdx_doc_doc], |
| 1093 | + relationship_type="hasDocument", |
| 1094 | + completeness_percentage=doc_doc.coverage, |
| 1095 | + ) |
| 1096 | + |
| 1097 | + # DocumentDocument |
| 1098 | + self.addDocumentsNestedElements( |
| 1099 | + api=api, xdoc=doc_doc, spdx_doc=spdx_doc_doc, dbsession=dbsession |
| 1100 | + ) |
| 1101 | + |
1055 | 1102 | def addSoftwareRequirementNestedElements( |
1056 | 1103 | self, |
1057 | 1104 | api: ApiModel = None, |
@@ -1275,6 +1322,8 @@ def addApiDocuments(self, spdx_api=None, spdx_api_ref_doc=None, api: ApiModel = |
1275 | 1322 | completeness_percentage=adoc.coverage, |
1276 | 1323 | ) |
1277 | 1324 |
|
| 1325 | + self.addDocumentsNestedElements(api=api, xdoc=adoc, spdx_doc=spdx_doc, dbsession=dbsession) |
| 1326 | + |
1278 | 1327 | def addApiJustifications(self, spdx_api=None, spdx_api_ref_doc=None, api: ApiModel = None, dbsession=None): |
1279 | 1328 | # ApiJustifications |
1280 | 1329 | api_justifications = ( |
@@ -1302,6 +1351,21 @@ def generate_diagraph(self, output_file: str = ""): |
1302 | 1351 | - output_file: output PNG file name |
1303 | 1352 | """ |
1304 | 1353 |
|
| 1354 | + def allowed_duplicate_node(node): |
| 1355 | + allowed_ids = [ |
| 1356 | + "basil:document:", |
| 1357 | + "basil:software-requirement:", |
| 1358 | + "basil:test-specification:", |
| 1359 | + "basil:test-case:", |
| 1360 | + "basil:test-run:", |
| 1361 | + "basil:justification:", |
| 1362 | + ] |
| 1363 | + |
| 1364 | + ret = any(allowed_id in node.spdx_id for allowed_id in allowed_ids) |
| 1365 | + if ret: |
| 1366 | + return True |
| 1367 | + return False |
| 1368 | + |
1305 | 1369 | def get_file_node_color(node): |
1306 | 1370 | purpose_colors = { |
1307 | 1371 | "library": "brown", |
@@ -1339,45 +1403,59 @@ def add_edge(src, dst, label): |
1339 | 1403 | # Add edges with appropriate colors |
1340 | 1404 | relationships = [item for item in self.sbom if isinstance(item, SPDXRelationship)] |
1341 | 1405 |
|
| 1406 | + iRel = 0 |
1342 | 1407 | for relationship in relationships: |
1343 | 1408 | if not [item for item in relationship.to if isinstance(item, SPDXFile)]: |
1344 | 1409 | continue |
1345 | 1410 |
|
| 1411 | + iRel += 1 |
1346 | 1412 | for to_relationship in relationship.to: |
| 1413 | + # Allow duplicates nodes for |
| 1414 | + # each work items as using the same node can results into a wrong mapping graph |
1347 | 1415 | from_node = relationship.from_element |
1348 | | - from_node_str = from_node.spdx_id.replace(":", "_") |
| 1416 | + from_node_label = from_node.spdx_id.replace(":", "_") |
| 1417 | + if allowed_duplicate_node(from_node): |
| 1418 | + from_node_id = f"{iRel-1}_{from_node_label}" |
| 1419 | + else: |
| 1420 | + from_node_id = from_node_label |
1349 | 1421 | from_color = get_file_node_color(from_node) |
1350 | 1422 |
|
1351 | 1423 | to_node = to_relationship |
1352 | | - to_node_str = to_node.spdx_id.replace(":", "_") |
| 1424 | + to_node_label = to_node.spdx_id.replace(":", "_") |
| 1425 | + if allowed_duplicate_node(to_node): |
| 1426 | + to_node_id = f"{iRel}_{to_node_label}" |
| 1427 | + else: |
| 1428 | + to_node_id = to_node_label |
1353 | 1429 | to_color = get_file_node_color(to_node) |
1354 | 1430 |
|
1355 | 1431 | # Add 'from' node with color based on type |
1356 | | - if from_node_str not in added_nodes: |
1357 | | - dot.node(from_node_str, style="filled", fillcolor=from_color) |
1358 | | - added_nodes[from_node_str] = from_color |
| 1432 | + if from_node_id not in added_nodes: |
| 1433 | + dot.node(from_node_id, label=from_node_label, style="filled", fillcolor=from_color) |
| 1434 | + added_nodes[from_node_id] = from_color |
1359 | 1435 |
|
1360 | 1436 | # Add 'to' node with color based on type |
1361 | | - if to_node_str not in added_nodes: |
1362 | | - dot.node(to_node_str, style="filled", fillcolor=to_color) |
1363 | | - added_nodes[to_node_str] = to_color |
| 1437 | + if to_node_id not in added_nodes: |
| 1438 | + dot.node(to_node_id, label=to_node_label, style="filled", fillcolor=to_color) |
| 1439 | + added_nodes[to_node_id] = to_color |
1364 | 1440 |
|
1365 | 1441 | if isinstance(to_node, SPDXSnippet): |
1366 | | - from_file_str = to_node.from_file.spdx_id.replace(":", "_") |
1367 | | - if from_file_str not in added_nodes: |
1368 | | - dot.node(from_file_str, style="filled", fillcolor="yellow") |
1369 | | - added_nodes[from_file_str] = "yellow" |
1370 | | - add_edge(from_file_str, to_node_str, "contains") |
| 1442 | + from_file_label = to_node.from_file.spdx_id.replace(":", "_") |
| 1443 | + from_file_id = from_file_label |
| 1444 | + if from_file_id not in added_nodes: |
| 1445 | + dot.node(from_file_id, label=from_file_label, style="filled", fillcolor="yellow") |
| 1446 | + added_nodes[from_file_id] = "yellow" |
| 1447 | + add_edge(from_file_id, to_node_id, "contains") |
1371 | 1448 |
|
1372 | 1449 | if isinstance(from_node, SPDXSnippet): |
1373 | | - from_file_str = from_node.from_file.spdx_id.replace(":", "_") |
1374 | | - if from_file_str not in added_nodes: |
1375 | | - dot.node(from_file_str, style="filled", fillcolor="yellow") |
1376 | | - added_nodes[from_file_str] = "yellow" |
1377 | | - add_edge(from_file_str, from_node_str, "contains") |
| 1450 | + from_file_label = from_node.from_file.spdx_id.replace(":", "_") |
| 1451 | + from_file_id = from_file_label |
| 1452 | + if from_file_id not in added_nodes: |
| 1453 | + dot.node(from_file_id, label=from_file_label, style="filled", fillcolor="yellow") |
| 1454 | + added_nodes[from_file_id] = "yellow" |
| 1455 | + add_edge(from_file_id, from_node_id, "contains") |
1378 | 1456 |
|
1379 | 1457 | # Add edge without special color |
1380 | | - add_edge(from_node_str, to_node_str, label=relationship.relationship_type) |
| 1458 | + add_edge(from_node_id, to_node_id, label=relationship.relationship_type) |
1381 | 1459 |
|
1382 | 1460 | # Populate dot edges |
1383 | 1461 | for edge in sorted(added_edges): |
|
0 commit comments