Skip to content

Commit

Permalink
Merge pull request #3916 from open-formulieren/feature/3855-cache-dmn…
Browse files Browse the repository at this point in the history
…-requests

[#3855] Cache DMN requests results
  • Loading branch information
sergei-maertens authored Feb 22, 2024
2 parents b50c5ca + 2b2479c commit b759366
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 6 deletions.
27 changes: 21 additions & 6 deletions src/openforms/submissions/logic/actions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any, Mapping, TypedDict

from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder

from glom import assign
from json_logic import jsonLogic
from typing_extensions import Self
Expand Down Expand Up @@ -245,13 +249,24 @@ def eval(
for item in self.input_mapping
}

# Perform DMN call
dmn_outputs = evaluate_dmn(
definition_id=self.decision_definition_id,
version=self.decision_definition_version,
input_values=dmn_inputs,
plugin_id=self.plugin_id,
def _evaluate_dmn():
return evaluate_dmn(
definition_id=self.decision_definition_id,
version=self.decision_definition_version,
input_values=dmn_inputs,
plugin_id=self.plugin_id,
)

# Perform DMN call or retrieve result from cache
inputs = json.dumps(dmn_inputs, cls=DjangoJSONEncoder, sort_keys=True)
cache_key = hash(
str(submission.uuid)
+ self.decision_definition_id
+ self.decision_definition_version
+ self.plugin_id
+ inputs
)
dmn_outputs = cache.get_or_set(cache_key, default=_evaluate_dmn)

# Map DMN output to form variables
return {
Expand Down
229 changes: 229 additions & 0 deletions src/openforms/submissions/tests/form_logic/test_cache_dmn_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
from unittest.mock import patch

import requests_mock
from django_camunda.models import CamundaConfig
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase

from openforms.forms.constants import LogicActionTypes
from openforms.forms.tests.factories import FormLogicFactory

from ..factories import SubmissionFactory
from ..mixins import SubmissionsMixin


@requests_mock.Mocker()
class TestCacheDMNRequests(SubmissionsMixin, APITestCase):

def test_requests_not_made_multiple_times(self, m):
submission = SubmissionFactory.from_components(
[
{"type": "textfield", "key": "fieldA"},
{"type": "textfield", "key": "fieldB"},
{"type": "textfield", "key": "fieldC"},
]
)

FormLogicFactory.create(
form=submission.form,
order=1,
json_logic_trigger=True,
actions=[
{
"variable": "fieldA",
"action": {
"type": LogicActionTypes.evaluate_dmn,
"config": {
"plugin_id": "camunda7",
"decision_definition_id": "some-id",
"decision_definition_version": "1",
"input_mapping": [
{
"form_variable": "fieldA",
"dmn_variable": "fieldADMN",
},
{
"form_variable": "fieldB",
"dmn_variable": "fieldBDMN",
},
],
"output_mapping": [
{
"form_variable": "fieldC",
"dmn_variable": "fieldCDMN",
}
],
},
},
}
],
)

self._add_submission_to_session(submission)

m.get(
"https://camunda.example.com/engine-rest/decision-definition?key=some-id&version=1",
json=[{"id": "some-id:blabla"}],
)
m.post(
"https://camunda.example.com/engine-rest/decision-definition/some-id:blabla/evaluate",
json=[
{"fieldCDMN": {"type": "String", "value": "a-result", "valueInfo": {}}}
],
)

endpoint = reverse(
"api:submission-steps-logic-check",
kwargs={
"submission_uuid": submission.uuid,
"step_uuid": submission.form.formstep_set.first().uuid,
},
)

config = CamundaConfig(
enabled=True,
root_url="https://camunda.example.com",
rest_api_path="engine-rest/",
)

with patch(
"openforms.dmn.contrib.camunda.checks.CamundaConfig.get_solo",
return_value=config,
):
response = self.client.post(
endpoint, data={"data": {"fieldA": "42", "fieldB": "43"}}
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(m.request_history), 2)
self.assertEqual(
m.request_history[-1].url,
"https://camunda.example.com/engine-rest/decision-definition/some-id:blabla/evaluate",
)
self.assertEqual(
m.request_history[-2].url,
"https://camunda.example.com/engine-rest/decision-definition?key=some-id&version=1",
)

response = self.client.post(
endpoint, data={"data": {"fieldA": "42", "fieldB": "43"}}
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(m.request_history), 2)

response = self.client.post(
endpoint, data={"data": {"fieldA": "44", "fieldB": "45"}}
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(m.request_history), 4)

def test_requests_not_made_multiple_times_with_non_serialiseable_vars(self, m):
submission = SubmissionFactory.from_components(
[
{"type": "date", "key": "fieldA", "multiple": True},
{"type": "datetime", "key": "fieldB"},
{"type": "textfield", "key": "fieldC"},
]
)

FormLogicFactory.create(
form=submission.form,
order=1,
json_logic_trigger=True,
actions=[
{
"variable": "fieldA",
"action": {
"type": LogicActionTypes.evaluate_dmn,
"config": {
"plugin_id": "camunda7",
"decision_definition_id": "some-id",
"decision_definition_version": "1",
"input_mapping": [
{
"form_variable": "fieldA",
"dmn_variable": "fieldADMN",
},
{
"form_variable": "fieldB",
"dmn_variable": "fieldBDMN",
},
],
"output_mapping": [
{
"form_variable": "fieldC",
"dmn_variable": "fieldCDMN",
}
],
},
},
}
],
)

self._add_submission_to_session(submission)

m.get(
"https://camunda.example.com/engine-rest/decision-definition?key=some-id&version=1",
json=[{"id": "some-id:blabla"}],
)
m.post(
"https://camunda.example.com/engine-rest/decision-definition/some-id:blabla/evaluate",
json=[
{"fieldCDMN": {"type": "String", "value": "a-result", "valueInfo": {}}}
],
)

endpoint = reverse(
"api:submission-steps-logic-check",
kwargs={
"submission_uuid": submission.uuid,
"step_uuid": submission.form.formstep_set.first().uuid,
},
)

config = CamundaConfig(
enabled=True,
root_url="https://camunda.example.com",
rest_api_path="engine-rest/",
)

with patch(
"openforms.dmn.contrib.camunda.checks.CamundaConfig.get_solo",
return_value=config,
):
response = self.client.post(
endpoint,
data={
"data": {
"fieldA": ["2020-01-01", "2021-01-01"],
"fieldB": "2022-02-21T00:00:00",
}
},
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(m.request_history), 2)
self.assertEqual(
m.request_history[-1].url,
"https://camunda.example.com/engine-rest/decision-definition/some-id:blabla/evaluate",
)
self.assertEqual(
m.request_history[-2].url,
"https://camunda.example.com/engine-rest/decision-definition?key=some-id&version=1",
)

response = self.client.post(
endpoint,
data={
"data": {
"fieldA": ["2020-01-01", "2021-01-01"],
"fieldB": "2022-02-21T00:00:00",
}
},
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(m.request_history), 2)

0 comments on commit b759366

Please sign in to comment.