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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,34 @@ provider (the payload is the project's data) and when the function was not
called. There is no default Code integration: every call names the
integration it means (`IntegrationId`), as the examples above do.

## Marketplace function runs

Each call of a marketplace function is one run row: an invoke (user, API
key, AI), a trigger run or a scheduler fire.
`Code.GetMarketplaceFunctionRunsAsync` lists them — set exactly one of
`FunctionId` (every source, newest first, up to 50), `TriggerId` or
`TaskId`. A row carries `Status` (`ok` / `failed` / `skipped`, `null` on
rows written before the field existed), `DurationMs`, `Source`, `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`. `GetMarketplaceFunctionRunDetailsAsync` (in
`Norbix.Hub`, a hand-written helper over `Logs.GetLogsByCorrelationIdAsync`,
`GET /{version}/logs/audit?targetCorrelationId=…`) reads them:

```csharp
var runs = await hub.Code.GetMarketplaceFunctionRunsAsync(
new GetMarketplaceFunctionRuns { FunctionId = "func_…", Limit = 10 });

foreach (var run in runs?.Runs ?? [])
{
if (!run.HasDetails || run.CorrelationId is null) continue; // logs were off
var details = await hub.GetMarketplaceFunctionRunDetailsAsync(run.CorrelationId);
// details.Items: marketplace:run:started → marketplace:run:step … → completed | failed | skipped
}
```

## Coverage Notes

Generated coverage tracks the API and Hub DTO contract files. Some flows are not generated automatically:
Expand Down
22 changes: 21 additions & 1 deletion src/Norbix.Hub/NorbixHubClient.Convenience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,25 @@ public sealed partial class NorbixHubClient
},
cancellationToken);
}
}

/// <summary>
/// The details of one marketplace function run: the project log lines filed
/// under the run's correlation id (<c>marketplace:run:started</c>, <c>step</c>,
/// then <c>completed</c>, <c>failed</c> or <c>skipped</c>). Pass
/// <see cref="MarketplaceFunctionRunDto.CorrelationId"/> of a row from
/// <c>Code.GetMarketplaceFunctionRunsAsync</c>. Only a row with
/// <see cref="MarketplaceFunctionRunDto.HasDetails"/> has lines — they are
/// written only while the environment's Norbix Logging is on.
/// Same call as <c>Logs.GetLogsByCorrelationIdAsync</c>
/// (GET /{version}/logs/audit?targetCorrelationId=…).
/// </summary>
public Task<GetLogsByCorrelationIdResponse?> GetMarketplaceFunctionRunDetailsAsync(
string correlationId,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
return Logs.GetLogsByCorrelationIdAsync(
new GetLogsByCorrelationId { TargetCorrelationId = correlationId },
cancellationToken);
}
}
24 changes: 22 additions & 2 deletions src/Norbix.Sdk.Types/Generated/Hub.dtos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@ public partial class MarketplaceFunctionRunDto
[DataMember]
public virtual bool Succeeded { get; set; }

[DataMember]
public virtual string? Status { get; set; }

[DataMember]
public virtual long? DurationMs { get; set; }

Expand All @@ -3910,6 +3913,18 @@ public partial class MarketplaceFunctionRunDto
[DataMember]
public virtual string? VendorRequestId { get; set; }

[DataMember]
public virtual string? CorrelationId { get; set; }

[DataMember]
public virtual bool HasDetails { get; set; }

[DataMember]
public virtual string? TriggerId { get; set; }

[DataMember]
public virtual string? TaskId { get; set; }

[DataMember]
public virtual string? Input { get; set; }
}
Expand Down Expand Up @@ -11422,12 +11437,17 @@ public partial class GetMarketplaceFunctionRuns
: CodeMashRequestBase, INorbixRequest<GetMarketplaceFunctionRunsResponse>
{
///<summary>
///The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId.
///The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId.
///</summary>
public virtual string? FunctionId { get; set; }

///<summary>
///The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId.
///</summary>
public virtual string? TriggerId { get; set; }

///<summary>
///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.
///</summary>
public virtual string? TaskId { get; set; }

Expand Down
87 changes: 87 additions & 0 deletions tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Norbix.Sdk;
using Norbix.Sdk.Tests.Helpers;
using Norbix.Sdk.Types.Hub;

using NUnit.Framework;

using VerifyNUnit;

namespace Norbix.Hub.Tests;

/// <summary>
/// GET /{version}/code/marketplace/functions/runs takes exactly one of
/// functionId / triggerId / taskId. The coverage snapshot fills every field at
/// once; these cases pin a function's own runs (functionId alone, on the query,
/// no body) and the details helper over GET /{version}/logs/audit.
/// Nothing leaves the process.
/// </summary>
[TestFixture]
public sealed class MarketplaceRunsTests
{
private const string CorrelationId = "0f8e2c9a4b7d4e1f9a3c5b6d7e8f9012";

private static NorbixTestFixture NewFixture() =>
NorbixTestFixture.Create(o =>
{
o.AccountId = "test-account";
o.BearerToken = "test-bearer";
});

[Test]
public async Task Function_runs_send_functionId_and_limit_on_the_query_with_no_body()
{
using var fixture = NewFixture();
fixture.RespondNext(new
{
runs = new[]
{
new
{
functionId = "func_1p4R3gTobGZpD6fXvHT4rd",
source = "api-key",
@event = "invoke",
succeeded = true,
status = "ok",
correlationId = CorrelationId,
hasDetails = true,
},
},
});

var response = await fixture.Client.Code.GetMarketplaceFunctionRunsAsync(
new GetMarketplaceFunctionRuns { FunctionId = "func_1p4R3gTobGZpD6fXvHT4rd", Limit = 50 });

// The snapshot pins verb, path, the exact query (functionId + limit only) and no body,
// and the new row fields read back from the generated response.
await Verifier.Verify(new { Sent = fixture.LastRequest, response!.Runs }, VerifyConfig.VerifySettings);
}

[Test]
public async Task Run_details_read_the_log_lines_by_targetCorrelationId()
{
using var fixture = NewFixture();

await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(CorrelationId);

// GET /v2/logs/audit?targetCorrelationId=… — the DTO property name, not "correlationId".
await Verifier.Verify(fixture.LastRequest, VerifyConfig.VerifySettings);
}

[Test]
public async Task Run_details_refuse_an_empty_correlation_id()
{
using var fixture = NewFixture();

string? error = null;
try
{
await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(" ");
}
catch (ArgumentException ex)
{
error = $"{ex.GetType().Name}: {ex.ParamName}";
}

await Verifier.Verify(new { Error = error, NothingSent = fixture.RecordedRequests.Count == 0 }, VerifyConfig.VerifySettings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@
Sent: {
Method: GET,
Path: /v2/code/marketplace/functions/runs,
Query: ?triggerId={guid}&taskId={guid}&limit=1&env=test-env,
Query: ?functionId={guid}&triggerId={guid}&taskId={guid}&limit=1&env=test-env,
Headers: {
Authorization: Bearer test-bearer,
norbix-account-id: test-account,
Expand All @@ -711,7 +711,8 @@
Response: {
Runs: [
{
Succeeded: false
Succeeded: false,
HasDetails: false
}
],
ResponseStatus: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
Sent: {
Method: GET,
Path: /v2/code/marketplace/functions/runs,
Query: ?functionId=func_1p4R3gTobGZpD6fXvHT4rd&limit=50,
Headers: {
Authorization: Bearer test-bearer,
norbix-account-id: test-account,
norbix-project-id: test-project
}
},
Runs: [
{
Event: invoke,
Source: api-key,
FunctionId: func_1p4R3gTobGZpD6fXvHT4rd,
Succeeded: true,
Status: ok,
CorrelationId: Guid_1,
HasDetails: true
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
Method: GET,
Path: /v2/logs/audit,
Query: ?targetCorrelationId=0f8e2c9a4b7d4e1f9a3c5b6d7e8f9012,
Headers: {
Authorization: Bearer test-bearer,
norbix-account-id: test-account,
norbix-project-id: test-project
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
Error: ArgumentException: correlationId,
NothingSent: true
}
Loading