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.1.67"
version = "0.1.68"
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 @@ -305,6 +305,34 @@ async def _assign_task_spec(
}
]
}
elif task_recipient.type == TaskRecipientType.WORKLOAD:
Comment thread
kunaluipath marked this conversation as resolved.
# This branch covers BOTH agent-side Workload criteria (single
# group, distributed by workload) AND agent-side CustomAssignees
# criteria (explicit email list — already resolved into
# `task_recipient.values` upstream). Both submit to the Action
# Center API as a "Workload" assignment; the difference is whether
# `values` carries one group or N emails.
request_spec.json = {
"taskAssignments": [
{
"taskId": task_key,
"assignmentCriteria": "Workload",
"assigneeNamesOrEmails": task_recipient.values
or [recipient_value],
}
]
}
elif task_recipient.type == TaskRecipientType.ROUND_ROBIN:
request_spec.json = {
"taskAssignments": [
{
"taskId": task_key,
"assignmentCriteria": "RoundRobin",
"assigneeNamesOrEmails": task_recipient.values
or [recipient_value],
}
]
}
else:
request_spec.json = {
"taskAssignments": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,35 @@ class TaskRecipientType(str, enum.Enum):
GROUP_ID = "GroupId"
EMAIL = "UserEmail"
GROUP_NAME = "GroupName"
WORKLOAD = "Workload"
ROUND_ROBIN = "RoundRobin"
Comment thread
kunaluipath marked this conversation as resolved.


class TaskRecipient(BaseModel):
"""Model representing a task recipient."""
"""Model representing a task recipient.

`value` is the single identifier (group name, group id, user id, email, …).
`values` is the multi-assignee form used by Workload-with-custom-emails
assignments; when set it takes precedence over `value` for the
`assigneeNamesOrEmails` payload.

Note: there is no CustomAssignees member here on purpose. The agent-side
CustomAssignees criteria (AgentEscalationRecipientType.CUSTOM_ASSIGNEES,
type 11) is resolved to a Workload assignment with the explicit email list
in `values` before reaching this layer, so the Action Center
AssignTasks API only ever sees the existing literal types.
"""

type: Literal[
TaskRecipientType.USER_ID,
TaskRecipientType.GROUP_ID,
TaskRecipientType.EMAIL,
TaskRecipientType.GROUP_NAME,
TaskRecipientType.WORKLOAD,
TaskRecipientType.ROUND_ROBIN,
] = Field(..., alias="type")
value: str = Field(..., alias="value")
values: Optional[List[str]] = Field(default=None, alias="values")
display_name: Optional[str] = Field(default=None, alias="displayName")


Expand Down
162 changes: 162 additions & 0 deletions packages/uipath-platform/tests/services/test_actions_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from uipath.platform import UiPathApiConfig, UiPathExecutionContext
from uipath.platform.action_center import Task
from uipath.platform.action_center._tasks_service import TasksService
from uipath.platform.action_center.tasks import TaskRecipient, TaskRecipientType
from uipath.platform.common.constants import HEADER_USER_AGENT


Expand Down Expand Up @@ -186,6 +187,167 @@ def test_create_with_assignee(
assert action.title == "Test Action"


def _mock_app_lookup_and_create(
httpx_mock: HTTPXMock,
base_url: str,
org: str,
tenant: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Common httpx mock setup for app lookup + task creation + assign."""
monkeypatch.setenv("UIPATH_TENANT_ID", "test-tenant-id")
httpx_mock.add_response(
url=f"{base_url}{org}/apps_/default/api/v1/default/deployed-action-apps-schemas?search=test-app&filterByDeploymentTitle=true",
status_code=200,
json={
"deployed": [
{
"systemName": "test-app",
"deploymentTitle": "test-app",
"actionSchema": {
"key": "test-key",
"inputs": [],
"outputs": [],
"inOuts": [],
"outcomes": [],
},
"deploymentFolder": {
"fullyQualifiedName": "test-folder-path",
"key": "test-folder-key",
},
}
]
},
)
httpx_mock.add_response(
url=f"{base_url}{org}{tenant}/orchestrator_/tasks/AppTasks/CreateAppTask",
status_code=200,
json={"id": 1, "title": "Test Action"},
)
httpx_mock.add_response(
url=f"{base_url}{org}{tenant}/orchestrator_/odata/Tasks/UiPath.Server.Configuration.OData.AssignTasks",
status_code=200,
json={},
)


def _assign_request_payload(httpx_mock: HTTPXMock) -> dict[str, Any]:
"""Return the parsed JSON body of the last AssignTasks request captured by the mock."""
assign_request = next(
req
for req in reversed(httpx_mock.get_requests())
if "AssignTasks" in str(req.url)
)
return json.loads(assign_request.content)


class TestAssignTaskSpec:
"""Tests for the task-assignment payload built by `_assign_task_spec`."""

def test_assign_workload_recipient_uses_workload_criteria_with_group(
self,
httpx_mock: HTTPXMock,
service: TasksService,
base_url: str,
org: str,
tenant: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_mock_app_lookup_and_create(httpx_mock, base_url, org, tenant, monkeypatch)

service.create(
title="Test Action",
app_name="test-app",
data={"x": 1},
recipient=TaskRecipient(
type=TaskRecipientType.WORKLOAD,
value="Support Team",
displayName="Support Team",
),
)

payload = _assign_request_payload(httpx_mock)
assert payload == {
"taskAssignments": [
{
"taskId": 1,
"assignmentCriteria": "Workload",
"assigneeNamesOrEmails": ["Support Team"],
}
]
}

def test_assign_round_robin_recipient_uses_round_robin_criteria(
self,
httpx_mock: HTTPXMock,
service: TasksService,
base_url: str,
org: str,
tenant: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_mock_app_lookup_and_create(httpx_mock, base_url, org, tenant, monkeypatch)

service.create(
title="Test Action",
app_name="test-app",
data={"x": 1},
recipient=TaskRecipient(
type=TaskRecipientType.ROUND_ROBIN,
value="Support Team",
displayName="Support Team",
),
)

payload = _assign_request_payload(httpx_mock)
assert payload == {
"taskAssignments": [
{
"taskId": 1,
"assignmentCriteria": "RoundRobin",
"assigneeNamesOrEmails": ["Support Team"],
}
]
}

def test_assign_workload_with_multiple_emails_uses_values_list(
self,
httpx_mock: HTTPXMock,
service: TasksService,
base_url: str,
org: str,
tenant: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Custom-assignees path: Workload criteria with a list of emails."""
_mock_app_lookup_and_create(httpx_mock, base_url, org, tenant, monkeypatch)

service.create(
title="Test Action",
app_name="test-app",
data={"x": 1},
recipient=TaskRecipient(
type=TaskRecipientType.WORKLOAD,
value="alice@example.com",
values=["alice@example.com", "bob@example.com"],
),
)

payload = _assign_request_payload(httpx_mock)
assert payload == {
"taskAssignments": [
{
"taskId": 1,
"assignmentCriteria": "Workload",
"assigneeNamesOrEmails": [
"alice@example.com",
"bob@example.com",
],
}
]
}


def _make_deployed_app(
name: str,
folder_path: str,
Expand Down
2 changes: 1 addition & 1 deletion 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/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "uipath"
version = "2.11.0"
version = "2.11.1"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
dependencies = [
"uipath-core>=0.5.17, <0.6.0",
"uipath-runtime>=0.11.0, <0.12.0",
"uipath-platform>=0.1.67, <0.2.0",
"uipath-platform>=0.1.68, <0.2.0",
"click>=8.3.1",
"httpx>=0.28.1",
"pyjwt>=2.10.1",
Expand Down
Loading
Loading