diff --git a/requirements.txt b/requirements.txt index 1deb8ad..b432f81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,6 +48,8 @@ zope.event==5.0 zope.interface==6.1 zope.schema==7.0.1 zope.sqlalchemy==3.1 +numpy==1.26.3 +plotly==5.18.0 psycopg2-binary==2.9.9 redis[hiredis]==5.0.3 diff --git a/src/riskmatrix/static/__init__.py b/src/riskmatrix/static/__init__.py index 4f62def..493ab4b 100644 --- a/src/riskmatrix/static/__init__.py +++ b/src/riskmatrix/static/__init__.py @@ -58,3 +58,4 @@ def css( ) datatable_js = js('datatables_custom.js', depends=[datatable_bootstrap]) xhr_edit_js = js('xhr_edit.js', depends=[datatable_js]) +plotly_js = js('plotly.min.js', depends=[datatable_js]) diff --git a/src/riskmatrix/subscribers.py b/src/riskmatrix/subscribers.py index 00c8bb1..2a2742f 100644 --- a/src/riskmatrix/subscribers.py +++ b/src/riskmatrix/subscribers.py @@ -55,6 +55,11 @@ def request_none_generator(event: 'NewRequest') -> None: request.set_property(lambda r: secrets.token_urlsafe(), 'csp_nonce', reify=True) +def request_none_generator(event: 'NewRequest') -> None: + request = event.request + request.set_property(lambda r: secrets.token_urlsafe(), 'csp_nonce', reify=True) + + def includeme(config: 'Configurator') -> None: config.add_subscriber(csp_header, NewResponse) config.add_subscriber(request_none_generator, NewRequest) diff --git a/src/riskmatrix/views/risk_assessment.py b/src/riskmatrix/views/risk_assessment.py index 2bdec82..8f66c77 100644 --- a/src/riskmatrix/views/risk_assessment.py +++ b/src/riskmatrix/views/risk_assessment.py @@ -25,7 +25,7 @@ if TYPE_CHECKING: from pyramid.interfaces import IRequest from sqlalchemy.orm.query import Query - from typing import TypeVar + from typing import TypeVar, Iterator from riskmatrix.models import Organization from riskmatrix.types import MixedDataOrRedirect @@ -130,25 +130,24 @@ def buttons(self, assessment: RiskAssessment | None = None) -> list[Button]: class AssessmentOverviewTable(AssessmentBaseTable): - nr = DataColumn(_("Nr.")) - name = DataColumn(_("Name")) - description = DataColumn(_("Description"), class_name="visually-hidden") - category = DataColumn(_("Category")) - asset_name = DataColumn(_("Asset")) - likelihood = DataColumn(_("Likelihood")) - impact = DataColumn(_("Impact")) - - def __init__(self, org: "Organization", request: "IRequest") -> None: - super().__init__(org, request, id="risks-table") + nr = DataColumn(_('Nr.')) + name = DataColumn(_('Name')) + description = DataColumn(_('Description'), class_name='visually-hidden') + category = DataColumn(_('Category')) + asset_name = DataColumn(_('Asset')) + likelihood = DataColumn(_('Likelihood')) + impact = DataColumn(_('Impact')) + + def __init__(self, org: 'Organization', request: 'IRequest') -> None: + super().__init__(org, request, id='risks-table') xhr_edit_js.need() - def query(self) -> "Query[RiskMatrixAssessment]": + def query(self) -> 'Iterator[RiskMatrixAssessment]': query = super().query() - return map( - lambda entry: setattr(entry[1], "nr", entry[0] + 1) or entry[1], - enumerate(query), - ) + for idx, item in enumerate(query, start=1): + item.nr = idx + yield item _RADIO_TEMPLATE = Markup( @@ -287,7 +286,6 @@ def __html__(self) -> str: params=params, ) - def generate_risk_matrix_view( context: "Organization", request: "IRequest" ) -> "RenderData": @@ -382,11 +380,13 @@ def plot_risk_matrix(risks: 'Query[RiskMatrixAssessment]') -> str: font=dict(size=20), ) - return fig.to_html( + return Markup(fig.to_html( full_html=False, - include_plotlyjs=False, - config={"modeBarButtonsToRemove": ["zoom", "pan", "select", "lasso2d"]}, - ) + include_plotlyjs=True, + config={ + 'modeBarButtonsToRemove': ['zoom', 'pan', 'select', 'lasso2d'] + }, + )) def edit_assessment_view(