diff --git a/docs/hub/code.md b/docs/hub/code.md index e24df63..101b14d 100644 --- a/docs/hub/code.md +++ b/docs/hub/code.md @@ -59,7 +59,7 @@ their secrets and token mappings, and the functions added on a connection | `reveal_marketplace_integration_secrets` | `POST` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/secrets/reveal` | `project` | path: `integration_view_id` | | `set_marketplace_integration_token_mappings` | `PUT` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/token-mappings` | `project` | path: `integration_view_id`; JSON body: `tokenMappings` | | `get_marketplace_function_catalog` | `GET` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/catalog` | `project` | path: `integration_view_id` | -| `get_marketplace_function_runs` | `GET` | `/{version}/code/marketplace/functions/runs` | `project` | query: exactly one of `triggerId` / `taskId`, `limit` (1–50, default 20); newest first | +| `get_marketplace_function_runs` | `GET` | `/{version}/code/marketplace/functions/runs` | `project` | query: exactly one of `functionId` / `triggerId` / `taskId`, `limit` (1–50, default 20); newest first | ```python listings = norbix.hub.code.get_marketplace_listings(search="slack") @@ -67,6 +67,30 @@ runs = norbix.hub.code.get_marketplace_function_runs(triggerId="trg_…", limit= result = norbix.hub.code.invoke_marketplace_function("func_…", payload={"text": "Order shipped"}) ``` +### Marketplace function runs and their details + +Each call of a marketplace function is one run row: an invoke (user, API key, +AI), a trigger run or a scheduler fire. `functionId` lists every source of one +function (up to 50 kept); `triggerId` / `taskId` list the runs one trigger or +task started (20 kept). A row carries `status` (`ok` / `failed` / `skipped`, +`None` on rows written before the field existed), `durationMs`, `source` +(`user`, `api-key`, `ai`, `trigger`, `scheduler`), `event`, `correlationId` +and `hasDetails`. + +The input, steps and response of a run are written to the project log under +the row's `correlationId`, only while the environment's Norbix Logging is on — +then `hasDetails` is `True`. Read them with the logs route; its query field is +`targetCorrelationId`: + +```python +runs = norbix.hub.code.get_marketplace_function_runs(functionId="func_…", limit=10) +for run in runs.get("runs", []): + if not run.get("hasDetails"): + continue # logs were off for this run + details = norbix.hub.logs.get_logs_by_correlation_id(targetCorrelationId=run["correlationId"]) + # details["items"]: marketplace:run:started → marketplace:run:step … → completed | failed | skipped +``` + ## Cloud functions The five routes of the gateway contract `Code.Functions.md` (§3.3, §3.4, diff --git a/references/hub_dtos.py b/references/hub_dtos.py index 101a46a..4ec2226 100644 --- a/references/hub_dtos.py +++ b/references/hub_dtos.py @@ -1,5 +1,5 @@ """ Options: -Date: 2026-10-12 21:43:18 +Date: 2026-10-13 06:06:37 Version: 10.20 Tip: To override a DTO option, remove "#" prefix before updating BaseUrl: http://localhost:5001 @@ -5965,9 +5965,14 @@ class MarketplaceFunctionRunDto: function_id: Optional[str] = None run_as_user_id: Optional[str] = None succeeded: bool = False + status: Optional[str] = None duration_ms: Optional[int] = None error: Optional[str] = None vendor_request_id: Optional[str] = None + correlation_id: Optional[str] = None + has_details: bool = False + trigger_id: Optional[str] = None + task_id: Optional[str] = None input: Optional[str] = None @@ -15361,17 +15366,24 @@ class GetMarketplaceFunctionCatalog(CodeMashRequestBase, IReturn[GetMarketplaceF @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetMarketplaceFunctionRuns(CodeMashRequestBase, IReturn[GetMarketplaceFunctionRunsResponse]): - # @ApiMember(Description="The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId.") + # @ApiMember(Description="The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId.") + function_id: Optional[str] = None + """ + The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId. + """ + + + # @ApiMember(Description="The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId.") trigger_id: Optional[str] = None """ - The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId. + The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId. """ - # @ApiMember(Description="The scheduler task whose marketplace function runs to list (tsk_…). One of triggerId / taskId.") + # @ApiMember(Description="The scheduler task whose marketplace function runs to list (tsk_…). One of functionId / triggerId / taskId.") task_id: Optional[str] = None """ - The scheduler task whose marketplace function runs to list (tsk_…). One of triggerId / taskId. + The scheduler task whose marketplace function runs to list (tsk_…). One of functionId / triggerId / taskId. """ diff --git a/tests/hub/test_code.py b/tests/hub/test_code.py index b238a72..eb75c37 100644 --- a/tests/hub/test_code.py +++ b/tests/hub/test_code.py @@ -356,3 +356,58 @@ def test_marketplace_surface_size() -> None: client, _ = make_client() for name, *_ in MARKETPLACE_CASES: assert callable(getattr(client.hub.code, name)), name + + +RUNS_PATH = "/v3/code/marketplace/functions/runs" +RUNS_BY_FUNCTION_QUERY = {"functionId": ["func_1p4R3gTobGZpD6fXvHT4rd"], "limit": ["50"]} + + +def test_marketplace_function_runs_by_function_id() -> None: + """Exactly one of functionId / triggerId / taskId (gateway item gw-runs): a function's own runs.""" + client, transport = make_client() + client.hub.code.get_marketplace_function_runs(functionId="func_1p4R3gTobGZpD6fXvHT4rd", limit=50) + assert transport.last_request is not None + _assert_function_request( + "get_marketplace_function_runs(functionId)", + transport.last_request["method"], + transport.last_request["url"], + transport.last_request["body"], + "GET", + RUNS_PATH, + RUNS_BY_FUNCTION_QUERY, + None, + ) + + +def test_async_marketplace_function_runs_by_function_id() -> None: + seen: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={"runs": [{"functionId": "func_1p4R3gTobGZpD6fXvHT4rd", "status": "ok", "hasDetails": True}]}) + + async def run() -> Any: + client = AsyncNorbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)), + ) + try: + return await client.hub.code.get_marketplace_function_runs(functionId="func_1p4R3gTobGZpD6fXvHT4rd", limit=50) + finally: + await client.aclose() + + result = asyncio.run(run()) + + assert len(seen) == 1 + _assert_function_request( + "async get_marketplace_function_runs(functionId)", + seen[0].method, + str(seen[0].url), + seen[0].content.decode("utf-8"), + "GET", + RUNS_PATH, + RUNS_BY_FUNCTION_QUERY, + None, + ) + assert result == {"runs": [{"functionId": "func_1p4R3gTobGZpD6fXvHT4rd", "status": "ok", "hasDetails": True}]}