diff --git a/api/core/entities/execution_extra_content.py b/api/core/entities/execution_extra_content.py index 04ae193396..f11c670069 100644 --- a/api/core/entities/execution_extra_content.py +++ b/api/core/entities/execution_extra_content.py @@ -5,7 +5,7 @@ from typing import Any, TypeAlias from pydantic import BaseModel, ConfigDict, Field -from graphon.nodes.human_input.entities import FormInput, UserAction +from graphon.nodes.human_input.entities import FormInputConfig, UserActionConfig from models.execution_extra_content import ExecutionContentType @@ -16,8 +16,8 @@ class HumanInputFormDefinition(BaseModel): node_id: str node_title: str form_content: str - inputs: Sequence[FormInput] = Field(default_factory=list) - actions: Sequence[UserAction] = Field(default_factory=list) + inputs: Sequence[FormInputConfig] = Field(default_factory=list) + actions: Sequence[UserActionConfig] = Field(default_factory=list) display_in_ui: bool = False form_token: str | None = None resolved_default_values: Mapping[str, Any] = Field(default_factory=dict) diff --git a/api/tests/test_containers_integration_tests/core/repositories/test_human_input_form_repository_impl.py b/api/tests/test_containers_integration_tests/core/repositories/test_human_input_form_repository_impl.py index 6524d6ce61..676833ce98 100644 --- a/api/tests/test_containers_integration_tests/core/repositories/test_human_input_form_repository_impl.py +++ b/api/tests/test_containers_integration_tests/core/repositories/test_human_input_form_repository_impl.py @@ -17,7 +17,7 @@ from core.workflow.human_input_adapter import ( MemberRecipient, WebAppDeliveryMethod, ) -from graphon.nodes.human_input.entities import FormDefinition, HumanInputNodeData, UserAction +from graphon.nodes.human_input.entities import FormDefinition, HumanInputNodeData, UserActionConfig from models.account import ( Account, AccountStatus, @@ -69,7 +69,7 @@ def _build_form_params(delivery_methods: list[DeliveryChannelConfig]) -> FormCre title="Human Approval", delivery_methods=delivery_methods, form_content="

Approve?

", - user_actions=[UserAction(id="approve", title="Approve")], + user_actions=[UserActionConfig(id="approve", title="Approve")], ) return FormCreateParams( workflow_execution_id=str(uuid4()), @@ -185,7 +185,7 @@ class TestHumanInputFormRepositoryImplWithContainers: title="Human Approval", form_content="

Approve?

", inputs=[], - user_actions=[UserAction(id="approve", title="Approve")], + user_actions=[UserActionConfig(id="approve", title="Approve")], ), rendered_content="

Approve?

", delivery_methods=[], @@ -220,7 +220,7 @@ class TestHumanInputFormRepositoryImplWithContainers: title="Human Approval", form_content="

Approve?

", inputs=[], - user_actions=[UserAction(id="approve", title="Approve")], + user_actions=[UserActionConfig(id="approve", title="Approve")], delivery_methods=[WebAppDeliveryMethod()], ), rendered_content="

Approve?

", diff --git a/api/tests/test_containers_integration_tests/core/workflow/test_human_input_resume_node_execution.py b/api/tests/test_containers_integration_tests/core/workflow/test_human_input_resume_node_execution.py index 5aed230cd4..54cf179341 100644 --- a/api/tests/test_containers_integration_tests/core/workflow/test_human_input_resume_node_execution.py +++ b/api/tests/test_containers_integration_tests/core/workflow/test_human_input_resume_node_execution.py @@ -21,7 +21,7 @@ from graphon.graph_engine import GraphEngine from graphon.graph_engine.command_channels import InMemoryChannel from graphon.nodes.end.end_node import EndNode from graphon.nodes.end.entities import EndNodeData -from graphon.nodes.human_input.entities import HumanInputNodeData, UserAction +from graphon.nodes.human_input.entities import HumanInputNodeData, UserActionConfig from graphon.nodes.human_input.enums import HumanInputFormStatus from graphon.nodes.human_input.human_input_node import HumanInputNode from graphon.nodes.start.entities import StartNodeData @@ -112,7 +112,7 @@ def _build_graph( form_content="Awaiting human input", inputs=[], user_actions=[ - UserAction(id="continue", title="Continue"), + UserActionConfig(id="continue", title="Continue"), ], ) human_node = HumanInputNode( diff --git a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_api_workflow_run_repository.py b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_api_workflow_run_repository.py index aebe87839c..fdc5377d1d 100644 --- a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_api_workflow_run_repository.py +++ b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_api_workflow_run_repository.py @@ -15,8 +15,8 @@ from extensions.ext_storage import storage from graphon.entities import WorkflowExecution from graphon.entities.pause_reason import HumanInputRequired, PauseReasonType from graphon.enums import WorkflowExecutionStatus -from graphon.nodes.human_input.entities import FormDefinition, FormInput, UserAction -from graphon.nodes.human_input.enums import FormInputType, HumanInputFormStatus +from graphon.nodes.human_input.entities import FormDefinition, ParagraphInputConfig, UserActionConfig +from graphon.nodes.human_input.enums import HumanInputFormStatus from libs.datetime_utils import naive_utc_now from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom from models.human_input import ( @@ -638,8 +638,8 @@ class TestBuildHumanInputRequiredReason: expiration_time = naive_utc_now() form_definition = FormDefinition( form_content="content", - inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="name")], - user_actions=[UserAction(id="approve", title="Approve")], + inputs=[ParagraphInputConfig(output_variable_name="name")], + user_actions=[UserActionConfig(id="approve", title="Approve")], rendered_content="rendered", expiration_time=expiration_time, default_values={"name": "Alice"}, diff --git a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py index 54b7afc018..c074e9f8ee 100644 --- a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py +++ b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py @@ -177,9 +177,9 @@ def _create_submitted_form( ) -> HumanInputForm: expiration_time = naive_utc_now() + timedelta(days=1) form_definition = FormDefinition( - form_content="content", - inputs=[], - user_actions=[UserAction(id=action_id, title=action_title)], + form_content=form_content, + inputs=inputs or [], + user_actions=[UserActionConfig(id=action_id, title=action_title)], rendered_content="rendered", expiration_time=expiration_time, node_title=node_title, diff --git a/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py b/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py index c4a8148446..d06978c3fa 100644 --- a/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_workflow_pause_details_api.py @@ -12,8 +12,7 @@ from controllers.console.app import workflow_run as workflow_run_module from controllers.web.error import NotFoundError from graphon.entities.pause_reason import HumanInputRequired from graphon.enums import WorkflowExecutionStatus -from graphon.nodes.human_input.entities import FormInput, UserAction -from graphon.nodes.human_input.enums import FormInputType +from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig from libs import login as login_lib from models.account import Account, AccountStatus, TenantAccountRole from models.workflow import WorkflowRun @@ -63,8 +62,8 @@ def test_pause_details_returns_backstage_input_url(app: Flask, monkeypatch: pyte reason = HumanInputRequired( form_id="form-1", form_content="content", - inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="name")], - actions=[UserAction(id="approve", title="Approve")], + inputs=[ParagraphInputConfig(output_variable_name="name")], + actions=[UserActionConfig(id="approve", title="Approve")], node_id="node-1", node_title="Ask Name", ) diff --git a/api/tests/unit_tests/core/app/apps/test_workflow_pause_events.py b/api/tests/unit_tests/core/app/apps/test_workflow_pause_events.py index a3ab379b66..61053a3784 100644 --- a/api/tests/unit_tests/core/app/apps/test_workflow_pause_events.py +++ b/api/tests/unit_tests/core/app/apps/test_workflow_pause_events.py @@ -14,8 +14,7 @@ from core.workflow.system_variables import build_system_variables from graphon.entities import WorkflowStartReason from graphon.entities.pause_reason import HumanInputRequired from graphon.graph_events import GraphRunPausedEvent -from graphon.nodes.human_input.entities import FormInput, UserAction -from graphon.nodes.human_input.enums import FormInputType +from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig from models.account import Account from models.human_input import RecipientType @@ -156,10 +155,8 @@ def test_queue_workflow_paused_event_to_stream_responses(monkeypatch: pytest.Mon reason = HumanInputRequired( form_id="form-1", form_content="Rendered", - inputs=[ - FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", default=None), - ], - actions=[UserAction(id="approve", title="Approve")], + inputs=[ParagraphInputConfig(output_variable_name="field")], + actions=[UserActionConfig(id="approve", title="Approve")], node_id="node-id", node_title="Human Step", ) diff --git a/api/tests/unit_tests/core/entities/test_entities_execution_extra_content.py b/api/tests/unit_tests/core/entities/test_entities_execution_extra_content.py index ef8f360dbf..6c15e54f26 100644 --- a/api/tests/unit_tests/core/entities/test_entities_execution_extra_content.py +++ b/api/tests/unit_tests/core/entities/test_entities_execution_extra_content.py @@ -4,8 +4,7 @@ from core.entities.execution_extra_content import ( HumanInputFormDefinition, HumanInputFormSubmissionData, ) -from graphon.nodes.human_input.entities import FormInput, UserAction -from graphon.nodes.human_input.enums import FormInputType +from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig from models.execution_extra_content import ExecutionContentType @@ -16,8 +15,8 @@ def test_human_input_content_defaults_and_domain_alias() -> None: node_id="node-1", node_title="Human Input", form_content="Please confirm", - inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="answer")], - actions=[UserAction(id="confirm", title="Confirm")], + inputs=[ParagraphInputConfig(output_variable_name="answer")], + actions=[UserActionConfig(id="confirm", title="Confirm")], resolved_default_values={"answer": "yes"}, expiration_time=1_700_000_000, ) diff --git a/api/tests/unit_tests/core/repositories/test_human_input_form_repository_impl.py b/api/tests/unit_tests/core/repositories/test_human_input_form_repository_impl.py index 18ae9fafc8..a2e10d924c 100644 --- a/api/tests/unit_tests/core/repositories/test_human_input_form_repository_impl.py +++ b/api/tests/unit_tests/core/repositories/test_human_input_form_repository_impl.py @@ -23,7 +23,7 @@ from core.workflow.human_input_adapter import ( ) from graphon.nodes.human_input.entities import ( FormDefinition, - UserAction, + UserActionConfig, ) from graphon.nodes.human_input.enums import HumanInputFormKind, HumanInputFormStatus from libs.datetime_utils import naive_utc_now @@ -272,7 +272,7 @@ def _make_form_definition() -> str: return FormDefinition( form_content="hello", inputs=[], - user_actions=[UserAction(id="submit", title="Submit")], + user_actions=[UserActionConfig(id="submit", title="Submit")], rendered_content="

hello

", expiration_time=naive_utc_now(), ).model_dump_json() diff --git a/api/tests/unit_tests/core/repositories/test_human_input_repository.py b/api/tests/unit_tests/core/repositories/test_human_input_repository.py index 4248782d93..1f9e1b21f8 100644 --- a/api/tests/unit_tests/core/repositories/test_human_input_repository.py +++ b/api/tests/unit_tests/core/repositories/test_human_input_repository.py @@ -29,7 +29,7 @@ from core.workflow.human_input_adapter import ( MemberRecipient, WebAppDeliveryMethod, ) -from graphon.nodes.human_input.entities import HumanInputNodeData, UserAction +from graphon.nodes.human_input.entities import HumanInputNodeData, UserActionConfig from graphon.nodes.human_input.enums import HumanInputFormKind, HumanInputFormStatus from libs.datetime_utils import naive_utc_now from models.human_input import HumanInputFormRecipient, RecipientType diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_parallel_human_input_join_resume.py b/api/tests/unit_tests/core/workflow/graph_engine/test_parallel_human_input_join_resume.py index 75bc6d05f7..624c1fb01d 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/test_parallel_human_input_join_resume.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_parallel_human_input_join_resume.py @@ -24,7 +24,14 @@ from graphon.graph_events import ( from graphon.nodes.base.entities import OutputVariableEntity from graphon.nodes.end.end_node import EndNode from graphon.nodes.end.entities import EndNodeData -from graphon.nodes.human_input.entities import HumanInputNodeData, UserAction +from graphon.nodes.human_input.entities import ( + FileInputConfig, + FileListInputConfig, + HumanInputNodeData, + SelectInputConfig, + StringListSource, + UserActionConfig, +) from graphon.nodes.human_input.enums import HumanInputFormStatus from graphon.nodes.human_input.human_input_node import HumanInputNode from graphon.nodes.start.entities import StartNodeData diff --git a/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py b/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py index 0659984c76..8499fdfbe4 100644 --- a/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py +++ b/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py @@ -36,20 +36,25 @@ from graphon.entities import GraphInitParams from graphon.node_events import PauseRequestedEvent from graphon.node_events.node import StreamCompletedEvent from graphon.nodes.human_input.entities import ( - FormInput, - FormInputDefault, + FileInputConfig, + FileListInputConfig, HumanInputNodeData, - UserAction, + ParagraphInputConfig, + SelectInputConfig, + StringListSource, + StringSource, + UserActionConfig, ) from graphon.nodes.human_input.enums import ( ButtonStyle, FormInputType, HumanInputFormStatus, - PlaceholderType, TimeoutUnit, + ValueSourceType, ) from graphon.nodes.human_input.human_input_node import HumanInputNode from graphon.runtime import GraphRuntimeState, VariablePool +from graphon.variables.segments import ArrayFileSegment, FileSegment from libs.datetime_utils import naive_utc_now @@ -195,27 +200,27 @@ class TestFormInput: def test_text_input_with_constant_default(self): """Test text input with constant default value.""" - default = FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter your response here...") + default = StringSource(type=ValueSourceType.CONSTANT, value="Enter your response here...") - form_input = FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default) + form_input = ParagraphInputConfig(output_variable_name="user_input", default=default) - assert form_input.type == FormInputType.TEXT_INPUT + assert form_input.type == FormInputType.PARAGRAPH assert form_input.output_variable_name == "user_input" - assert form_input.default.type == PlaceholderType.CONSTANT + assert form_input.default.type == ValueSourceType.CONSTANT assert form_input.default.value == "Enter your response here..." def test_text_input_with_variable_default(self): """Test text input with variable default value.""" - default = FormInputDefault(type=PlaceholderType.VARIABLE, selector=["node_123", "output_var"]) + default = StringSource(type=ValueSourceType.VARIABLE, selector=["node_123", "output_var"]) - form_input = FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default) + form_input = ParagraphInputConfig(output_variable_name="user_input", default=default) - assert form_input.default.type == PlaceholderType.VARIABLE + assert form_input.default.type == ValueSourceType.VARIABLE assert form_input.default.selector == ["node_123", "output_var"] def test_form_input_without_default(self): """Test form input without default value.""" - form_input = FormInput(type=FormInputType.PARAGRAPH, output_variable_name="description") + form_input = ParagraphInputConfig(output_variable_name="description") assert form_input.type == FormInputType.PARAGRAPH assert form_input.output_variable_name == "description" @@ -227,7 +232,7 @@ class TestUserAction: def test_user_action_creation(self): """Test user action creation.""" - action = UserAction(id="approve", title="Approve", button_style=ButtonStyle.PRIMARY) + action = UserActionConfig(id="approve", title="Approve", button_style=ButtonStyle.PRIMARY) assert action.id == "approve" assert action.title == "Approve" @@ -235,13 +240,13 @@ class TestUserAction: def test_user_action_default_button_style(self): """Test user action with default button style.""" - action = UserAction(id="cancel", title="Cancel") + action = UserActionConfig(id="cancel", title="Cancel") assert action.button_style == ButtonStyle.DEFAULT def test_user_action_length_boundaries(self): """Test user action id and title length boundaries.""" - action = UserAction(id="a" * 20, title="b" * 20) + action = UserActionConfig(id="a" * 20, title="b" * 20) assert action.id == "a" * 20 assert action.title == "b" * 20 @@ -259,7 +264,7 @@ class TestUserAction: data[field_name] = value with pytest.raises(ValidationError) as exc_info: - UserAction.model_validate(data) + UserActionConfig.model_validate(data) errors = exc_info.value.errors() assert any(error["loc"] == (field_name,) and error["type"] == "string_too_long" for error in errors) @@ -273,14 +278,13 @@ class TestHumanInputNodeData: delivery_methods = [WebAppDeliveryMethod(enabled=True, config=_WebAppDeliveryConfig())] inputs = [ - FormInput( - type=FormInputType.TEXT_INPUT, + ParagraphInputConfig( output_variable_name="content", - default=FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter content..."), + default=StringSource(type=ValueSourceType.CONSTANT, value="Enter content..."), ) ] - user_actions = [UserAction(id="submit", title="Submit", button_style=ButtonStyle.PRIMARY)] + user_actions = [UserActionConfig(id="submit", title="Submit", button_style=ButtonStyle.PRIMARY)] node_data = HumanInputNodeData( title="Human Input Test", @@ -338,8 +342,8 @@ class TestHumanInputNodeData: def test_duplicate_input_output_variable_name_raises_validation_error(self): """Duplicate form input output_variable_name should raise validation error.""" duplicate_inputs = [ - FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="content"), - FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="content"), + ParagraphInputConfig(output_variable_name="content"), + ParagraphInputConfig(output_variable_name="content"), ] with pytest.raises(ValidationError, match="duplicated output_variable_name 'content'"): @@ -348,8 +352,8 @@ class TestHumanInputNodeData: def test_duplicate_user_action_ids_raise_validation_error(self): """Duplicate user action ids should raise validation error.""" duplicate_actions = [ - UserAction(id="submit", title="Submit"), - UserAction(id="submit", title="Submit Again"), + UserActionConfig(id="submit", title="Submit"), + UserActionConfig(id="submit", title="Submit Again"), ] with pytest.raises(ValidationError, match="duplicated user action id 'submit'"): @@ -458,18 +462,16 @@ class TestHumanInputNodeVariableResolution: title="Human Input", form_content="Provide your name", inputs=[ - FormInput( - type=FormInputType.TEXT_INPUT, + ParagraphInputConfig( output_variable_name="user_name", - default=FormInputDefault(type=PlaceholderType.VARIABLE, selector=["start", "name"]), + default=StringSource(type=ValueSourceType.VARIABLE, selector=["start", "name"]), ), - FormInput( - type=FormInputType.TEXT_INPUT, + ParagraphInputConfig( output_variable_name="user_email", - default=FormInputDefault(type=PlaceholderType.CONSTANT, value="foo@example.com"), + default=StringSource(type=ValueSourceType.CONSTANT, value="foo@example.com"), ), ], - user_actions=[UserAction(id="submit", title="Submit")], + user_actions=[UserActionConfig(id="submit", title="Submit")], ) config = {"id": "human", "data": node_data.model_dump()} @@ -534,7 +536,7 @@ class TestHumanInputNodeVariableResolution: title="Human Input", form_content="Provide your name", inputs=[], - user_actions=[UserAction(id="submit", title="Submit")], + user_actions=[UserActionConfig(id="submit", title="Submit")], ) config = {"id": "human", "data": node_data.model_dump()} @@ -661,7 +663,7 @@ class TestHumanInputNodeVariableResolution: title="Human Input", form_content="Provide your name", inputs=[], - user_actions=[UserAction(id="submit", title="Submit")], + user_actions=[UserActionConfig(id="submit", title="Submit")], delivery_methods=[ EmailDeliveryMethod( enabled=True, @@ -721,15 +723,17 @@ class TestValidation: def test_invalid_form_input_type(self): """Test validation with invalid form input type.""" with pytest.raises(ValidationError): - FormInput( - type="invalid-type", # Invalid type - output_variable_name="test", + ParagraphInputConfig.model_validate( + { + "type": "invalid-type", + "output_variable_name": "test", + } ) def test_invalid_button_style(self): """Test validation with invalid button style.""" with pytest.raises(ValidationError): - UserAction( + UserActionConfig( id="test", title="Test", button_style="invalid-style", # Invalid style @@ -777,13 +781,8 @@ class TestHumanInputNodeRenderedContent: node_data = HumanInputNodeData( title="Human Input", form_content="Name: {{#$output.name#}}", - inputs=[ - FormInput( - type=FormInputType.TEXT_INPUT, - output_variable_name="name", - ) - ], - user_actions=[UserAction(id="approve", title="Approve")], + inputs=[ParagraphInputConfig(output_variable_name="name")], + user_actions=[UserActionConfig(id="approve", title="Approve")], ) config = {"id": "human", "data": node_data.model_dump()} diff --git a/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py b/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py index 4a9438b14f..e4fb5954f6 100644 --- a/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py +++ b/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py @@ -6,15 +6,26 @@ from core.workflow.node_runtime import DifyHumanInputNodeRuntime from core.workflow.system_variables import default_system_variables from graphon.entities import GraphInitParams from graphon.enums import BuiltinNodeTypes +from graphon.file import FileTransferMethod, FileType from graphon.graph_events import ( NodeRunHumanInputFormFilledEvent, NodeRunHumanInputFormTimeoutEvent, NodeRunStartedEvent, ) -from graphon.nodes.human_input.entities import HumanInputNodeData +from graphon.nodes.human_input.entities import ( + FileInputConfig, + FileListInputConfig, + HumanInputNodeData, + ParagraphInputConfig, + SelectInputConfig, + StringListSource, + UserActionConfig, +) from graphon.nodes.human_input.enums import HumanInputFormStatus from graphon.nodes.human_input.human_input_node import HumanInputNode from graphon.runtime import GraphRuntimeState, VariablePool +from graphon.variables.segments import ArrayFileSegment, FileSegment, StringSegment +from graphon.variables.types import SegmentType from libs.datetime_utils import naive_utc_now @@ -76,19 +87,15 @@ def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name# "title": "Human Input", "form_content": form_content, "inputs": [ - { - "type": "text_input", - "output_variable_name": "name", - "default": {"type": "constant", "value": ""}, - } - ], - "user_actions": [ - { - "id": "Accept", - "title": "Approve", - "button_style": "default", - } + ParagraphInputConfig(output_variable_name="name").model_dump(mode="json"), + SelectInputConfig( + output_variable_name="decision", + option_source=StringListSource(type="constant", value=["approve", "reject"]), + ).model_dump(mode="json"), + FileInputConfig(output_variable_name="attachment").model_dump(mode="json"), + FileListInputConfig(output_variable_name="attachments", number_limits=2).model_dump(mode="json"), ], + "user_actions": [UserActionConfig(id="Accept", title="Approve").model_dump(mode="json")], }, } @@ -97,7 +104,28 @@ def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name# rendered_content=form_content, submitted=True, selected_action_id="Accept", - submitted_data={"name": "Alice"}, + submitted_data={ + "name": "Alice", + "decision": "approve", + "attachment": { + "type": "document", + "transfer_method": "remote_url", + "remote_url": "https://example.com/resume.pdf", + "filename": "resume.pdf", + "extension": ".pdf", + "mime_type": "application/pdf", + }, + "attachments": [ + { + "type": "image", + "transfer_method": "remote_url", + "remote_url": "https://example.com/a.png", + "filename": "a.png", + "extension": ".png", + "mime_type": "image/png", + } + ], + }, status=HumanInputFormStatus.SUBMITTED, expiration_time=naive_utc_now() + datetime.timedelta(days=1), ) @@ -138,20 +166,8 @@ def _build_timeout_node() -> HumanInputNode: "data": { "title": "Human Input", "form_content": "Please enter your name:\n\n{{#$output.name#}}", - "inputs": [ - { - "type": "text_input", - "output_variable_name": "name", - "default": {"type": "constant", "value": ""}, - } - ], - "user_actions": [ - { - "id": "Accept", - "title": "Approve", - "button_style": "default", - } - ], + "inputs": [ParagraphInputConfig(output_variable_name="name").model_dump(mode="json")], + "user_actions": [UserActionConfig(id="Accept", title="Approve").model_dump(mode="json")], }, } diff --git a/api/tests/unit_tests/libs/_human_input/support.py b/api/tests/unit_tests/libs/_human_input/support.py index e6cc23161e..0f593507fd 100644 --- a/api/tests/unit_tests/libs/_human_input/support.py +++ b/api/tests/unit_tests/libs/_human_input/support.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from datetime import datetime, timedelta from typing import Any -from graphon.nodes.human_input.entities import FormInput +from graphon.nodes.human_input.entities import FormInputConfig from graphon.nodes.human_input.enums import TimeoutUnit from libs.datetime_utils import naive_utc_now @@ -45,7 +45,7 @@ class HumanInputForm: tenant_id: str app_id: str | None form_content: str - inputs: list[FormInput] + inputs: list[FormInputConfig] user_actions: list[dict[str, Any]] timeout: int timeout_unit: TimeoutUnit @@ -88,7 +88,7 @@ class HumanInputForm: def to_response_dict(self, *, include_site_info: bool) -> dict[str, Any]: inputs_response = [ { - "type": form_input.type.name.lower().replace("_", "-"), + "type": form_input.type.value, "output_variable_name": form_input.output_variable_name, } for form_input in self.inputs diff --git a/api/tests/unit_tests/libs/_human_input/test_form_service.py b/api/tests/unit_tests/libs/_human_input/test_form_service.py index fa2c02020b..decd7c484b 100644 --- a/api/tests/unit_tests/libs/_human_input/test_form_service.py +++ b/api/tests/unit_tests/libs/_human_input/test_form_service.py @@ -7,11 +7,10 @@ from datetime import timedelta import pytest from graphon.nodes.human_input.entities import ( - FormInput, - UserAction, + ParagraphInputConfig, + UserActionConfig, ) from graphon.nodes.human_input.enums import ( - FormInputType, TimeoutUnit, ) from libs.datetime_utils import naive_utc_now @@ -50,8 +49,8 @@ class TestFormService: "tenant_id": "tenant-abc", "app_id": "app-def", "form_content": "# Test Form\n\nInput: {{#$output.input#}}", - "inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)], - "user_actions": [UserAction(id="submit", title="Submit")], + "inputs": [ParagraphInputConfig(output_variable_name="input")], + "user_actions": [UserActionConfig(id="submit", title="Submit")], "timeout": 1, "timeout_unit": TimeoutUnit.HOUR, "form_token": "token-xyz", @@ -304,8 +303,8 @@ class TestFormValidation: "tenant_id": "tenant-abc", "app_id": "app-def", "form_content": "Test form", - "inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", default=None)], - "user_actions": [UserAction(id="submit", title="Submit")], + "inputs": [ParagraphInputConfig(output_variable_name="required_input")], + "user_actions": [UserActionConfig(id="submit", title="Submit")], "timeout": 1, "timeout_unit": TimeoutUnit.HOUR, } diff --git a/api/tests/unit_tests/libs/_human_input/test_models.py b/api/tests/unit_tests/libs/_human_input/test_models.py index 866ee61b3e..f6e4c9ec18 100644 --- a/api/tests/unit_tests/libs/_human_input/test_models.py +++ b/api/tests/unit_tests/libs/_human_input/test_models.py @@ -7,11 +7,10 @@ from datetime import datetime, timedelta import pytest from graphon.nodes.human_input.entities import ( - FormInput, - UserAction, + ParagraphInputConfig, + UserActionConfig, ) from graphon.nodes.human_input.enums import ( - FormInputType, TimeoutUnit, ) from libs.datetime_utils import naive_utc_now @@ -32,8 +31,8 @@ class TestHumanInputForm: "tenant_id": "tenant-abc", "app_id": "app-def", "form_content": "# Test Form\n\nInput: {{#$output.input#}}", - "inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)], - "user_actions": [UserAction(id="submit", title="Submit")], + "inputs": [ParagraphInputConfig(output_variable_name="input")], + "user_actions": [UserActionConfig(id="submit", title="Submit")], "timeout": 2, "timeout_unit": TimeoutUnit.HOUR, "form_token": "token-xyz", @@ -132,7 +131,7 @@ class TestHumanInputForm: assert "site" not in response assert response["form_content"] == "# Test Form\n\nInput: {{#$output.input#}}" assert len(response["inputs"]) == 1 - assert response["inputs"][0]["type"] == "text-input" + assert response["inputs"][0]["type"] == "paragraph" assert response["inputs"][0]["output_variable_name"] == "input" def test_form_to_response_dict_with_site_info(self, sample_form_data): diff --git a/api/tests/unit_tests/services/test_human_input_service.py b/api/tests/unit_tests/services/test_human_input_service.py index 55af564821..af0e793261 100644 --- a/api/tests/unit_tests/services/test_human_input_service.py +++ b/api/tests/unit_tests/services/test_human_input_service.py @@ -9,12 +9,17 @@ from core.repositories.human_input_repository import ( HumanInputFormRecord, HumanInputFormSubmissionRepository, ) +from graphon.file import File, FileTransferMethod, FileType from graphon.nodes.human_input.entities import ( + FileInputConfig, + FileListInputConfig, FormDefinition, - FormInput, - UserAction, + ParagraphInputConfig, + SelectInputConfig, + StringListSource, + UserActionConfig, ) -from graphon.nodes.human_input.enums import FormInputType, HumanInputFormKind, HumanInputFormStatus +from graphon.nodes.human_input.enums import HumanInputFormKind, HumanInputFormStatus, ValueSourceType from libs.datetime_utils import naive_utc_now from models.human_input import RecipientType from services.human_input_service import ( @@ -50,7 +55,7 @@ def sample_form_record(): definition=FormDefinition( form_content="hello", inputs=[], - user_actions=[UserAction(id="submit", title="Submit")], + user_actions=[UserActionConfig(id="submit", title="Submit")], rendered_content="

hello

", expiration_time=naive_utc_now() + timedelta(hours=1), ), @@ -273,7 +278,7 @@ def test_submit_form_by_token_missing_inputs(sample_form_record, mock_session_fa definition_with_input = FormDefinition( form_content="hello", - inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="content")], + inputs=[ParagraphInputConfig(output_variable_name="content")], user_actions=sample_form_record.definition.user_actions, rendered_content="

hello

", expiration_time=sample_form_record.expiration_time,