mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:58:04 +08:00
refactor(api): rename placeholder to default_value in various parts
Previously the fields / classes are named with `placeholder`. However, the actual purpose is to use as default values. This commit addresses this problem by correcting names for relevant fields / classes. - FormInputPlaceholder - FormInput.placeholder - HumanInputRequiredResponse.resolved_placeholder_values - HumanInputFormDefinition.resolved_placeholder_values - FormCreateParams.resolved_placeholder_values - HumanInputRequired.resolved_placeholder_values - The `resolved_placeholder_values` argument of _create_human_input_delivery_test_form - The `resolved_placeholder_values` inside _jsonify_form_definition
This commit is contained in:
@ -21,7 +21,7 @@ from services.human_input_service import Form, FormNotFoundError, HumanInputServ
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _stringify_placeholder_values(values: dict[str, object]) -> dict[str, str]:
|
||||
def _stringify_default_values(values: dict[str, object]) -> dict[str, str]:
|
||||
result: dict[str, str] = {}
|
||||
for key, value in values.items():
|
||||
if value is None:
|
||||
@ -43,7 +43,7 @@ def _jsonify_form_definition(form: Form, site_payload: dict | None = None) -> Re
|
||||
payload = {
|
||||
"form_content": definition_payload["rendered_content"],
|
||||
"inputs": definition_payload["inputs"],
|
||||
"resolved_placeholder_values": _stringify_placeholder_values(definition_payload["placeholder_values"]),
|
||||
"resolved_default_values": _stringify_default_values(definition_payload["default_values"]),
|
||||
"user_actions": definition_payload["user_actions"],
|
||||
"expiration_time": _to_timestamp(form.expiration_time),
|
||||
}
|
||||
|
||||
@ -302,7 +302,7 @@ class WorkflowResponseConverter:
|
||||
actions=reason.actions,
|
||||
display_in_ui=reason.display_in_ui,
|
||||
form_token=reason.form_token,
|
||||
resolved_placeholder_values=reason.resolved_placeholder_values,
|
||||
resolved_default_values=reason.resolved_default_values,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@ -281,7 +281,7 @@ class HumanInputRequiredResponse(StreamResponse):
|
||||
actions: Sequence[UserAction] = Field(default_factory=list)
|
||||
display_in_ui: bool = False
|
||||
form_token: str | None = None
|
||||
resolved_placeholder_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
|
||||
event: StreamEvent = StreamEvent.HUMAN_INPUT_REQUIRED
|
||||
workflow_run_id: str
|
||||
|
||||
@ -20,7 +20,7 @@ class HumanInputFormDefinition(BaseModel):
|
||||
actions: Sequence[UserAction] = Field(default_factory=list)
|
||||
display_in_ui: bool = False
|
||||
form_token: str | None = None
|
||||
resolved_placeholder_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class HumanInputFormSubmissionData(BaseModel):
|
||||
|
||||
@ -337,7 +337,7 @@ class HumanInputFormRepositoryImpl:
|
||||
rendered_content=params.rendered_content,
|
||||
timeout=form_config.timeout,
|
||||
timeout_unit=form_config.timeout_unit,
|
||||
placeholder_values=dict(params.resolved_placeholder_values),
|
||||
default_values=dict(params.resolved_default_values),
|
||||
display_in_ui=params.display_in_ui,
|
||||
node_title=form_config.title,
|
||||
)
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from libs.exception import BaseHTTPException
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMeta
|
||||
from libs.exception import BaseHTTPException
|
||||
|
||||
|
||||
class ToolProviderNotFoundError(ValueError):
|
||||
|
||||
@ -22,16 +22,16 @@ class HumanInputRequired(BaseModel):
|
||||
node_id: str
|
||||
node_title: str
|
||||
|
||||
# The `resolved_placeholder_values` stores the resolved values of variable placeholders. It's a mapping from
|
||||
# The `resolved_default_values` stores the resolved values of variable defaults. It's a mapping from
|
||||
# `output_variable_name` to their resolved values.
|
||||
#
|
||||
# For example, The form contains a input with output variable name `name` and placeholder type `VARIABLE`, its
|
||||
# selector is ["start", "name"]. While the HumanInputNode is executed, the correspond value of variable
|
||||
# `start.name` in variable pool is `John`. Thus, the resolved value of the output variable `name` is `John`. The
|
||||
# `resolved_placeholder_values` is `{"name": "John"}`.
|
||||
# `resolved_default_values` is `{"name": "John"}`.
|
||||
#
|
||||
# Only form inputs with placeholder type `VARIABLE` will be resolved and stored in `resolved_placeholder_values`.
|
||||
resolved_placeholder_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
# Only form inputs with default value type `VARIABLE` will be resolved and stored in `resolved_default_values`.
|
||||
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
|
||||
# The `form_token` is the token used to submit the form via UI surfaces. It corresponds to
|
||||
# `HumanInputFormRecipient.access_token`.
|
||||
|
||||
@ -153,20 +153,20 @@ def apply_debug_email_recipient(
|
||||
return method.model_copy(update={"config": debug_config})
|
||||
|
||||
|
||||
class FormInputPlaceholder(BaseModel):
|
||||
"""Placeholder configuration for form inputs."""
|
||||
class FormInputDefault(BaseModel):
|
||||
"""Default configuration for form inputs."""
|
||||
|
||||
# NOTE: Ideally, a discriminated union would be used to model
|
||||
# FormInputPlaceholder. However, the UI requires preserving the previous
|
||||
# FormInputDefault. However, the UI requires preserving the previous
|
||||
# value when switching between `VARIABLE` and `CONSTANT` types. This
|
||||
# necessitates retaining all fields, making a discriminated union unsuitable.
|
||||
|
||||
type: PlaceholderType
|
||||
|
||||
# The selector of placeholder variable, used when `type` is `VARIABLE`
|
||||
# The selector of default variable, used when `type` is `VARIABLE`.
|
||||
selector: Sequence[str] = Field(default_factory=tuple) #
|
||||
|
||||
# The value of the placeholder, used when `type` is `CONSTANT`.
|
||||
# The value of the default, used when `type` is `CONSTANT`.
|
||||
# TODO: How should we express JSON values?
|
||||
value: str = ""
|
||||
|
||||
@ -184,7 +184,7 @@ class FormInput(BaseModel):
|
||||
|
||||
type: FormInputType
|
||||
output_variable_name: str
|
||||
placeholder: Optional[FormInputPlaceholder] = None
|
||||
default: Optional[FormInputDefault] = None
|
||||
|
||||
|
||||
_IDENTIFIER_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
||||
@ -286,14 +286,14 @@ class HumanInputNodeData(BaseNodeData):
|
||||
_add_variable_selectors(delivery_method.extract_variable_selectors())
|
||||
|
||||
for input in self.inputs:
|
||||
placeholder = input.placeholder
|
||||
if placeholder is None:
|
||||
default_value = input.default
|
||||
if default_value is None:
|
||||
continue
|
||||
if placeholder.type == PlaceholderType.CONSTANT:
|
||||
if default_value.type == PlaceholderType.CONSTANT:
|
||||
continue
|
||||
placeholder_key = ".".join(placeholder.selector)
|
||||
qualified_variable_mapping_key = f"{node_id}.#{placeholder_key}#"
|
||||
variable_mappings[qualified_variable_mapping_key] = placeholder.selector
|
||||
default_value_key = ".".join(default_value.selector)
|
||||
qualified_variable_mapping_key = f"{node_id}.#{default_value_key}#"
|
||||
variable_mappings[qualified_variable_mapping_key] = default_value.selector
|
||||
|
||||
return variable_mappings
|
||||
|
||||
@ -316,8 +316,8 @@ class FormDefinition(BaseModel):
|
||||
timeout: int
|
||||
timeout_unit: TimeoutUnit
|
||||
|
||||
# this is used to store the values of the placeholders
|
||||
placeholder_values: dict[str, Any] = Field(default_factory=dict)
|
||||
# this is used to store the resolved default values
|
||||
default_values: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
# node_title records the title of the HumanInput node.
|
||||
node_title: str | None = None
|
||||
|
||||
@ -51,7 +51,7 @@ class FormInputType(enum.StrEnum):
|
||||
|
||||
|
||||
class PlaceholderType(enum.StrEnum):
|
||||
"""Placeholder types for form inputs."""
|
||||
"""Default value types for form inputs."""
|
||||
|
||||
VARIABLE = enum.auto()
|
||||
CONSTANT = enum.auto()
|
||||
|
||||
@ -136,23 +136,23 @@ class HumanInputNode(Node[HumanInputNodeData]):
|
||||
pause_requested_event = PauseRequestedEvent(reason=required_event)
|
||||
return pause_requested_event
|
||||
|
||||
def _resolve_inputs(self) -> Mapping[str, Any]:
|
||||
def _resolve_default_values(self) -> Mapping[str, Any]:
|
||||
variable_pool = self.graph_runtime_state.variable_pool
|
||||
resolved_inputs = {}
|
||||
resolved_defaults: dict[str, Any] = {}
|
||||
for input in self._node_data.inputs:
|
||||
if (placeholder := input.placeholder) is None:
|
||||
if (default_value := input.default) is None:
|
||||
continue
|
||||
if placeholder.type == PlaceholderType.CONSTANT:
|
||||
if default_value.type == PlaceholderType.CONSTANT:
|
||||
continue
|
||||
placeholder_value = variable_pool.get(placeholder.selector)
|
||||
if placeholder_value is None:
|
||||
resolved_value = variable_pool.get(default_value.selector)
|
||||
if resolved_value is None:
|
||||
# TODO: How should we handle this?
|
||||
continue
|
||||
resolved_inputs[input.output_variable_name] = (
|
||||
WorkflowRuntimeTypeConverter().value_to_json_encodable_recursive(placeholder_value.value)
|
||||
resolved_defaults[input.output_variable_name] = (
|
||||
WorkflowRuntimeTypeConverter().value_to_json_encodable_recursive(resolved_value.value)
|
||||
)
|
||||
|
||||
return resolved_inputs
|
||||
return resolved_defaults
|
||||
|
||||
def _should_require_console_recipient(self) -> bool:
|
||||
if self.invoke_from == InvokeFrom.DEBUGGER:
|
||||
@ -181,7 +181,7 @@ class HumanInputNode(Node[HumanInputNodeData]):
|
||||
|
||||
def _human_input_required_event(self, form_entity: HumanInputFormEntity) -> HumanInputRequired:
|
||||
node_data = self._node_data
|
||||
resolved_placeholder_values = self._resolve_inputs()
|
||||
resolved_default_values = self._resolve_default_values()
|
||||
display_in_ui = self._display_in_ui()
|
||||
form_token = form_entity.web_app_token
|
||||
if display_in_ui and form_token is None:
|
||||
@ -195,7 +195,7 @@ class HumanInputNode(Node[HumanInputNodeData]):
|
||||
node_id=self.id,
|
||||
node_title=node_data.title,
|
||||
form_token=form_token,
|
||||
resolved_placeholder_values=resolved_placeholder_values,
|
||||
resolved_default_values=resolved_default_values,
|
||||
)
|
||||
|
||||
def _run(self) -> Generator[NodeEventBase, None, None]:
|
||||
@ -222,7 +222,7 @@ class HumanInputNode(Node[HumanInputNodeData]):
|
||||
rendered_content=self._render_form_content_before_submission(),
|
||||
delivery_methods=self._effective_delivery_methods(),
|
||||
display_in_ui=display_in_ui,
|
||||
resolved_placeholder_values=self._resolve_inputs(),
|
||||
resolved_default_values=self._resolve_default_values(),
|
||||
console_recipient_required=self._should_require_console_recipient(),
|
||||
console_creator_account_id=(
|
||||
self.user_id if self.invoke_from in {InvokeFrom.DEBUGGER, InvokeFrom.EXPLORE} else None
|
||||
@ -330,11 +330,11 @@ class HumanInputNode(Node[HumanInputNodeData]):
|
||||
node_data: Mapping[str, Any],
|
||||
) -> Mapping[str, Sequence[str]]:
|
||||
"""
|
||||
Extract variable selectors referenced in form content and input placeholders.
|
||||
Extract variable selectors referenced in form content and input default values.
|
||||
|
||||
This method should parse:
|
||||
1. Variables referenced in form_content ({{#node_name.var_name#}})
|
||||
2. Variables referenced in input placeholders
|
||||
2. Variables referenced in input default values
|
||||
"""
|
||||
validated_node_data = HumanInputNodeData.model_validate(node_data)
|
||||
return validated_node_data.extract_variable_selector_to_variable_mapping(node_id)
|
||||
|
||||
@ -36,11 +36,11 @@ class FormCreateParams:
|
||||
# UI display flag computed by runtime context.
|
||||
display_in_ui: bool
|
||||
|
||||
# resolved_placeholder_values saves the values for placeholders with
|
||||
# resolved_default_values saves the values for defaults with
|
||||
# type = VARIABLE.
|
||||
#
|
||||
# For type = CONSTANT, the value is not stored inside `resolved_placeholder_values`
|
||||
resolved_placeholder_values: Mapping[str, Any]
|
||||
# For type = CONSTANT, the value is not stored inside `resolved_default_values`
|
||||
resolved_default_values: Mapping[str, Any]
|
||||
form_kind: HumanInputFormKind = HumanInputFormKind.RUNTIME
|
||||
|
||||
# Force creating a console-only recipient for submission in Console.
|
||||
|
||||
@ -80,7 +80,7 @@ def _build_human_input_required_reason(
|
||||
inputs = []
|
||||
actions = []
|
||||
display_in_ui = False
|
||||
resolved_placeholder_values: dict[str, Any] = {}
|
||||
resolved_default_values: dict[str, Any] = {}
|
||||
node_title = "Human Input"
|
||||
form_id = reason_model.form_id
|
||||
node_id = reason_model.node_id
|
||||
@ -98,7 +98,7 @@ def _build_human_input_required_reason(
|
||||
inputs = list(definition.inputs)
|
||||
actions = list(definition.user_actions)
|
||||
display_in_ui = bool(definition.display_in_ui)
|
||||
resolved_placeholder_values = dict(definition.placeholder_values)
|
||||
resolved_default_values = dict(definition.default_values)
|
||||
node_title = definition.node_title or node_title
|
||||
|
||||
form_token = (
|
||||
@ -116,7 +116,7 @@ def _build_human_input_required_reason(
|
||||
node_id=node_id,
|
||||
node_title=node_title,
|
||||
form_token=form_token,
|
||||
resolved_placeholder_values=resolved_placeholder_values,
|
||||
resolved_default_values=resolved_default_values,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -134,7 +134,7 @@ class SQLAlchemyExecutionExtraContentRepository(ExecutionExtraContentRepository)
|
||||
actions=form_definition.user_actions,
|
||||
display_in_ui=display_in_ui,
|
||||
form_token=form_token,
|
||||
resolved_placeholder_values=form_definition.placeholder_values,
|
||||
resolved_default_values=form_definition.default_values,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -807,7 +807,7 @@ class WorkflowService:
|
||||
)
|
||||
|
||||
rendered_content = node._render_form_content_before_submission()
|
||||
resolved_placeholder_values = node._resolve_inputs()
|
||||
resolved_default_values = node._resolve_default_values()
|
||||
node_data = node.node_data
|
||||
human_input_required = HumanInputRequired(
|
||||
form_id=node_id,
|
||||
@ -816,7 +816,7 @@ class WorkflowService:
|
||||
actions=node_data.user_actions,
|
||||
node_id=node_id,
|
||||
node_title=node.title,
|
||||
resolved_placeholder_values=resolved_placeholder_values,
|
||||
resolved_default_values=resolved_default_values,
|
||||
form_token=None,
|
||||
)
|
||||
return human_input_required.model_dump(mode="json")
|
||||
@ -944,14 +944,14 @@ class WorkflowService:
|
||||
variable_pool=variable_pool,
|
||||
)
|
||||
rendered_content = node._render_form_content_before_submission()
|
||||
resolved_placeholder_values = node._resolve_inputs()
|
||||
resolved_default_values = node._resolve_default_values()
|
||||
form_id, recipients = self._create_human_input_delivery_test_form(
|
||||
app_model=app_model,
|
||||
node_id=node_id,
|
||||
node_data=node_data,
|
||||
delivery_method=delivery_method,
|
||||
rendered_content=rendered_content,
|
||||
resolved_placeholder_values=resolved_placeholder_values,
|
||||
resolved_default_values=resolved_default_values,
|
||||
)
|
||||
test_service = HumanInputDeliveryTestService()
|
||||
context = DeliveryTestContext(
|
||||
@ -990,7 +990,7 @@ class WorkflowService:
|
||||
node_data: HumanInputNodeData,
|
||||
delivery_method: DeliveryChannelConfig,
|
||||
rendered_content: str,
|
||||
resolved_placeholder_values: Mapping[str, Any],
|
||||
resolved_default_values: Mapping[str, Any],
|
||||
) -> tuple[str, list[DeliveryTestEmailRecipient]]:
|
||||
repo = HumanInputFormRepositoryImpl(session_factory=db.engine, tenant_id=app_model.tenant_id)
|
||||
params = FormCreateParams(
|
||||
@ -1001,7 +1001,7 @@ class WorkflowService:
|
||||
rendered_content=rendered_content,
|
||||
delivery_methods=[delivery_method],
|
||||
display_in_ui=False,
|
||||
resolved_placeholder_values=resolved_placeholder_values,
|
||||
resolved_default_values=resolved_default_values,
|
||||
form_kind=HumanInputFormKind.DELIVERY_TEST,
|
||||
)
|
||||
form_entity = repo.create_form(params)
|
||||
|
||||
@ -74,7 +74,7 @@ def _build_form_params(delivery_methods: list[EmailDeliveryMethod]) -> FormCreat
|
||||
rendered_content="<p>Approve?</p>",
|
||||
delivery_methods=delivery_methods,
|
||||
display_in_ui=False,
|
||||
resolved_placeholder_values={},
|
||||
resolved_default_values={},
|
||||
)
|
||||
|
||||
|
||||
@ -164,7 +164,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
|
||||
assert len(external_payloads) == 1
|
||||
assert external_payloads[0].email == "external@example.com"
|
||||
|
||||
def test_create_form_persists_placeholder_values(self, db_session_with_containers: Session) -> None:
|
||||
def test_create_form_persists_default_values(self, db_session_with_containers: Session) -> None:
|
||||
engine = db_session_with_containers.get_bind()
|
||||
assert isinstance(engine, Engine)
|
||||
tenant, _ = _create_tenant_with_members(
|
||||
@ -187,7 +187,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
|
||||
rendered_content="<p>Approve?</p>",
|
||||
delivery_methods=[],
|
||||
display_in_ui=False,
|
||||
resolved_placeholder_values=resolved_values,
|
||||
resolved_default_values=resolved_values,
|
||||
)
|
||||
|
||||
form_entity = repository.create_form(params)
|
||||
@ -199,7 +199,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
|
||||
|
||||
assert form_model is not None
|
||||
definition = FormDefinition.model_validate_json(form_model.form_definition)
|
||||
assert definition.placeholder_values == resolved_values
|
||||
assert definition.default_values == resolved_values
|
||||
|
||||
def test_create_form_persists_display_in_ui(self, db_session_with_containers: Session) -> None:
|
||||
engine = db_session_with_containers.get_bind()
|
||||
@ -224,7 +224,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
|
||||
rendered_content="<p>Approve?</p>",
|
||||
delivery_methods=[WebAppDeliveryMethod()],
|
||||
display_in_ui=True,
|
||||
resolved_placeholder_values={},
|
||||
resolved_default_values={},
|
||||
)
|
||||
|
||||
form_entity = repository.create_form(params)
|
||||
|
||||
@ -95,7 +95,7 @@ def _build_form(db_session_with_containers, tenant, account):
|
||||
rendered_content="Rendered",
|
||||
delivery_methods=node_data.delivery_methods,
|
||||
display_in_ui=False,
|
||||
resolved_placeholder_values={},
|
||||
resolved_default_values={},
|
||||
)
|
||||
return repo.create_form(params)
|
||||
|
||||
|
||||
@ -66,8 +66,8 @@ def test_get_form_includes_site(monkeypatch: pytest.MonkeyPatch, app: Flask):
|
||||
return {
|
||||
"form_content": "Raw content",
|
||||
"rendered_content": "Rendered {{#$output.name#}}",
|
||||
"inputs": [{"type": "text", "output_variable_name": "name", "placeholder": None}],
|
||||
"placeholder_values": {"name": "Alice", "age": 30, "meta": {"k": "v"}},
|
||||
"inputs": [{"type": "text", "output_variable_name": "name", "default": None}],
|
||||
"default_values": {"name": "Alice", "age": 30, "meta": {"k": "v"}},
|
||||
"user_actions": [{"id": "approve", "title": "Approve", "button_style": "default"}],
|
||||
}
|
||||
|
||||
@ -132,13 +132,13 @@ def test_get_form_includes_site(monkeypatch: pytest.MonkeyPatch, app: Flask):
|
||||
"site",
|
||||
"form_content",
|
||||
"inputs",
|
||||
"resolved_placeholder_values",
|
||||
"resolved_default_values",
|
||||
"user_actions",
|
||||
"expiration_time",
|
||||
}
|
||||
assert body["form_content"] == "Rendered {{#$output.name#}}"
|
||||
assert body["inputs"] == [{"type": "text", "output_variable_name": "name", "placeholder": None}]
|
||||
assert body["resolved_placeholder_values"] == {"name": "Alice", "age": "30", "meta": '{"k": "v"}'}
|
||||
assert body["inputs"] == [{"type": "text", "output_variable_name": "name", "default": None}]
|
||||
assert body["resolved_default_values"] == {"name": "Alice", "age": "30", "meta": '{"k": "v"}'}
|
||||
assert body["user_actions"] == [{"id": "approve", "title": "Approve", "button_style": "default"}]
|
||||
assert body["expiration_time"] == int(expiration_time.timestamp())
|
||||
assert body["site"] == {
|
||||
@ -184,7 +184,7 @@ def test_get_form_allows_backstage_token(monkeypatch: pytest.MonkeyPatch, app: F
|
||||
"form_content": "Raw content",
|
||||
"rendered_content": "Rendered",
|
||||
"inputs": [],
|
||||
"placeholder_values": {},
|
||||
"default_values": {},
|
||||
"user_actions": [],
|
||||
}
|
||||
|
||||
@ -245,13 +245,13 @@ def test_get_form_allows_backstage_token(monkeypatch: pytest.MonkeyPatch, app: F
|
||||
"site",
|
||||
"form_content",
|
||||
"inputs",
|
||||
"resolved_placeholder_values",
|
||||
"resolved_default_values",
|
||||
"user_actions",
|
||||
"expiration_time",
|
||||
}
|
||||
assert body["form_content"] == "Rendered"
|
||||
assert body["inputs"] == []
|
||||
assert body["resolved_placeholder_values"] == {}
|
||||
assert body["resolved_default_values"] == {}
|
||||
assert body["user_actions"] == []
|
||||
assert body["expiration_time"] == int(expiration_time.timestamp())
|
||||
assert body["site"] == {
|
||||
@ -297,7 +297,7 @@ def test_get_form_raises_forbidden_when_site_missing(monkeypatch: pytest.MonkeyP
|
||||
"form_content": "Raw content",
|
||||
"rendered_content": "Rendered",
|
||||
"inputs": [],
|
||||
"placeholder_values": {},
|
||||
"default_values": {},
|
||||
"user_actions": [],
|
||||
}
|
||||
|
||||
|
||||
@ -125,7 +125,7 @@ def test_handle_workflow_paused_event_persists_human_input_extra_content() -> No
|
||||
node_id="node-1",
|
||||
node_title="Approval",
|
||||
form_token="token-1",
|
||||
resolved_placeholder_values={},
|
||||
resolved_default_values={},
|
||||
)
|
||||
event = QueueWorkflowPausedEvent(reasons=[reason], outputs={}, paused_nodes=["node-1"])
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ def test_queue_workflow_paused_event_to_stream_responses():
|
||||
form_id="form-1",
|
||||
form_content="Rendered",
|
||||
inputs=[
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", placeholder=None),
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", default=None),
|
||||
],
|
||||
actions=[UserAction(id="approve", title="Approve")],
|
||||
display_in_ui=True,
|
||||
|
||||
@ -17,7 +17,7 @@ from core.workflow.nodes.human_input.entities import (
|
||||
EmailRecipients,
|
||||
ExternalRecipient,
|
||||
FormInput,
|
||||
FormInputPlaceholder,
|
||||
FormInputDefault,
|
||||
HumanInputNodeData,
|
||||
MemberRecipient,
|
||||
UserAction,
|
||||
@ -76,37 +76,37 @@ class TestDeliveryMethod:
|
||||
class TestFormInput:
|
||||
"""Test FormInput entity."""
|
||||
|
||||
def test_text_input_with_constant_placeholder(self):
|
||||
"""Test text input with constant placeholder."""
|
||||
placeholder = FormInputPlaceholder(type=PlaceholderType.CONSTANT, value="Enter your response here...")
|
||||
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...")
|
||||
|
||||
form_input = FormInput(
|
||||
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", placeholder=placeholder
|
||||
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default
|
||||
)
|
||||
|
||||
assert form_input.type == FormInputType.TEXT_INPUT
|
||||
assert form_input.output_variable_name == "user_input"
|
||||
assert form_input.placeholder.type == PlaceholderType.CONSTANT
|
||||
assert form_input.placeholder.value == "Enter your response here..."
|
||||
assert form_input.default.type == PlaceholderType.CONSTANT
|
||||
assert form_input.default.value == "Enter your response here..."
|
||||
|
||||
def test_text_input_with_variable_placeholder(self):
|
||||
"""Test text input with variable placeholder."""
|
||||
placeholder = FormInputPlaceholder(type=PlaceholderType.VARIABLE, selector=["node_123", "output_var"])
|
||||
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"])
|
||||
|
||||
form_input = FormInput(
|
||||
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", placeholder=placeholder
|
||||
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default
|
||||
)
|
||||
|
||||
assert form_input.placeholder.type == PlaceholderType.VARIABLE
|
||||
assert form_input.placeholder.selector == ["node_123", "output_var"]
|
||||
assert form_input.default.type == PlaceholderType.VARIABLE
|
||||
assert form_input.default.selector == ["node_123", "output_var"]
|
||||
|
||||
def test_form_input_without_placeholder(self):
|
||||
"""Test form input without placeholder."""
|
||||
def test_form_input_without_default(self):
|
||||
"""Test form input without default value."""
|
||||
form_input = FormInput(type=FormInputType.PARAGRAPH, output_variable_name="description")
|
||||
|
||||
assert form_input.type == FormInputType.PARAGRAPH
|
||||
assert form_input.output_variable_name == "description"
|
||||
assert form_input.placeholder is None
|
||||
assert form_input.default is None
|
||||
|
||||
|
||||
class TestUserAction:
|
||||
@ -163,7 +163,7 @@ class TestHumanInputNodeData:
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
output_variable_name="content",
|
||||
placeholder=FormInputPlaceholder(type=PlaceholderType.CONSTANT, value="Enter content..."),
|
||||
default=FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter content..."),
|
||||
)
|
||||
]
|
||||
|
||||
@ -209,7 +209,7 @@ class TestHumanInputNodeData:
|
||||
assert node_data.timeout == 1
|
||||
assert node_data.timeout_unit == TimeoutUnit.DAY
|
||||
|
||||
def test_node_data_default_values(self):
|
||||
def test_node_data_defaults(self):
|
||||
"""Test node data with default values."""
|
||||
node_data = HumanInputNodeData(title="Test Node")
|
||||
|
||||
@ -302,9 +302,9 @@ class TestRecipients:
|
||||
|
||||
|
||||
class TestHumanInputNodeVariableResolution:
|
||||
"""Tests for resolving variable-based placeholders in HumanInputNode."""
|
||||
"""Tests for resolving variable-based defaults in HumanInputNode."""
|
||||
|
||||
def test_resolves_variable_placeholders(self):
|
||||
def test_resolves_variable_defaults(self):
|
||||
variable_pool = VariablePool(
|
||||
system_variables=SystemVariable(
|
||||
user_id="user",
|
||||
@ -335,12 +335,12 @@ class TestHumanInputNodeVariableResolution:
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
output_variable_name="user_name",
|
||||
placeholder=FormInputPlaceholder(type=PlaceholderType.VARIABLE, selector=["start", "name"]),
|
||||
default=FormInputDefault(type=PlaceholderType.VARIABLE, selector=["start", "name"]),
|
||||
),
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
output_variable_name="user_email",
|
||||
placeholder=FormInputPlaceholder(type=PlaceholderType.CONSTANT, value="foo@example.com"),
|
||||
default=FormInputDefault(type=PlaceholderType.CONSTANT, value="foo@example.com"),
|
||||
),
|
||||
],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
@ -370,10 +370,10 @@ class TestHumanInputNodeVariableResolution:
|
||||
|
||||
assert isinstance(pause_event, PauseRequestedEvent)
|
||||
expected_values = {"user_name": "Jane Doe"}
|
||||
assert pause_event.reason.resolved_placeholder_values == expected_values
|
||||
assert pause_event.reason.resolved_default_values == expected_values
|
||||
|
||||
params = mock_repo.create_form.call_args.args[0]
|
||||
assert params.resolved_placeholder_values == expected_values
|
||||
assert params.resolved_default_values == expected_values
|
||||
|
||||
def test_debugger_falls_back_to_recipient_token_when_webapp_disabled(self):
|
||||
variable_pool = VariablePool(
|
||||
|
||||
@ -53,7 +53,7 @@ def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name#
|
||||
{
|
||||
"type": "text_input",
|
||||
"output_variable_name": "name",
|
||||
"placeholder": {"type": "constant", "value": ""},
|
||||
"default": {"type": "constant", "value": ""},
|
||||
}
|
||||
],
|
||||
"user_actions": [
|
||||
|
||||
@ -50,7 +50,7 @@ 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", placeholder=None)],
|
||||
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"timeout": 1,
|
||||
"timeout_unit": TimeoutUnit.HOUR,
|
||||
@ -305,7 +305,7 @@ class TestFormValidation:
|
||||
"app_id": "app-def",
|
||||
"form_content": "Test form",
|
||||
"inputs": [
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", placeholder=None)
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", default=None)
|
||||
],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"timeout": 1,
|
||||
|
||||
@ -31,7 +31,7 @@ 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", placeholder=None)],
|
||||
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"timeout": 2,
|
||||
"timeout_unit": TimeoutUnit.HOUR,
|
||||
|
||||
@ -379,7 +379,7 @@ class TestBuildHumanInputRequiredReason:
|
||||
rendered_content="rendered",
|
||||
timeout=1,
|
||||
timeout_unit=TimeoutUnit.HOUR,
|
||||
placeholder_values={"name": "Alice"},
|
||||
default_values={"name": "Alice"},
|
||||
node_title="Ask Name",
|
||||
display_in_ui=True,
|
||||
)
|
||||
|
||||
@ -127,7 +127,7 @@ def test_get_by_message_ids_returns_unsubmitted_form_definition() -> None:
|
||||
rendered_content="rendered",
|
||||
timeout=1,
|
||||
timeout_unit=TimeoutUnit.HOUR,
|
||||
placeholder_values={"name": "John"},
|
||||
default_values={"name": "John"},
|
||||
node_title="Approval",
|
||||
display_in_ui=True,
|
||||
)
|
||||
@ -177,4 +177,4 @@ def test_get_by_message_ids_returns_unsubmitted_form_definition() -> None:
|
||||
assert form_definition.form_content == "Rendered block"
|
||||
assert form_definition.display_in_ui is True
|
||||
assert form_definition.form_token == "token-1"
|
||||
assert form_definition.resolved_placeholder_values == {"name": "John"}
|
||||
assert form_definition.resolved_default_values == {"name": "John"}
|
||||
|
||||
@ -24,8 +24,6 @@ from services.workflow_event_snapshot_service import (
|
||||
BufferState,
|
||||
MessageContext,
|
||||
_build_snapshot_events,
|
||||
_collect_snapshot_keys,
|
||||
_filter_buffered_events,
|
||||
_resolve_task_id,
|
||||
)
|
||||
|
||||
@ -221,36 +219,3 @@ def test_resolve_task_id_priority(context_task_id, buffered_task_id, expected) -
|
||||
buffer_state.task_id_ready.set()
|
||||
task_id = _resolve_task_id(resumption_context, buffer_state, "run-1", wait_timeout=0.0)
|
||||
assert task_id == expected
|
||||
|
||||
|
||||
def test_filter_buffered_events_deduplicates_snapshot_nodes() -> None:
|
||||
workflow_run = _build_workflow_run(WorkflowExecutionStatus.RUNNING)
|
||||
snapshot = _build_snapshot(WorkflowNodeExecutionStatus.SUCCEEDED)
|
||||
events = _build_snapshot_events(
|
||||
workflow_run=workflow_run,
|
||||
node_snapshots=[snapshot],
|
||||
task_id="task-1",
|
||||
message_context=None,
|
||||
pause_entity=None,
|
||||
resumption_context=None,
|
||||
)
|
||||
snapshot_keys = _collect_snapshot_keys(events)
|
||||
|
||||
buffered_events = [
|
||||
{
|
||||
"event": "node_started",
|
||||
"data": {"id": "exec-1"},
|
||||
},
|
||||
{
|
||||
"event": "node_finished",
|
||||
"data": {"id": "exec-2"},
|
||||
},
|
||||
]
|
||||
|
||||
filtered = list(_filter_buffered_events(buffered_events, snapshot_keys))
|
||||
assert filtered == [
|
||||
{
|
||||
"event": "node_finished",
|
||||
"data": {"id": "exec-2"},
|
||||
}
|
||||
]
|
||||
|
||||
@ -95,7 +95,7 @@ def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.Mon
|
||||
service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined]
|
||||
node_stub = MagicMock()
|
||||
node_stub._render_form_content_before_submission.return_value = "rendered"
|
||||
node_stub._resolve_inputs.return_value = {}
|
||||
node_stub._resolve_default_values.return_value = {}
|
||||
service._build_human_input_node = MagicMock(return_value=node_stub) # type: ignore[attr-defined]
|
||||
service._create_human_input_delivery_test_form = MagicMock( # type: ignore[attr-defined]
|
||||
return_value=("form-1", {})
|
||||
@ -134,7 +134,7 @@ def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytes
|
||||
service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined]
|
||||
node_stub = MagicMock()
|
||||
node_stub._render_form_content_before_submission.return_value = "rendered"
|
||||
node_stub._resolve_inputs.return_value = {}
|
||||
node_stub._resolve_default_values.return_value = {}
|
||||
service._build_human_input_node = MagicMock(return_value=node_stub) # type: ignore[attr-defined]
|
||||
service._create_human_input_delivery_test_form = MagicMock( # type: ignore[attr-defined]
|
||||
return_value=("form-1", {})
|
||||
|
||||
Reference in New Issue
Block a user