diff --git a/src/agent_toolkit/forestadmin/agent_toolkit/resources/actions/resources.py b/src/agent_toolkit/forestadmin/agent_toolkit/resources/actions/resources.py index d9b30f492..36a9cf5e3 100644 --- a/src/agent_toolkit/forestadmin/agent_toolkit/resources/actions/resources.py +++ b/src/agent_toolkit/forestadmin/agent_toolkit/resources/actions/resources.py @@ -59,7 +59,10 @@ async def execute(self, request: ActionRequest) -> Union[FileResponse, Response] # As forms are dynamic, we don't have any way to ensure that we're parsing the data correctly # => better send invalid data to the getForm() customer handler than to the execute() one. unsafe_data = ForestValueConverter.make_form_unsafe_data(raw_data) - fields = await request.collection.get_form(request.user, request.action_name, unsafe_data, filter_) + + fields = await request.collection.get_form( + request.user, request.action_name, unsafe_data, filter_, {"include_hidden_fields": True} + ) fields = SchemaActionGenerator.extract_fields_and_layout(fields)[0] # Now that we have the field list, we can parse the data again. diff --git a/src/agent_toolkit/tests/resources/actions/test_resources.py b/src/agent_toolkit/tests/resources/actions/test_resources.py index 48859f2c8..b07c61b69 100644 --- a/src/agent_toolkit/tests/resources/actions/test_resources.py +++ b/src/agent_toolkit/tests/resources/actions/test_resources.py @@ -1,4 +1,5 @@ import asyncio +import copy import importlib import json import sys @@ -980,3 +981,56 @@ def execute(ctx, result_builder): response = self.loop.run_until_complete(self.action_resource.execute(request)) self.assertEqual(response.headers["headerOne"], "valueOne") self.assertEqual(response.headers["headerOne"], "valueOne") + + def test_execute_should_get_all_form_fields_included_hidden(self): + + self.decorated_collection_book.add_action( + "test_action_global_hidden_fields", + { + "scope": ActionsScope.GLOBAL, + "execute": lambda ctx, rb: rb.success(ctx.form_values.get("hidden_field")), + "form": [ + { + "type": "String", + "label": "hidden_field", + "if_": lambda ctx: False, + } + ], + }, + ) + body_params = copy.deepcopy(self.body_params) + body_params["data"]["attributes"]["values"] = {"hidden_field": "hidden_value"} + request = ActionRequest( + method=RequestMethod.POST, + action_name="test_action_global_hidden_fields", + collection=self.decorated_collection_book, + body=body_params, + query={ + "timezone": "Europe/Paris", + "collection_name": "Book", + "action_name": 0, + "slug": "test_action_global_hidden_fields", + }, + headers={}, + user=self.mocked_caller, + client_ip="127.0.0.1", + ) + with patch.object( + self.decorated_collection_book, + "get_form", + new_callable=AsyncMock, + wraps=self.decorated_collection_book.get_form, + ) as spy_get_form: + response = self.loop.run_until_complete(self.action_resource.execute(request)) + spy_get_form.assert_awaited_once_with( + request.user, + "test_action_global_hidden_fields", + {"hidden_field": "hidden_value"}, + ANY, + {"include_hidden_fields": True}, + ) + self.assertEqual(response.status, 200) + self.assertEqual( + response.body, + '{"success": "hidden_value"}', + ) diff --git a/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/action/collections.py b/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/action/collections.py index f22138b9e..edd6b3f9b 100644 --- a/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/action/collections.py +++ b/src/datasource_toolkit/forestadmin/datasource_toolkit/decorators/action/collections.py @@ -125,7 +125,11 @@ async def get_form( form_values = await self._build_form_values(context, form_fields, form_values) context.form_values.update(form_values) action_fields = await self._build_fields( - context, form_fields, form_values, meta.get("search_values", {}).get(meta.get("search_field")) + context, + form_fields, + form_values, + meta.get("search_values", {}).get(meta.get("search_field")), + meta.get("include_hidden_fields", False), ) self._set_watch_changes_attr(action_fields, context) @@ -215,10 +219,11 @@ async def _build_fields( fields: List[DynamicFormElements], form_values: RecordsDataAlias, search_value: Optional[str] = None, + include_hidden_fields: bool = False, ) -> List[Union[ActionLayoutElement, ActionField]]: action_fields: List[Union[ActionLayoutElement, ActionField]] = [] for field in fields: - if await field.if_(context): + if include_hidden_fields or await field.if_(context): value = form_values if isinstance(field, DynamicLayoutElements) else form_values.get(field.id) action_field = await field.to_action_field(context, value, search_value) # type:ignore if action_field is not None: diff --git a/src/datasource_toolkit/tests/decorators/action/test_action_decorator.py b/src/datasource_toolkit/tests/decorators/action/test_action_decorator.py index 2f95d2e45..ac53d486b 100644 --- a/src/datasource_toolkit/tests/decorators/action/test_action_decorator.py +++ b/src/datasource_toolkit/tests/decorators/action/test_action_decorator.py @@ -819,3 +819,43 @@ def _search_fn(context, search_value): } ], ) + + def test_get_form_should_return_hidden_fields_when_asked(self): + if_fn = Mock(return_value=False) + test_action: ActionDict = { + "scope": ActionsScope.SINGLE, + "execute": lambda ctx, result_builder: result_builder.success("ok"), + "form": [ + { + "label": "name", + "type": ActionFieldType.STRING, + "if_": if_fn, + }, + ], + } + self.product_collection.add_action("action_test", test_action) + + result = self.loop.run_until_complete( + self.product_collection.get_form( + self.mocked_caller, "action_test", {"name": "name"}, None, {"include_hidden_fields": True} + ) + ) + self.assertEqual( + result, + [ + { + "label": "name", + "id": "name", + "type": ActionFieldType.STRING, + "description": "", + "is_read_only": False, + "is_required": False, + "value": "name", + "default_value": None, + "collection_name": None, + "enum_values": None, + "watch_changes": False, + } + ], + ) + if_fn.assert_not_called()