Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/uipath-platform/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-platform"
version = "0.2.25"
version = "0.2.26"
description = "HTTP client library for programmatic access to UiPath Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def _create_spec(
labels: Optional[List[str]] = None,
is_actionable_message_enabled: Optional[bool] = None,
actionable_message_metadata: Optional[Dict[str, Any]] = None,
task_source_metadata: Optional[Dict[str, Any]] = None,
source_name: str = "Agent",
is_debug: bool = False,
) -> RequestSpec:
Expand Down Expand Up @@ -147,7 +148,12 @@ def _create_spec(
_apply_priority_labels_and_actionable_toggle(
json_payload, priority, labels, is_actionable_message_enabled
)
_apply_task_source(json_payload, source_name, is_debug=is_debug)
_apply_task_source(
json_payload,
source_name,
is_debug=is_debug,
custom_metadata=task_source_metadata,
)

return RequestSpec(
method="POST",
Expand Down Expand Up @@ -185,12 +191,20 @@ def _apply_priority_labels_and_actionable_toggle(


def _apply_task_source(
payload: Dict[str, Any], source_name: str, is_debug: bool = False
payload: Dict[str, Any],
source_name: str,
is_debug: bool = False,
custom_metadata: Optional[Dict[str, Any]] = None,
) -> None:
"""Populate ``payload["taskSource"]`` when UiPathConfig has project_id + trace_id.

Shared between AppTask and QuickForm spec builders — the taskSource block is
identical for both task types.

``custom_metadata`` is merged into ``taskSourceMetadata`` on top of the built-in
InstanceId/FolderKey/JobKey/ProcessKey keys, so a caller-supplied key (e.g. a
conversational-agent id a HITL task should carry) wins on collision rather than
being silently dropped.
"""
project_id = UiPathConfig.project_id
trace_id = UiPathConfig.trace_id
Expand All @@ -204,6 +218,7 @@ def _apply_task_source(
"FolderKey": UiPathConfig.folder_key,
"JobKey": UiPathConfig.job_key,
"ProcessKey": UiPathConfig.process_uuid,
**(custom_metadata or {}),
},
"jobId": UiPathConfig.job_key,
}
Expand Down Expand Up @@ -488,6 +503,7 @@ async def create_async(
labels: Optional[List[str]] = None,
is_actionable_message_enabled: Optional[bool] = None,
actionable_message_metadata: Optional[Dict[str, Any]] = None,
task_source_metadata: Optional[Dict[str, Any]] = None,
source_name: str = "Agent",
) -> Task:
"""Creates a new action asynchronously.
Expand All @@ -507,6 +523,12 @@ async def create_async(
labels: Optional list of labels for the task
is_actionable_message_enabled: Optional boolean indicating whether actionable notifications are enabled for this task
actionable_message_metadata: Optional metadata for the action
task_source_metadata: Optional extra keys merged into taskSource.taskSourceMetadata,
on top of the built-in InstanceId/FolderKey/JobKey/ProcessKey (e.g. a
conversational-agent id a HITL task should carry downstream). Takes
effect only when taskSource itself is populated, which needs both
UiPathConfig.project_id and UiPathConfig.trace_id set; otherwise this
(like the rest of taskSource) is silently dropped.
source_name: The name of the source that created the task. Defaults to 'Agent'.

Returns:
Expand Down Expand Up @@ -540,6 +562,7 @@ async def create_async(
labels=labels,
is_actionable_message_enabled=is_actionable_message_enabled,
actionable_message_metadata=actionable_message_metadata,
task_source_metadata=task_source_metadata,
source_name=source_name,
is_debug=is_debug,
)
Expand Down Expand Up @@ -582,6 +605,7 @@ def create(
labels: Optional[List[str]] = None,
is_actionable_message_enabled: Optional[bool] = None,
actionable_message_metadata: Optional[Dict[str, Any]] = None,
task_source_metadata: Optional[Dict[str, Any]] = None,
source_name: str = "Agent",
) -> Task:
"""Creates a new task synchronously.
Expand All @@ -601,6 +625,12 @@ def create(
labels: Optional list of labels for the task
is_actionable_message_enabled: Optional boolean indicating whether actionable notifications are enabled for this task
actionable_message_metadata: Optional metadata for the action
task_source_metadata: Optional extra keys merged into taskSource.taskSourceMetadata,
on top of the built-in InstanceId/FolderKey/JobKey/ProcessKey (e.g. a
conversational-agent id a HITL task should carry downstream). Takes
effect only when taskSource itself is populated, which needs both
UiPathConfig.project_id and UiPathConfig.trace_id set; otherwise this
(like the rest of taskSource) is silently dropped.
source_name: The name of the source that created the task. Defaults to 'Agent'.

Returns:
Expand Down Expand Up @@ -634,6 +664,7 @@ def create(
labels=labels,
is_actionable_message_enabled=is_actionable_message_enabled,
actionable_message_metadata=actionable_message_metadata,
task_source_metadata=task_source_metadata,
source_name=source_name,
is_debug=is_debug,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class CreateTask(BaseModel):
labels: list[str] | None = None
is_actionable_message_enabled: bool | None = None
actionable_message_metadata: dict[str, Any] | None = None
task_source_metadata: dict[str, Any] | None = None
source_name: str = "Agent"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ async def _handle_task_trigger(
labels=value.labels,
is_actionable_message_enabled=value.is_actionable_message_enabled,
actionable_message_metadata=value.actionable_message_metadata,
task_source_metadata=value.task_source_metadata,
source_name=value.source_name,
)
if not action:
Expand Down
85 changes: 85 additions & 0 deletions packages/uipath-platform/tests/services/test_actions_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,3 +1024,88 @@ def test_create_skips_jit_when_not_a_studio_project(

assert _requested_app_schemas(httpx_mock)
assert _posted_body(httpx_mock, create_task_url)["folderPath"] == "Shared/Apps"


def test_create_merges_task_source_metadata(
httpx_mock: HTTPXMock,
service: TasksService,
create_task_url: str,
jit_debug_env: None,
) -> None:
_mock_create_task(httpx_mock, create_task_url)

service.create(
title="Test Action",
app_key="test-app-key",
data={"test": "data"},
task_source_metadata={"ConversationalAgentId": "agent-1"},
)

task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][
"taskSourceMetadata"
]
assert task_source_metadata["ConversationalAgentId"] == "agent-1"
# The built-in correlation keys are still populated alongside the extra one.
assert task_source_metadata["InstanceId"] == "trace-1"

Comment thread
dushyant-uipath marked this conversation as resolved.

async def test_create_async_merges_task_source_metadata(
httpx_mock: HTTPXMock,
service: TasksService,
create_task_url: str,
jit_debug_env: None,
) -> None:
_mock_create_task(httpx_mock, create_task_url)

await service.create_async(
title="Test Action",
app_key="test-app-key",
data={"test": "data"},
task_source_metadata={"ConversationalAgentId": "agent-1"},
)

task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][
"taskSourceMetadata"
]
assert task_source_metadata["ConversationalAgentId"] == "agent-1"
assert task_source_metadata["InstanceId"] == "trace-1"


def test_create_omits_extra_task_source_metadata_when_unset(
httpx_mock: HTTPXMock,
service: TasksService,
create_task_url: str,
jit_debug_env: None,
) -> None:
_mock_create_task(httpx_mock, create_task_url)

service.create(title="Test Action", app_key="test-app-key", data={"test": "data"})

task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][
"taskSourceMetadata"
]
assert "ConversationalAgentId" not in task_source_metadata
assert task_source_metadata["InstanceId"] == "trace-1"


def test_create_task_source_metadata_overrides_a_built_in_key_on_collision(
httpx_mock: HTTPXMock,
service: TasksService,
create_task_url: str,
jit_debug_env: None,
) -> None:
_mock_create_task(httpx_mock, create_task_url)

service.create(
title="Test Action",
app_key="test-app-key",
data={"test": "data"},
task_source_metadata={"InstanceId": "caller-supplied-instance-id"},
)

task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][
"taskSourceMetadata"
]
# jit_debug_env sets UIPATH_TRACE_ID=trace-1, which _apply_task_source uses to
# build the built-in InstanceId; the caller-supplied key wins on collision.
assert task_source_metadata["InstanceId"] == "caller-supplied-instance-id"
1 change: 1 addition & 0 deletions packages/uipath-platform/tests/services/test_hitl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,7 @@ async def test_create_resume_trigger_create_task(
labels=None,
is_actionable_message_enabled=None,
actionable_message_metadata=None,
task_source_metadata=None,
source_name="Agent",
)

Expand Down
4 changes: 2 additions & 2 deletions packages/uipath-platform/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/uipath/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@ sonar.cpd.exclusions=**/__init__.py
# Python-idiomatic without breaking interop. It is isolated in cli_server_ipc.py
# so this suppression of method-naming (S100) and field-naming (S116) touches
# that file only.
sonar.issue.ignore.multicriteria=ipc1,ipc2
sonar.issue.ignore.multicriteria=ipc1,ipc2,tasks1
sonar.issue.ignore.multicriteria.ipc1.ruleKey=python:S100
sonar.issue.ignore.multicriteria.ipc1.resourceKey=**/cli_server_ipc.py
sonar.issue.ignore.multicriteria.ipc2.ruleKey=python:S116
sonar.issue.ignore.multicriteria.ipc2.resourceKey=**/cli_server_ipc.py

# _create_spec/create/create_async in _tasks_service.py mirror CreateAppTask's
# own many independent optional fields one parameter at a time (priority, labels,
# actionable_message_metadata, task_source_metadata, ...); each was already at
# S107's 13-parameter limit before task_source_metadata added a 14th. Collapsing
# them into an options object is a real fix, but a separate, larger refactor
# across this whole file's public API, not something to fold into a one-field
# addition.
sonar.issue.ignore.multicriteria.tasks1.ruleKey=python:S107
sonar.issue.ignore.multicriteria.tasks1.resourceKey=**/_tasks_service.py

sonar.sourceEncoding=UTF-8
Loading