Skip to content

Commit

Permalink
⚗️ Initial prototyping of config checks
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-maertens committed Jun 18, 2024
1 parent 76d5fd9 commit 0b72d29
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 7 deletions.
177 changes: 170 additions & 7 deletions src/openforms/emails/digest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import uuid
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import datetime, timedelta
from itertools import groupby
Expand All @@ -18,15 +19,13 @@

from openforms.contrib.brk.service import check_brk_config_for_addressNL
from openforms.contrib.kadaster.service import check_bag_config_for_address_fields
from openforms.dmn.registry import register as dmn_register
from openforms.dmn.registry import BasePlugin as DMNPlugin, register as dmn_register
from openforms.forms.constants import LogicActionTypes
from openforms.forms.models import Form
from openforms.forms.models.form import FormQuerySet
from openforms.forms.models.form_registration_backend import FormRegistrationBackend
from openforms.forms.models.logic import FormLogic
from openforms.forms.models import Form, FormLogic, FormRegistrationBackend
from openforms.logging.models import TimelineLogProxy
from openforms.plugins.exceptions import InvalidPluginConfiguration
from openforms.registrations.registry import register
from openforms.submissions.logic.actions import ActionDict, EvaluateDMNAction
from openforms.submissions.models.submission import Submission
from openforms.submissions.utils import get_filtered_submission_admin_url
from openforms.typing import StrOrPromise
Expand Down Expand Up @@ -238,6 +237,7 @@ def collect_failed_prefill_plugins(since: datetime) -> list[FailedPrefill]:
return failed_prefill_plugins


# TODO: check DMN config
def collect_broken_configurations() -> list[BrokenConfiguration]:
check_brk_configuration = check_brk_config_for_addressNL()
check_bag_configuration = check_bag_config_for_address_fields()
Expand Down Expand Up @@ -476,7 +476,7 @@ def _report():
return invalid_logic_rules


def _get_forms_with_dmn_action(plugin_id: str) -> FormQuerySet:
def _get_forms_with_dmn_action(plugin_id: str):
# actions is a JSONField - the top-level is an array of actions. Each action is
# an object with the shape openforms.submissions.logic.actions.ActionDict. The
# JSONField query does not fully specify all properties because it performs
Expand All @@ -486,9 +486,172 @@ def _get_forms_with_dmn_action(plugin_id: str) -> FormQuerySet:
{
"action": {
"type": LogicActionTypes.evaluate_dmn,
"config": {"plugin_id": plugin_id},
# if a plugin ID is provided, filter actions for that plugin, otherwise
# return any DMN evaluation action plugin
"config": {"plugin_id": plugin_id} if plugin_id else {},
}
}
]
base_qs = Form.objects.live().distinct()
return base_qs.filter(formlogic__actions__contains=lookup_value)


@dataclass
class InvalidDMNAction:
form_id: int
form_name: str
# definition: DecisionDefinition
message: StrOrPromise

@property
def admin_link(self) -> str:
form_relative_admin_url = reverse(

Check warning on line 508 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L508

Added line #L508 was not covered by tests
"admin:forms_form_change", kwargs={"object_id": self.form_id}
)

return build_absolute_uri(form_relative_admin_url)

Check warning on line 512 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L512

Added line #L512 was not covered by tests


def _iter_dmn_actions(form_qs) -> Iterator[tuple[Form, ActionDict]]:
for form in form_qs.iterator():
logic_rules = form.formlogic_set.filter(

Check warning on line 517 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L517

Added line #L517 was not covered by tests
actions__contains=[{"action": {"type": LogicActionTypes.evaluate_dmn}}]
)
for rule in logic_rules.iterator():
for action in rule.actions:
match action:

Check warning on line 522 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L522

Added line #L522 was not covered by tests
case {"action": {"type": LogicActionTypes.evaluate_dmn}}:
yield (form, action)

Check warning on line 524 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L524

Added line #L524 was not covered by tests


def collect_invalid_dmn_actions() -> list[InvalidDMNAction]:
"""
Introspect forms using the DMN action in their logic rules.
Checks whether:
1. The decision definition can be introspected
2. The input/output variables exist
"""
invalid_actions = []
# track available definitions to present human readable names
available_definitions: dict[str, dict[str, str]] = {}

static_variables = [var.key for var in get_static_variables()]

def _populate_definitions(plugin: DMNPlugin) -> None:
if (key := plugin.identifier) in available_definitions:
return
try:
definitions = plugin.get_available_decision_definitions()
except Exception:
definitions = []

Check warning on line 548 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L544-L548

Added lines #L544 - L548 were not covered by tests
available_definitions[key] = {
definition.identifier: definition.label for definition in definitions
}

forms = _get_forms_with_dmn_action(plugin_id="")
for form, _action in _iter_dmn_actions(forms):

def _report(msg: StrOrPromise):
invalid_actions.append(

Check warning on line 557 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L556-L557

Added lines #L556 - L557 were not covered by tests
InvalidDMNAction(
form_id=form.id, form_name=form.admin_name, message=msg
)
)

try:
action = EvaluateDMNAction.from_action(_action)
except Exception:
_report(_("Could not parse configuration"))
continue

Check warning on line 567 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L563-L567

Added lines #L563 - L567 were not covered by tests

try:
plugin = dmn_register[action.plugin_id]
except KeyError:
_report(

Check warning on line 572 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L569-L572

Added lines #L569 - L572 were not covered by tests
_("Unknown DMN plugin: {plugin_id}").format(plugin_id=action.plugin_id)
)
continue

Check warning on line 575 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L575

Added line #L575 was not covered by tests

definition_id = action.decision_definition_id
definition_version = action.decision_definition_version

Check warning on line 578 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L577-L578

Added lines #L577 - L578 were not covered by tests

# introspect the definition - if this fails, there's likely some permission issue
# or the definition was removed
try:
parameters = plugin.get_decision_definition_parameters(

Check warning on line 583 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L582-L583

Added lines #L582 - L583 were not covered by tests
definition_id=definition_id,
version=definition_version,
)
except Exception:
_populate_definitions(plugin)
name = available_definitions[action.plugin_id].get(

Check warning on line 589 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L587-L589

Added lines #L587 - L589 were not covered by tests
definition_id, definition_id
)
_report(

Check warning on line 592 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L592

Added line #L592 was not covered by tests
_(
"Definition {name} does not exist or Open Forms has no access"
).format(name=name)
)
continue

Check warning on line 597 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L597

Added line #L597 was not covered by tests

form_variable_keys = list(form.formvariable_set.values_list("key", flat=True))
all_keys = static_variables + form_variable_keys

Check warning on line 600 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L599-L600

Added lines #L599 - L600 were not covered by tests
output_names = [param.name for param in parameters.outputs]

invalid_form_vars = []
invalid_input_vars = []
invalid_output_vars = []

Check warning on line 605 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L603-L605

Added lines #L603 - L605 were not covered by tests

for input_mapping in action.input_mapping:
if (form_var := input_mapping["form_variable"]) not in all_keys:
invalid_form_vars.append(form_var)

Check warning on line 609 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L609

Added line #L609 was not covered by tests

dmn_var = input_mapping["dmn_variable"]

Check warning on line 611 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L611

Added line #L611 was not covered by tests
# there could be false negatives here, since we don't parse the FEEL
# expressions and just do string containment checks.
if not any(dmn_var in param.expression for param in parameters.inputs):
invalid_input_vars.append(dmn_var)

Check warning on line 615 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L615

Added line #L615 was not covered by tests

for output_mapping in action.output_mapping:
# exclude static vars, since you cannot assign to them
if (form_var := output_mapping["form_variable"]) not in form_variable_keys:
invalid_form_vars.append(form_var)

Check warning on line 620 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L620

Added line #L620 was not covered by tests
if (dmn_var := output_mapping["dmn_variable"]) not in output_names:
invalid_output_vars.append(dmn_var)

Check warning on line 622 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L622

Added line #L622 was not covered by tests

if any([invalid_form_vars, invalid_input_vars, invalid_output_vars]):
_populate_definitions(plugin)
name = available_definitions[action.plugin_id].get(

Check warning on line 626 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L625-L626

Added lines #L625 - L626 were not covered by tests
definition_id, definition_id
)

if invalid_form_vars:
msg = _(

Check warning on line 631 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L631

Added line #L631 was not covered by tests
"The '{name}' definition configuration points to invalid form variables: {form_vars}"
).format(
name=name,
form_vars=", ".join(invalid_form_vars),
)
_report(msg)

Check warning on line 637 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L637

Added line #L637 was not covered by tests

if invalid_input_vars:
msg = _(

Check warning on line 640 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L640

Added line #L640 was not covered by tests
"Definition '{name}' does not appear to have the input variable(s): {input_vars}"
).format(
name=name,
input_vars=", ".join(invalid_input_vars),
)
_report(msg)

Check warning on line 646 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L646

Added line #L646 was not covered by tests

if invalid_output_vars:
msg = _(

Check warning on line 649 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L649

Added line #L649 was not covered by tests
"Definition '{name}' does not appear to have the output variable(s): {output_vars}"
).format(
name=name,
output_vars=", ".join(invalid_output_vars),
)
_report(msg)

Check warning on line 655 in src/openforms/emails/digest.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/emails/digest.py#L655

Added line #L655 was not covered by tests

return invalid_actions
3 changes: 3 additions & 0 deletions src/openforms/emails/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
collect_failed_prefill_plugins,
collect_failed_registrations,
collect_invalid_certificates,
collect_invalid_dmn_actions,
collect_invalid_logic_variables,
collect_invalid_registration_backends,
)
Expand All @@ -33,6 +34,7 @@ def get_context_data(self) -> dict[str, Any]:
invalid_certificates = collect_invalid_certificates()
invalid_registration_backends = collect_invalid_registration_backends()
invalid_logic_variables = collect_invalid_logic_variables()
invalid_dmn_actions = collect_invalid_dmn_actions()

return {
"failed_emails": failed_emails,
Expand All @@ -42,6 +44,7 @@ def get_context_data(self) -> dict[str, Any]:
"invalid_certificates": invalid_certificates,
"invalid_registration_backends": invalid_registration_backends,
"invalid_logic_variables": invalid_logic_variables,
"invalid_dmn_actions": invalid_dmn_actions,
}

def render(self) -> str:
Expand Down
16 changes: 16 additions & 0 deletions src/openforms/emails/templates/emails/admin_digest.html
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,19 @@ <h5>{% trans "Logic rules problems" %}</h5>
{% endfor %}
</ul>
{% endif %}

{% if invalid_dmn_actions %}
<h5>{% trans "Invalid DMN evaluation actions" %}</h5>
<ul>
{% for problem in invalid_dmn_actions %}
<li>
<p>
{% blocktranslate with form_name=problem.form_name message=problem.message trimmed %}
Invalid DMN evaluation action in form {{ form_name }} (<em>{{ message }}</em>).
{% endblocktranslate %}
</p>
<p><a href="{{ problem.admin_link }}">{% trans "View form" %}</a></p>
</li>
{% endfor %}
</ul>
{% endif %}

0 comments on commit 0b72d29

Please sign in to comment.