Skip to content

Commit 81bf325

Browse files
committed
fix(eval): propagate projectFilesSource on POST/PUT/GET so rows match UI filter
projectFilesSource is the Local/Cloud discriminator the UI filters by (`?projectFilesSource=1` for local-workspace runs), not a project ID. Until now the SDK didn't send it on the eval-run / eval-set-run callbacks, so the backend created every worker-spawned row defaulting to Cloud and the UI's local-runs filter never matched them. Reads UIPATH_PROJECT_FILES_SOURCE (string Local/Cloud) once at reporter construction and maps it to the backend's enum integer (Local=1, Cloud=0). Adds it to: POST eval set run payload POST eval run payload PUT eval run (legacy) payload PUT eval run (coded) payload PUT eval set run payload GET eval runs (resume lookup) query parameter When the env var is unset (cloud-project case or older backend that doesn't emit it) the field is omitted from payloads/params, preserving existing behaviour. 14 new tests in TestProjectFilesSourcePropagation covering the parametrized env-var resolution (Local/Cloud/numeric/garbage), each of the six request shapes carries the right value, and absence-omits-field. 1840 reporter tests pass. Refs: AE-1396
1 parent 5027a3f commit 81bf325

2 files changed

Lines changed: 163 additions & 1 deletion

File tree

packages/uipath/src/uipath/_cli/_evals/_progress_reporter.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ def __init__(self):
108108
"Cannot report data to StudioWeb. Please set UIPATH_PROJECT_ID."
109109
)
110110

111+
# Map UIPATH_PROJECT_FILES_SOURCE (Local/Cloud) to the backend's
112+
# ProjectFilesSource enum integer. Without this every row the worker
113+
# creates lands as Cloud, and the UI's `?projectFilesSource=1` filter
114+
# never matches local-workspace runs.
115+
self._project_files_source = self._resolve_project_files_source()
116+
111117
self.eval_set_ids: dict[str, str] = {} # Track eval_set_id per execution
112118
self.eval_set_run_ids: dict[str, str] = {}
113119
self.evaluators: dict[str, Any] = {}
@@ -1090,6 +1096,29 @@ def _collect_coded_results(
10901096
evaluator_runs.append(evaluator_run)
10911097
return evaluator_runs, evaluator_scores_list
10921098

1099+
@staticmethod
1100+
def _resolve_project_files_source() -> int | None:
1101+
raw = os.getenv("UIPATH_PROJECT_FILES_SOURCE")
1102+
if not raw:
1103+
return None
1104+
normalized = raw.strip().lower()
1105+
if normalized == "local":
1106+
return 1
1107+
if normalized == "cloud":
1108+
return 0
1109+
try:
1110+
return int(normalized)
1111+
except ValueError:
1112+
logger.warning(
1113+
f"Unrecognized UIPATH_PROJECT_FILES_SOURCE value: {raw!r}; ignoring."
1114+
)
1115+
return None
1116+
1117+
def _project_files_source_field(self) -> dict[str, int]:
1118+
if self._project_files_source is None:
1119+
return {}
1120+
return {"projectFilesSource": self._project_files_source}
1121+
10931122
def _update_eval_run_spec(
10941123
self,
10951124
assertion_runs: list[dict[str, Any]],
@@ -1116,6 +1145,7 @@ def _update_eval_run_spec(
11161145
},
11171146
"completionMetrics": {"duration": int(execution_time * 1000)},
11181147
"assertionRuns": assertion_runs,
1148+
**self._project_files_source_field(),
11191149
}
11201150

11211151
# Legacy backend expects payload wrapped in "request" field
@@ -1167,6 +1197,7 @@ def _update_coded_eval_run_spec(
11671197
},
11681198
"completionMetrics": {"duration": int(execution_time * 1000)},
11691199
"evaluatorRuns": evaluator_runs,
1200+
**self._project_files_source_field(),
11701201
}
11711202

11721203
# Log the payload for debugging coded eval run updates
@@ -1236,6 +1267,7 @@ def _create_eval_run_spec(
12361267
"evalSnapshot": eval_snapshot,
12371268
# Backend expects integer status
12381269
"status": EvaluationStatus.IN_PROGRESS.value,
1270+
**self._project_files_source_field(),
12391271
}
12401272

12411273
# Legacy backend expects payload wrapped in "request" field
@@ -1292,6 +1324,7 @@ def _create_eval_set_run_spec(
12921324
"numberOfEvalsExecuted": no_of_evals,
12931325
# Source is required by the backend (0 = coded SDK)
12941326
"source": 0,
1327+
**self._project_files_source_field(),
12951328
}
12961329

12971330
# Both coded and legacy send payload directly at root level
@@ -1354,6 +1387,7 @@ def _update_eval_set_run_spec(
13541387
# Backend expects integer status
13551388
"status": status.value,
13561389
"evaluatorScores": evaluator_scores_list,
1390+
**self._project_files_source_field(),
13571391
}
13581392

13591393
# Legacy backend expects payload wrapped in "request" field
@@ -1421,10 +1455,14 @@ def _get_eval_runs_spec(
14211455
f"eval_set_run_id={eval_set_run_id}, evaluation_id={evaluation_id}, coded={is_coded}"
14221456
)
14231457

1458+
# The backend's listing endpoint filters by projectFilesSource +
1459+
# cloudUserId so the UI only shows the caller's local rows. Mirror
1460+
# that here so resume lookups match the row written by the same
1461+
# worker session.
14241462
return RequestSpec(
14251463
method="GET",
14261464
endpoint=Endpoint(endpoint_path),
1427-
params={}, # No query params needed - evalSetRunId is in the path
1465+
params=self._project_files_source_field(),
14281466
headers=self._tenant_header(),
14291467
)
14301468

packages/uipath/tests/cli/eval/test_progress_reporter.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,3 +1007,127 @@ def test_falls_back_to_project_id_when_agent_id_unset(self, monkeypatch):
10071007
is_coded=False,
10081008
)
10091009
assert "/agents/cloud-project-id/" in spec.endpoint
1010+
1011+
1012+
class TestProjectFilesSourcePropagation:
1013+
"""Reporter must propagate UIPATH_PROJECT_FILES_SOURCE to backend rows.
1014+
1015+
Backend filters listings by `projectFilesSource` (Local=1, Cloud=0). Without
1016+
the SDK setting it on POST/PUT payloads and GET query params, every row
1017+
lands as Cloud and the UI's `?projectFilesSource=1` filter never matches
1018+
local-workspace runs.
1019+
"""
1020+
1021+
def _make_reporter(self, monkeypatch, project_files_source):
1022+
monkeypatch.setenv("UIPATH_URL", "https://test.uipath.com")
1023+
monkeypatch.setenv("UIPATH_ACCESS_TOKEN", "test-token")
1024+
monkeypatch.setenv("UIPATH_TENANT_ID", "test-tenant-id")
1025+
monkeypatch.setenv("UIPATH_PROJECT_ID", "test-project-id")
1026+
if project_files_source is not None:
1027+
monkeypatch.setenv("UIPATH_PROJECT_FILES_SOURCE", project_files_source)
1028+
else:
1029+
monkeypatch.delenv("UIPATH_PROJECT_FILES_SOURCE", raising=False)
1030+
return StudioWebProgressReporter()
1031+
1032+
@pytest.mark.parametrize(
1033+
"raw,expected",
1034+
[("Local", 1), ("local", 1), ("Cloud", 0), ("cloud", 0), ("1", 1), ("0", 0)],
1035+
)
1036+
def test_resolves_env_var_to_int(self, monkeypatch, raw, expected):
1037+
reporter = self._make_reporter(monkeypatch, raw)
1038+
assert reporter._project_files_source == expected
1039+
1040+
def test_returns_none_when_unset_or_garbage(self, monkeypatch):
1041+
reporter = self._make_reporter(monkeypatch, None)
1042+
assert reporter._project_files_source is None
1043+
reporter2 = self._make_reporter(monkeypatch, "Banana")
1044+
assert reporter2._project_files_source is None
1045+
1046+
def test_post_eval_set_run_payload_carries_source(self, monkeypatch):
1047+
from uipath._cli._evals._progress_reporter import StudioWebAgentSnapshot
1048+
1049+
reporter = self._make_reporter(monkeypatch, "Local")
1050+
spec = reporter._create_eval_set_run_spec(
1051+
eval_set_id="test-eval-set",
1052+
agent_snapshot=StudioWebAgentSnapshot(
1053+
input_schema={"type": "object"}, output_schema={"type": "object"}
1054+
),
1055+
no_of_evals=1,
1056+
is_coded=False,
1057+
)
1058+
assert spec.json["projectFilesSource"] == 1
1059+
1060+
def test_post_eval_run_payload_carries_source(self, monkeypatch):
1061+
from uipath.eval.models.evaluation_set import EvaluationItem
1062+
1063+
reporter = self._make_reporter(monkeypatch, "Local")
1064+
item = EvaluationItem(
1065+
id="11111111-1111-1111-1111-111111111111",
1066+
name="t",
1067+
inputs={},
1068+
evaluation_criterias={},
1069+
)
1070+
spec = reporter._create_eval_run_spec(
1071+
eval_item=item, eval_set_run_id="run-1", is_coded=False
1072+
)
1073+
assert spec.json["projectFilesSource"] == 1
1074+
1075+
def test_put_eval_run_payload_carries_source(self, monkeypatch):
1076+
reporter = self._make_reporter(monkeypatch, "Local")
1077+
spec = reporter._update_eval_run_spec(
1078+
assertion_runs=[],
1079+
evaluator_scores=[],
1080+
eval_run_id="run-1",
1081+
actual_output={},
1082+
execution_time=1.0,
1083+
success=True,
1084+
is_coded=False,
1085+
)
1086+
assert spec.json["projectFilesSource"] == 1
1087+
1088+
def test_put_coded_eval_run_payload_carries_source(self, monkeypatch):
1089+
reporter = self._make_reporter(monkeypatch, "Local")
1090+
spec = reporter._update_coded_eval_run_spec(
1091+
evaluator_runs=[],
1092+
evaluator_scores=[],
1093+
eval_run_id="run-1",
1094+
actual_output={},
1095+
execution_time=1.0,
1096+
success=True,
1097+
is_coded=True,
1098+
)
1099+
assert spec.json["projectFilesSource"] == 1
1100+
1101+
def test_put_eval_set_run_payload_carries_source(self, monkeypatch):
1102+
reporter = self._make_reporter(monkeypatch, "Local")
1103+
spec = reporter._update_eval_set_run_spec(
1104+
eval_set_run_id="set-run-1",
1105+
evaluator_scores={},
1106+
is_coded=False,
1107+
success=True,
1108+
)
1109+
assert spec.json["projectFilesSource"] == 1
1110+
1111+
def test_get_eval_runs_query_carries_source(self, monkeypatch):
1112+
reporter = self._make_reporter(monkeypatch, "Local")
1113+
spec = reporter._get_eval_runs_spec(
1114+
eval_set_id="set-1",
1115+
eval_set_run_id="run-1",
1116+
evaluation_id=None,
1117+
is_coded=False,
1118+
)
1119+
assert spec.params == {"projectFilesSource": 1}
1120+
1121+
def test_unset_source_omits_field_from_payloads(self, monkeypatch):
1122+
from uipath._cli._evals._progress_reporter import StudioWebAgentSnapshot
1123+
1124+
reporter = self._make_reporter(monkeypatch, None)
1125+
spec = reporter._create_eval_set_run_spec(
1126+
eval_set_id="test-eval-set",
1127+
agent_snapshot=StudioWebAgentSnapshot(
1128+
input_schema={"type": "object"}, output_schema={"type": "object"}
1129+
),
1130+
no_of_evals=1,
1131+
is_coded=False,
1132+
)
1133+
assert "projectFilesSource" not in spec.json

0 commit comments

Comments
 (0)