From 4e67892a7034dcdf46fd6776af5afabb6a09e4a4 Mon Sep 17 00:00:00 2001 From: Jesse Lisser Date: Wed, 12 Apr 2023 10:13:41 +0200 Subject: [PATCH 1/8] feature(octopoes): implement explanations endpoint --- octopoes/octopoes/api/router.py | 18 ++++++- octopoes/octopoes/connector/octopoes.py | 6 +++ octopoes/octopoes/core/service.py | 60 ++++++++++++++++++++- octopoes/octopoes/models/explanation.py | 15 ++++++ octopoes/tests/robot/01_scan_profiles.robot | 1 - octopoes/tests/robot/06_explanation.robot | 31 +++++++++++ octopoes/tests/robot/robot.resource | 13 ++--- 7 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 octopoes/octopoes/models/explanation.py create mode 100644 octopoes/tests/robot/06_explanation.robot diff --git a/octopoes/octopoes/api/router.py b/octopoes/octopoes/api/router.py index bee42d94246..8df0444f6c0 100644 --- a/octopoes/octopoes/api/router.py +++ b/octopoes/octopoes/api/router.py @@ -1,8 +1,8 @@ import uuid from datetime import datetime, timezone +from http import HTTPStatus from logging import getLogger from typing import List, Optional, Set, Type -from http import HTTPStatus from fastapi import APIRouter, Depends, HTTPException, Query, Path, status from requests import RequestException, HTTPError @@ -23,6 +23,7 @@ ) from octopoes.models.datetime import TimezoneAwareDatetime from octopoes.models.exception import ObjectNotFoundException +from octopoes.models.explanation import InheritanceSection from octopoes.models.origin import Origin, OriginType from octopoes.models.pagination import Paginated from octopoes.models.tree import ReferenceTree @@ -249,6 +250,21 @@ def recalculate_scan_profiles( xtdb_session_.commit() +@router.get("/explanations") +def get_explanations( + octopoes: OctopoesService = Depends(octopoes_service), + valid_time: datetime = Depends(extract_valid_time), + reference: Reference = Depends(extract_reference), +) -> List[InheritanceSection]: + ooi = octopoes.get_ooi(reference, valid_time) + root = InheritanceSection( + reference=ooi.reference, level=ooi.scan_profile.level, scan_profile_type=ooi.scan_profile.scan_profile_type + ) + if ooi.scan_profile.scan_profile_type == ScanProfileType.DECLARED: + return [root] + return octopoes.get_explanation(reference, valid_time, [root]) + + @router.post("/node") def create_node( client: str = Depends(extract_client), diff --git a/octopoes/octopoes/connector/octopoes.py b/octopoes/octopoes/connector/octopoes.py index 2d61ae8d729..4be17fb4ae0 100644 --- a/octopoes/octopoes/connector/octopoes.py +++ b/octopoes/octopoes/connector/octopoes.py @@ -18,6 +18,7 @@ DEFAULT_SCAN_PROFILE_TYPE_FILTER, ) from octopoes.models.exception import ObjectNotFoundException +from octopoes.models.explanation import InheritanceSection from octopoes.models.origin import Origin from octopoes.models.pagination import Paginated from octopoes.models.tree import ReferenceTree @@ -149,3 +150,8 @@ def create_node(self): def delete_node(self): self.session.delete(f"/{self.client}/node") + + def get_explanation(self, reference: Reference, valid_time: Optional[datetime] = None) -> List[InheritanceSection]: + params = {"reference": str(reference), "valid_time": valid_time} + res = self.session.get(f"/{self.client}/explanations", params=params) + return parse_obj_as(List[InheritanceSection], res.json()) diff --git a/octopoes/octopoes/core/service.py b/octopoes/octopoes/core/service.py index 323a835e8a0..7c85ddd55b5 100644 --- a/octopoes/octopoes/core/service.py +++ b/octopoes/octopoes/core/service.py @@ -25,9 +25,14 @@ ScanProfileType, ) from octopoes.models.exception import ObjectNotFoundException +from octopoes.models.explanation import InheritanceSection from octopoes.models.origin import Origin, OriginType, OriginParameter from octopoes.models.pagination import Paginated -from octopoes.models.path import get_max_scan_level_issuance, get_paths_to_neighours +from octopoes.models.path import ( + get_max_scan_level_issuance, + get_paths_to_neighours, + get_max_scan_level_inheritance, +) from octopoes.models.tree import ReferenceTree from octopoes.repositories.ooi_repository import OOIRepository from octopoes.repositories.origin_parameter_repository import OriginParameterRepository @@ -419,3 +424,56 @@ def list_random_ooi(self, amount: int, valid_time: datetime) -> List[OOI]: oois = self.ooi_repository.list_random(amount, valid_time) self._populate_scan_profiles(oois, valid_time) return oois + + def get_explanation( + self, reference: Reference, valid_time: datetime, inheritance_chain: List[InheritanceSection] + ) -> List[InheritanceSection]: + neighbour_cache = self.ooi_repository.get_neighbours(reference, valid_time) + + last_inheritance_level = inheritance_chain[-1].level + visited = {inheritance.reference for inheritance in inheritance_chain} + + # load scan profiles for all neighbours + neighbours_: List[OOI] = [ + neighbour + for neighbours in neighbour_cache.values() + for neighbour in neighbours + if neighbour.reference not in visited + ] + self._populate_scan_profiles(neighbours_, valid_time) + + # collect all inheritances + inheritances = [] + for path, neighbours in neighbour_cache.items(): + segment = path.segments[0] + for neighbour in neighbours: + if neighbour.reference not in visited and neighbour.scan_profile.level >= last_inheritance_level: + inherited_level = min(get_max_scan_level_inheritance(segment), neighbour.scan_profile.level) + inheritances.append( + InheritanceSection( + segment=str(segment), + reference=neighbour.reference, + level=inherited_level, + scan_profile_type=neighbour.scan_profile.scan_profile_type, + ) + ) + + # sort per ooi, per level, ascending + inheritances.sort(key=lambda x: x.level) + + # if any declared, return highest straight away + declared_inheritances = [ + inheritance for inheritance in inheritances if inheritance.scan_profile_type == ScanProfileType.DECLARED + ] + if declared_inheritances: + return inheritance_chain + [declared_inheritances[-1]] + + # group by ooi, as the list is already sorted, it will contain the highest inheritance + highest_inheritance_per_neighbour = {inheritance.reference: inheritance for inheritance in inheritances} + # traverse depth-first + for neighbour, inheritance in highest_inheritance_per_neighbour.items(): + expl = self.get_explanation(neighbour, valid_time, inheritance_chain + [inheritance]) + if expl[-1].scan_profile_type == ScanProfileType.DECLARED: + return expl + + return inheritance_chain diff --git a/octopoes/octopoes/models/explanation.py b/octopoes/octopoes/models/explanation.py new file mode 100644 index 00000000000..6beccb9021c --- /dev/null +++ b/octopoes/octopoes/models/explanation.py @@ -0,0 +1,15 @@ +from typing import Optional + +from pydantic import BaseModel + +from octopoes.models import Reference, ScanProfileType + + +class InheritanceSection(BaseModel): + reference: Reference + level: int + segment: Optional[str] + scan_profile_type: ScanProfileType + + class Config: + arbitrary_types_allowed = True diff --git a/octopoes/tests/robot/01_scan_profiles.robot b/octopoes/tests/robot/01_scan_profiles.robot index 2aee3c76ffe..6034bcfee39 100644 --- a/octopoes/tests/robot/01_scan_profiles.robot +++ b/octopoes/tests/robot/01_scan_profiles.robot @@ -59,7 +59,6 @@ Empty Scan Profiles Verify Scan Profile Mutation Queue ${REF_IPADDR} ${{[0]}} Verify Scan Profile Mutation Queue ${REF_RESOLVEDHOSTNAME} ${{[0]}} - *** Keywords *** Setup Test Start Monitoring ${QUEUE_URI} diff --git a/octopoes/tests/robot/06_explanation.robot b/octopoes/tests/robot/06_explanation.robot new file mode 100644 index 00000000000..6556e4bad78 --- /dev/null +++ b/octopoes/tests/robot/06_explanation.robot @@ -0,0 +1,31 @@ +*** Settings *** +Resource robot.resource + +Test Setup Setup Test +Test Teardown Teardown Test + + +*** Test Cases *** +Simple Explanation + Declare Scan Profile ${REF_HOSTNAME} ${4} + ${response_data} Get Explanation ${REF_IPADDR} + Length Should Be ${response_data} 3 + Should Be Equal As Strings ${response_data[1]["reference"]} ${REF_RESOLVEDHOSTNAME} + +*** Keywords *** +Setup Test + Start Monitoring ${QUEUE_URI} + Insert Normalizer Output + Await Sync + +Teardown Test + Cleanup + Await Sync + Stop Monitoring + +Get Explanation + [Arguments] ${reference} + ${response} Get ${OCTOPOES_URI}/explanations params=reference=${reference} + Should Be Equal As Integers ${response.status_code} 200 + ${response_data} Set Variable ${response.json()} + [Return] ${response_data} diff --git a/octopoes/tests/robot/robot.resource b/octopoes/tests/robot/robot.resource index f9584d2d27c..4404090c29d 100644 --- a/octopoes/tests/robot/robot.resource +++ b/octopoes/tests/robot/robot.resource @@ -63,14 +63,6 @@ Insert Regular Declarations Await Sync -Get Url - [Arguments] ${url} - ${response} Get ${url} - Should Be Equal As Integers ${response.status_code} 200 - Should Be Equal ${response.headers["content-type"]} application/json - ${response_data} Set Variable ${response.json()} - RETURN ${response_data} - Get Messages From Queue [Arguments] ${queue} ${ackmode} &{data} Create dictionary count=10000 ackmode=${ackmode} encoding=auto truncate=50000 @@ -116,7 +108,10 @@ Await Sync Wait For XTDB Synced Get Objects - ${response_data} Get Url ${OCTOPOES_URI}/objects + ${response} Get ${OCTOPOES_URI}/objects + Should Be Equal As Integers ${response.status_code} 200 + Should Be Equal ${response.headers["content-type"]} application/json + ${response_data} Set Variable ${response.json()} RETURN ${response_data} Get Valid Time Params From 53523bedc7ab2987328dfcd46e28bbc40b9b421f Mon Sep 17 00:00:00 2001 From: Jesse Lisser Date: Wed, 12 Apr 2023 11:25:52 +0200 Subject: [PATCH 2/8] fix(octopoes): explanations endpoint --- octopoes/octopoes/core/service.py | 36 ++++++++++++++-------- rocky/rocky/templates/oois/ooi_detail.html | 2 ++ rocky/rocky/views/ooi_detail.py | 2 ++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/octopoes/octopoes/core/service.py b/octopoes/octopoes/core/service.py index 7c85ddd55b5..abc785436fc 100644 --- a/octopoes/octopoes/core/service.py +++ b/octopoes/octopoes/core/service.py @@ -447,16 +447,23 @@ def get_explanation( for path, neighbours in neighbour_cache.items(): segment = path.segments[0] for neighbour in neighbours: - if neighbour.reference not in visited and neighbour.scan_profile.level >= last_inheritance_level: - inherited_level = min(get_max_scan_level_inheritance(segment), neighbour.scan_profile.level) - inheritances.append( - InheritanceSection( - segment=str(segment), - reference=neighbour.reference, - level=inherited_level, - scan_profile_type=neighbour.scan_profile.scan_profile_type, - ) + segment_inheritance = get_max_scan_level_inheritance(segment) + if ( + segment_inheritance is None + or neighbour.reference in visited + or neighbour.scan_profile.level < last_inheritance_level + ): + continue + + inherited_level = min(get_max_scan_level_inheritance(segment), neighbour.scan_profile.level) + inheritances.append( + InheritanceSection( + segment=str(segment), + reference=neighbour.reference, + level=inherited_level, + scan_profile_type=neighbour.scan_profile.scan_profile_type, ) + ) # sort per ooi, per level, ascending inheritances.sort(key=lambda x: x.level) @@ -469,10 +476,13 @@ def get_explanation( return inheritance_chain + [declared_inheritances[-1]] # group by ooi, as the list is already sorted, it will contain the highest inheritance - highest_inheritance_per_neighbour = {inheritance.reference: inheritance for inheritance in inheritances} - # traverse depth-first - for neighbour, inheritance in highest_inheritance_per_neighbour.items(): - expl = self.get_explanation(neighbour, valid_time, inheritance_chain + [inheritance]) + highest_inheritance_per_neighbour = { + inheritance.reference: inheritance for inheritance in reversed(inheritances) + } + + # traverse depth-first, highest levels first + for inheritance in sorted(highest_inheritance_per_neighbour.values(), key=lambda x: x.level, reverse=True): + expl = self.get_explanation(inheritance.reference, valid_time, inheritance_chain + [inheritance]) if expl[-1].scan_profile_type == ScanProfileType.DECLARED: return expl diff --git a/rocky/rocky/templates/oois/ooi_detail.html b/rocky/rocky/templates/oois/ooi_detail.html index 9f9b90adca1..6b10fd5304f 100644 --- a/rocky/rocky/templates/oois/ooi_detail.html +++ b/rocky/rocky/templates/oois/ooi_detail.html @@ -10,6 +10,8 @@
+ {{ explanation }} + {% if ooi_past_due %} {% include "oois/ooi_past_due_warning.html" with ooi_current=ooi_current %} diff --git a/rocky/rocky/views/ooi_detail.py b/rocky/rocky/views/ooi_detail.py index c5315eaedd9..039308ebc00 100644 --- a/rocky/rocky/views/ooi_detail.py +++ b/rocky/rocky/views/ooi_detail.py @@ -181,4 +181,6 @@ def get_context_data(self, **kwargs): "scan_history_page", ] + context["explanation"] = self.octopoes_api_connector.get_explanation(self.ooi.reference) + return context From e1de9dd96547fe4328cc1043f837dcb6b7fe2688 Mon Sep 17 00:00:00 2001 From: Jesse Lisser Date: Wed, 12 Apr 2023 12:14:33 +0200 Subject: [PATCH 3/8] revert rocky rendering of explanation --- rocky/rocky/templates/oois/ooi_detail.html | 2 -- rocky/rocky/views/ooi_detail.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/rocky/rocky/templates/oois/ooi_detail.html b/rocky/rocky/templates/oois/ooi_detail.html index 6b10fd5304f..9f9b90adca1 100644 --- a/rocky/rocky/templates/oois/ooi_detail.html +++ b/rocky/rocky/templates/oois/ooi_detail.html @@ -10,8 +10,6 @@
- {{ explanation }} - {% if ooi_past_due %} {% include "oois/ooi_past_due_warning.html" with ooi_current=ooi_current %} diff --git a/rocky/rocky/views/ooi_detail.py b/rocky/rocky/views/ooi_detail.py index 039308ebc00..c5315eaedd9 100644 --- a/rocky/rocky/views/ooi_detail.py +++ b/rocky/rocky/views/ooi_detail.py @@ -181,6 +181,4 @@ def get_context_data(self, **kwargs): "scan_history_page", ] - context["explanation"] = self.octopoes_api_connector.get_explanation(self.ooi.reference) - return context From 47cf1c1a59b969e901a6388b0bab7cca4fa824fd Mon Sep 17 00:00:00 2001 From: Noam Blitz Date: Wed, 12 Apr 2023 20:57:26 +0200 Subject: [PATCH 4/8] initial commit of add explanations to rocky --- rocky/rocky/templates/oois/ooi_detail.html | 3 ++ .../templates/partials/explanations.html | 38 +++++++++++++++++++ rocky/rocky/views/mixins.py | 6 +++ rocky/rocky/views/ooi_detail.py | 12 +++++- 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 rocky/rocky/templates/partials/explanations.html diff --git a/rocky/rocky/templates/oois/ooi_detail.html b/rocky/rocky/templates/oois/ooi_detail.html index 9f9b90adca1..e23e8ba2ea9 100644 --- a/rocky/rocky/templates/oois/ooi_detail.html +++ b/rocky/rocky/templates/oois/ooi_detail.html @@ -97,6 +97,9 @@

{% translate "Scan" %} {{ ooi.human_readable }} {% translate "using boefjes" {% endif %}

{% endif %} + + {% include "partials/explanations.html" with explanations=explanations %} +
diff --git a/rocky/rocky/templates/partials/explanations.html b/rocky/rocky/templates/partials/explanations.html new file mode 100644 index 00000000000..a102c0f328b --- /dev/null +++ b/rocky/rocky/templates/partials/explanations.html @@ -0,0 +1,38 @@ +{% load i18n %} +{% load ooi_extra %} + +{% spaceless %} +
+

{% translate "Explanation" %}

+ {% if explanations %} + + + + + + + + + {% for explanation in explanations %} + + {% if forloop.first %} + + {% elif forloop.last %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + +
{% translate "Type" %}{% translate "URL" %}
{% translate "OOI" %}{% translate "Origin" %}Object + {{ explanation.reference }} +
+ {% else %} + {% include "partials/single_action_form.html" with btn_text="Show explanation of existence" action="get_explanation" %} + {% endif %} +
+{% endspaceless %} diff --git a/rocky/rocky/views/mixins.py b/rocky/rocky/views/mixins.py index a8b7e5d1b5e..11d4b286778 100644 --- a/rocky/rocky/views/mixins.py +++ b/rocky/rocky/views/mixins.py @@ -118,6 +118,12 @@ def get_depth(self, default_depth=DEPTH_DEFAULT) -> int: except ValueError: return default_depth + def get_explanation(self, ooi: OOI) -> List[Dict[str, str]]: + if ooi.scan_profile == 0 or ooi.scan_profile.scan_profile_type != ScanProfileType.INHERITED.value: + return [] + explanations = self.octopoes_api_connector.get_explanation(ooi.reference) + return explanations + class OOIList: HARD_LIMIT = 99_999_999 diff --git a/rocky/rocky/views/ooi_detail.py b/rocky/rocky/views/ooi_detail.py index c5315eaedd9..d97a5ee74c9 100644 --- a/rocky/rocky/views/ooi_detail.py +++ b/rocky/rocky/views/ooi_detail.py @@ -12,6 +12,7 @@ from katalogus.utils import get_enabled_boefjes_for_ooi_class from katalogus.views.mixins import BoefjeMixin from octopoes.models import OOI + from rocky import scheduler from rocky.views.mixins import OOIBreadcrumbsMixin from rocky.views.ooi_detail_related_object import OOIRelatedObjectAddView @@ -24,6 +25,7 @@ class PageActions(Enum): START_SCAN = "start_scan" + GET_EXPLANATION = "get_explanation" class OOIDetailView( @@ -48,7 +50,12 @@ def post(self, request, *args, **kwargs): self.ooi = self.get_ooi() - if not self.handle_page_action(request.POST.get("action")): + action = self.request.POST.get("action") + + if action == PageActions.GET_EXPLANATION.value: + return super().get(request, get_explanations=True, *args, **kwargs) + + if not self.handle_page_action(action): return self.get(request, status_code=500, *args, **kwargs) success_message = ( @@ -180,5 +187,6 @@ def get_context_data(self, **kwargs): "scan_history_search", "scan_history_page", ] - + if kwargs.get("get_explanations"): + context["explanations"] = [explanation for explanation in self.get_explanation(self.ooi)] return context From d863547636a5d2e40ac852618b3d19907623cace Mon Sep 17 00:00:00 2001 From: Noam Blitz Date: Wed, 12 Apr 2023 21:34:17 +0200 Subject: [PATCH 5/8] refactor --- rocky/rocky/templates/partials/explanations.html | 2 +- rocky/rocky/views/ooi_detail.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/rocky/rocky/templates/partials/explanations.html b/rocky/rocky/templates/partials/explanations.html index a102c0f328b..44912c454d7 100644 --- a/rocky/rocky/templates/partials/explanations.html +++ b/rocky/rocky/templates/partials/explanations.html @@ -32,7 +32,7 @@

{% translate "Explanation" %}

{% else %} - {% include "partials/single_action_form.html" with btn_text="Show explanation of existence" action="get_explanation" %} + {% translate "Show explanation of existence" %} {% endif %} {% endspaceless %} diff --git a/rocky/rocky/views/ooi_detail.py b/rocky/rocky/views/ooi_detail.py index d97a5ee74c9..751f2eecc1f 100644 --- a/rocky/rocky/views/ooi_detail.py +++ b/rocky/rocky/views/ooi_detail.py @@ -25,7 +25,6 @@ class PageActions(Enum): START_SCAN = "start_scan" - GET_EXPLANATION = "get_explanation" class OOIDetailView( @@ -51,10 +50,6 @@ def post(self, request, *args, **kwargs): self.ooi = self.get_ooi() action = self.request.POST.get("action") - - if action == PageActions.GET_EXPLANATION.value: - return super().get(request, get_explanations=True, *args, **kwargs) - if not self.handle_page_action(action): return self.get(request, status_code=500, *args, **kwargs) @@ -187,6 +182,6 @@ def get_context_data(self, **kwargs): "scan_history_search", "scan_history_page", ] - if kwargs.get("get_explanations"): + if self.request.GET.get("get_explanations"): context["explanations"] = [explanation for explanation in self.get_explanation(self.ooi)] return context From 23319f0cdc2de2f7fe46f8a0ce2429bd4498813f Mon Sep 17 00:00:00 2001 From: Jesse Lisser Date: Wed, 12 Apr 2023 23:11:23 +0200 Subject: [PATCH 6/8] cleanup scan-level-inheritance --- octopoes/octopoes/api/router.py | 10 +++---- octopoes/octopoes/connector/octopoes.py | 6 ++-- octopoes/octopoes/core/service.py | 6 ++-- rocky/rocky/templates/oois/ooi_detail.html | 2 +- .../templates/partials/explanations.html | 30 +++++++++++-------- rocky/rocky/views/mixins.py | 7 +++-- rocky/rocky/views/ooi_detail.py | 16 ++++++++-- 7 files changed, 48 insertions(+), 29 deletions(-) diff --git a/octopoes/octopoes/api/router.py b/octopoes/octopoes/api/router.py index 8df0444f6c0..bff9e4c2d2e 100644 --- a/octopoes/octopoes/api/router.py +++ b/octopoes/octopoes/api/router.py @@ -250,19 +250,19 @@ def recalculate_scan_profiles( xtdb_session_.commit() -@router.get("/explanations") -def get_explanations( +@router.get("/scan_profiles/inheritance") +def get_scan_profile_inheritance( octopoes: OctopoesService = Depends(octopoes_service), valid_time: datetime = Depends(extract_valid_time), reference: Reference = Depends(extract_reference), ) -> List[InheritanceSection]: ooi = octopoes.get_ooi(reference, valid_time) - root = InheritanceSection( + start = InheritanceSection( reference=ooi.reference, level=ooi.scan_profile.level, scan_profile_type=ooi.scan_profile.scan_profile_type ) if ooi.scan_profile.scan_profile_type == ScanProfileType.DECLARED: - return [root] - return octopoes.get_explanation(reference, valid_time, [root]) + return [start] + return octopoes.get_scan_profile_inheritance(reference, valid_time, [start]) @router.post("/node") diff --git a/octopoes/octopoes/connector/octopoes.py b/octopoes/octopoes/connector/octopoes.py index 4be17fb4ae0..5756af2cdd3 100644 --- a/octopoes/octopoes/connector/octopoes.py +++ b/octopoes/octopoes/connector/octopoes.py @@ -151,7 +151,9 @@ def create_node(self): def delete_node(self): self.session.delete(f"/{self.client}/node") - def get_explanation(self, reference: Reference, valid_time: Optional[datetime] = None) -> List[InheritanceSection]: + def get_scan_profile_inheritance( + self, reference: Reference, valid_time: Optional[datetime] = None + ) -> List[InheritanceSection]: params = {"reference": str(reference), "valid_time": valid_time} - res = self.session.get(f"/{self.client}/explanations", params=params) + res = self.session.get(f"/{self.client}/scan_profiles/inheritance", params=params) return parse_obj_as(List[InheritanceSection], res.json()) diff --git a/octopoes/octopoes/core/service.py b/octopoes/octopoes/core/service.py index abc785436fc..dd1fde108b6 100644 --- a/octopoes/octopoes/core/service.py +++ b/octopoes/octopoes/core/service.py @@ -425,7 +425,7 @@ def list_random_ooi(self, amount: int, valid_time: datetime) -> List[OOI]: self._populate_scan_profiles(oois, valid_time) return oois - def get_explanation( + def get_scan_profile_inheritance( self, reference: Reference, valid_time: datetime, inheritance_chain: List[InheritanceSection] ) -> List[InheritanceSection]: neighbour_cache = self.ooi_repository.get_neighbours(reference, valid_time) @@ -482,7 +482,9 @@ def get_explanation( # traverse depth-first, highest levels first for inheritance in sorted(highest_inheritance_per_neighbour.values(), key=lambda x: x.level, reverse=True): - expl = self.get_explanation(inheritance.reference, valid_time, inheritance_chain + [inheritance]) + expl = self.get_scan_profile_inheritance( + inheritance.reference, valid_time, inheritance_chain + [inheritance] + ) if expl[-1].scan_profile_type == ScanProfileType.DECLARED: return expl diff --git a/rocky/rocky/templates/oois/ooi_detail.html b/rocky/rocky/templates/oois/ooi_detail.html index e23e8ba2ea9..59c59097114 100644 --- a/rocky/rocky/templates/oois/ooi_detail.html +++ b/rocky/rocky/templates/oois/ooi_detail.html @@ -98,7 +98,7 @@

{% translate "Scan" %} {{ ooi.human_readable }} {% translate "using boefjes" {% endif %} - {% include "partials/explanations.html" with explanations=explanations %} + {% include "partials/explanations.html" %} diff --git a/rocky/rocky/templates/partials/explanations.html b/rocky/rocky/templates/partials/explanations.html index 44912c454d7..528a74cc70f 100644 --- a/rocky/rocky/templates/partials/explanations.html +++ b/rocky/rocky/templates/partials/explanations.html @@ -3,36 +3,40 @@ {% spaceless %}
-

{% translate "Explanation" %}

- {% if explanations %} +

{% translate "Clearance level inheritance" %}

+ {% if clearance_level_inheritance %} - + + + - {% for explanation in explanations %} + {% for section in clearance_level_inheritance %} - {% if forloop.first %} - - {% elif forloop.last %} - - {% else %} - - {% endif %} + {% if forloop.first %} + + {% elif forloop.last %} + + {% else %} + + {% endif %} + + {% endfor %}
{% translate "Type" %}{% translate "URL" %}{% translate "Object Type" %}{% translate "OOI" %}{% translate "Clearance level" %}
{% translate "OOI" %}{% translate "Origin" %}Object{% translate "OOI" %}{% translate "Origin" %}Object - {{ explanation.reference }} + {{ section.human_readable }} {{ section.object_type }}L{{ section.level }}
{% else %} - {% translate "Show explanation of existence" %} + {% translate "Show clearance level inheritance" %} {% endif %}
{% endspaceless %} diff --git a/rocky/rocky/views/mixins.py b/rocky/rocky/views/mixins.py index 11d4b286778..916482cfa20 100644 --- a/rocky/rocky/views/mixins.py +++ b/rocky/rocky/views/mixins.py @@ -14,6 +14,7 @@ from octopoes.connector import ObjectNotFoundException from octopoes.connector.octopoes import OctopoesAPIConnector from octopoes.models import OOI, Reference, ScanLevel, ScanProfileType +from octopoes.models.explanation import InheritanceSection from octopoes.models.ooi.findings import Finding from octopoes.models.origin import Origin, OriginType from octopoes.models.tree import ReferenceTree @@ -118,10 +119,10 @@ def get_depth(self, default_depth=DEPTH_DEFAULT) -> int: except ValueError: return default_depth - def get_explanation(self, ooi: OOI) -> List[Dict[str, str]]: - if ooi.scan_profile == 0 or ooi.scan_profile.scan_profile_type != ScanProfileType.INHERITED.value: + def get_scan_profile_inheritance(self, ooi: OOI) -> List[InheritanceSection]: + explanations = self.octopoes_api_connector.get_scan_profile_inheritance(ooi.reference) + if len(explanations) == 1: return [] - explanations = self.octopoes_api_connector.get_explanation(ooi.reference) return explanations diff --git a/rocky/rocky/views/ooi_detail.py b/rocky/rocky/views/ooi_detail.py index 751f2eecc1f..2d500722c5e 100644 --- a/rocky/rocky/views/ooi_detail.py +++ b/rocky/rocky/views/ooi_detail.py @@ -11,7 +11,7 @@ from katalogus.client import get_katalogus from katalogus.utils import get_enabled_boefjes_for_ooi_class from katalogus.views.mixins import BoefjeMixin -from octopoes.models import OOI +from octopoes.models import OOI, Reference from rocky import scheduler from rocky.views.mixins import OOIBreadcrumbsMixin @@ -182,6 +182,16 @@ def get_context_data(self, **kwargs): "scan_history_search", "scan_history_page", ] - if self.request.GET.get("get_explanations"): - context["explanations"] = [explanation for explanation in self.get_explanation(self.ooi)] + if self.request.GET.get("show_clearance_level_inheritance"): + clearance_level_inheritance = self.get_scan_profile_inheritance(self.ooi) + formatted_inheritance = [ + { + "object_type": Reference.from_str(section.reference).class_, + "primary_key": section.reference, + "human_readable": Reference.from_str(section.reference).human_readable, + "level": section.level, + } + for section in clearance_level_inheritance + ] + context["clearance_level_inheritance"] = formatted_inheritance return context From 0dbe0e51aab2a3458f5ed56613e8a29656f2d37d Mon Sep 17 00:00:00 2001 From: Jesse Lisser Date: Wed, 12 Apr 2023 23:13:43 +0200 Subject: [PATCH 7/8] test(octopoes): rename robot test --- ...xplanation.robot => 06_scan_profile_inheritance.robot} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename octopoes/tests/robot/{06_explanation.robot => 06_scan_profile_inheritance.robot} (75%) diff --git a/octopoes/tests/robot/06_explanation.robot b/octopoes/tests/robot/06_scan_profile_inheritance.robot similarity index 75% rename from octopoes/tests/robot/06_explanation.robot rename to octopoes/tests/robot/06_scan_profile_inheritance.robot index 6556e4bad78..e124531d6df 100644 --- a/octopoes/tests/robot/06_explanation.robot +++ b/octopoes/tests/robot/06_scan_profile_inheritance.robot @@ -6,9 +6,9 @@ Test Teardown Teardown Test *** Test Cases *** -Simple Explanation +Simple Scan Profile Inheritance Declare Scan Profile ${REF_HOSTNAME} ${4} - ${response_data} Get Explanation ${REF_IPADDR} + ${response_data} Get Scan Profile Inheritance ${REF_IPADDR} Length Should Be ${response_data} 3 Should Be Equal As Strings ${response_data[1]["reference"]} ${REF_RESOLVEDHOSTNAME} @@ -23,9 +23,9 @@ Teardown Test Await Sync Stop Monitoring -Get Explanation +Get Scan Profile Inheritance [Arguments] ${reference} - ${response} Get ${OCTOPOES_URI}/explanations params=reference=${reference} + ${response} Get ${OCTOPOES_URI}/scan_profiles/inheritance params=reference=${reference} Should Be Equal As Integers ${response.status_code} 200 ${response_data} Set Variable ${response.json()} [Return] ${response_data} From 1d0959234ecf5f3d121a3a006faaaa5fd5ead062 Mon Sep 17 00:00:00 2001 From: Jesse Lisser Date: Fri, 14 Apr 2023 17:07:45 +0200 Subject: [PATCH 8/8] refactor(explanations): only show explanations block for ooi with inherited scan profiles --- rocky/rocky/templates/oois/ooi_detail.html | 4 +++- rocky/rocky/views/mixins.py | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rocky/rocky/templates/oois/ooi_detail.html b/rocky/rocky/templates/oois/ooi_detail.html index 59c59097114..a6e5f7c4637 100644 --- a/rocky/rocky/templates/oois/ooi_detail.html +++ b/rocky/rocky/templates/oois/ooi_detail.html @@ -98,7 +98,9 @@

{% translate "Scan" %} {{ ooi.human_readable }} {% translate "using boefjes" {% endif %} - {% include "partials/explanations.html" %} + {% if ooi.scan_profile.scan_profile_type == 'inherited' %} + {% include "partials/explanations.html" %} + {% endif %} diff --git a/rocky/rocky/views/mixins.py b/rocky/rocky/views/mixins.py index e2a3e62e825..e2c25e1dd2d 100644 --- a/rocky/rocky/views/mixins.py +++ b/rocky/rocky/views/mixins.py @@ -120,10 +120,7 @@ def get_depth(self, default_depth=DEPTH_DEFAULT) -> int: return default_depth def get_scan_profile_inheritance(self, ooi: OOI) -> List[InheritanceSection]: - explanations = self.octopoes_api_connector.get_scan_profile_inheritance(ooi.reference) - if len(explanations) == 1: - return [] - return explanations + return self.octopoes_api_connector.get_scan_profile_inheritance(ooi.reference) class OOIList: