From 216e5d76b3a5389b17aeec57a1a9bb782a49df3d Mon Sep 17 00:00:00 2001 From: Maciej Dudkowski Date: Mon, 20 Oct 2025 12:48:35 -0400 Subject: [PATCH 1/3] Manual pagination of ListWorkflows operation --- .../Client/GetListWorkflowsPageOptions.cs | 9 +++ .../Client/ITemporalClient.Workflow.cs | 19 ++++++ .../ClientOutboundInterceptor.Workflow.cs | 23 ++++++- .../FetchListWorkflowsPageInput.cs | 17 +++++ src/Temporalio/Client/ListWorkflowsPage.cs | 14 ++++ .../Client/TemporalClient.Workflow.cs | 65 ++++++++++++++----- .../Client/TemporalClientWorkflowTests.cs | 27 ++++++++ 7 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 src/Temporalio/Client/GetListWorkflowsPageOptions.cs create mode 100644 src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs create mode 100644 src/Temporalio/Client/ListWorkflowsPage.cs diff --git a/src/Temporalio/Client/GetListWorkflowsPageOptions.cs b/src/Temporalio/Client/GetListWorkflowsPageOptions.cs new file mode 100644 index 00000000..3fc2049d --- /dev/null +++ b/src/Temporalio/Client/GetListWorkflowsPageOptions.cs @@ -0,0 +1,9 @@ +namespace Temporalio.Client +{ + /// + /// Options for . + /// + /// Number of results per page. Zero means server default. + /// RPC options for listing workflows. + public record GetListWorkflowsPageOptions(int PageSize = 0, RpcOptions? Rpc = null); +} diff --git a/src/Temporalio/Client/ITemporalClient.Workflow.cs b/src/Temporalio/Client/ITemporalClient.Workflow.cs index 16eaebd5..dcc49e8e 100644 --- a/src/Temporalio/Client/ITemporalClient.Workflow.cs +++ b/src/Temporalio/Client/ITemporalClient.Workflow.cs @@ -183,5 +183,24 @@ public IAsyncEnumerable ListWorkflowsAsync( /// Visibility docs. public Task CountWorkflowsAsync( string query, WorkflowCountOptions? options = null); + + /// + /// List workflows with the given query using manual paging. + /// + /// + /// Query to use for filtering. Subsequent pages must have the same query as the initial call. + /// + /// + /// Set to null for the initial call to retrieve the first page. + /// Set to returned by a previous call to retrieve the next page. + /// + /// Options for the list call. + /// + /// A single page of a list of workflows. + /// Repeat the call using to get more pages. + /// + /// Visibility docs. + Task GetListWorkflowsPageAsync( + string query, byte[]? nextPageToken, GetListWorkflowsPageOptions? options = null); } } diff --git a/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs b/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs index 4b191f37..0d534788 100644 --- a/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs +++ b/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs @@ -99,6 +99,12 @@ public virtual Task FetchWorkflowHistoryEventPageAsync /// /// Input details of the call. /// Async enumerator for the workflows. + /// + /// This method only gets called by . + /// It does not get called by . + /// This method is called before the first page is fetched. Afterwards, before each page fetched + /// (including before the first page), is called. + /// public virtual IAsyncEnumerable ListWorkflowsAsync( ListWorkflowsInput input) => Next.ListWorkflowsAsync(input); @@ -112,5 +118,20 @@ public virtual IAsyncEnumerable ListWorkflowsAsync( public virtual Task CountWorkflowsAsync( CountWorkflowsInput input) => Next.CountWorkflowsAsync(input); + +#pragma warning disable CS1574 // ListWorkflowsAsync does not exist in .Net Framework/Standard + /// + /// Intercept page fetch for list workflows calls. + /// + /// Input details of the call. + /// A single page of query results. + /// + /// This method is called each time is called. + /// It also gets called for each page fetched when iterating the enumerable returned by . + /// +#pragma warning restore CS1574 + public virtual Task FetchListWorkflowsPageAsync( + FetchListWorkflowsPageInput input) => + Next.FetchListWorkflowsPageAsync(input); } -} \ No newline at end of file +} diff --git a/src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs b/src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs new file mode 100644 index 00000000..e1d1912d --- /dev/null +++ b/src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs @@ -0,0 +1,17 @@ +namespace Temporalio.Client.Interceptors +{ + /// + /// Input for . + /// + /// List query. + /// Next page token from a previous response. Null if the request is for the first page. + /// Options passed in to list. + /// + /// WARNING: This constructor may have required properties added. Do not rely on the exact + /// constructor, only use "with" clauses. + /// + public record FetchListWorkflowsPageInput( + string Query, + byte[]? NextPageToken, + GetListWorkflowsPageOptions? Options); +} diff --git a/src/Temporalio/Client/ListWorkflowsPage.cs b/src/Temporalio/Client/ListWorkflowsPage.cs new file mode 100644 index 00000000..f57041e1 --- /dev/null +++ b/src/Temporalio/Client/ListWorkflowsPage.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Temporalio.Client +{ + /// + /// Result type of . + /// + /// A page of the list of workflows matching the query. + /// + /// Token to pass to to retrieve the next page. + /// Null if it's the last page. + /// + public record ListWorkflowsPage(IReadOnlyCollection Workflows, byte[]? NextPageToken); +} diff --git a/src/Temporalio/Client/TemporalClient.Workflow.cs b/src/Temporalio/Client/TemporalClient.Workflow.cs index 8a50bb23..d02d8213 100644 --- a/src/Temporalio/Client/TemporalClient.Workflow.cs +++ b/src/Temporalio/Client/TemporalClient.Workflow.cs @@ -122,9 +122,14 @@ public Task CountWorkflowsAsync( string query, WorkflowCountOptions? options = null) => OutboundInterceptor.CountWorkflowsAsync(new(Query: query, Options: options)); + /// + public Task GetListWorkflowsPageAsync( + string query, byte[]? nextPageToken, GetListWorkflowsPageOptions? options = null) => + OutboundInterceptor.FetchListWorkflowsPageAsync(new(Query: query, NextPageToken: nextPageToken, Options: options)); + internal partial class Impl { - private static IReadOnlyCollection emptyEvents = new List(0); + private static IReadOnlyCollection emptyEvents = new List(0).AsReadOnly(); /// public override async Task> StartWorkflowAsync( @@ -621,7 +626,7 @@ public override async Task FetchWorkflowHistoryEventPa if (pageComplete) { return new WorkflowHistoryEventPage( - resp.History?.Events ?? emptyEvents, + resp.History?.Events?.ToList().AsReadOnly() ?? emptyEvents, resp.NextPageToken.IsEmpty ? null : resp.NextPageToken.ToByteArray()); } req.NextPageToken = resp.NextPageToken; @@ -645,39 +650,65 @@ public override async Task CountWorkflowsAsync( return new(resp); } + /// + public override async Task FetchListWorkflowsPageAsync(FetchListWorkflowsPageInput input) + { + var req = new ListWorkflowExecutionsRequest + { + Namespace = Client.Options.Namespace, + PageSize = input.Options?.PageSize ?? 0, + Query = input.Query, + }; + if (input.NextPageToken is not null) + { + req.NextPageToken = ByteString.CopyFrom(input.NextPageToken); + } + + var resp = await Client.Connection.WorkflowService.ListWorkflowExecutionsAsync( + req, DefaultRetryOptions(input.Options?.Rpc)).ConfigureAwait(false); + + return new( + Workflows: resp.Executions + .Select(e => new WorkflowExecution(e, Client.Options.DataConverter, Client.Options.Namespace)) + .ToList() + .AsReadOnly(), + NextPageToken: resp.NextPageToken.IsEmpty ? null : resp.NextPageToken.ToByteArray()); + } + #if NETCOREAPP3_0_OR_GREATER private async IAsyncEnumerable ListWorkflowsInternalAsync( ListWorkflowsInput input, [EnumeratorCancellation] CancellationToken cancellationToken = default) { + var limit = input.Options?.Limit ?? 0; + if (limit < 0) + { + throw new ArgumentOutOfRangeException(nameof(input), "Limit cannot be negative"); + } + // Need to combine cancellation token var rpcOptsAndCancelSource = DefaultRetryOptions(input.Options?.Rpc). WithAdditionalCancellationToken(cancellationToken); - var yielded = 0; try { - var req = new ListWorkflowExecutionsRequest() - { - // TODO(cretz): Allow setting of page size or next page token? - Namespace = Client.Options.Namespace, - Query = input.Query, - }; + var pageOpts = new GetListWorkflowsPageOptions { Rpc = rpcOptsAndCancelSource.Item1 }; + byte[]? nextPageToken = null; + var yielded = 0; do { - var resp = await Client.Connection.WorkflowService.ListWorkflowExecutionsAsync( - req, rpcOptsAndCancelSource.Item1).ConfigureAwait(false); - foreach (var exec in resp.Executions) + var page = await Client.GetListWorkflowsPageAsync(input.Query, nextPageToken, pageOpts).ConfigureAwait(false); + foreach (var exec in page.Workflows) { - if (input.Options != null && input.Options.Limit > 0 && - yielded++ >= input.Options.Limit) + yield return exec; + yielded++; + if (limit > 0 && yielded >= limit) { yield break; } - yield return new(exec, Client.Options.DataConverter, Client.Options.Namespace); } - req.NextPageToken = resp.NextPageToken; + nextPageToken = page.NextPageToken; } - while (!req.NextPageToken.IsEmpty); + while (nextPageToken is not null); } finally { diff --git a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs index b0c54dc3..25a0144e 100644 --- a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs +++ b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs @@ -330,6 +330,33 @@ await AssertMore.EventuallyAsync(async () => }); } + [Fact] + public async Task ListWorkflowsAsync_ManualPaging_IsAccurate() + { + var workflowId = $"workflow-{Guid.NewGuid()}"; + for (var i = 0; i < 5; i++) + { + var arg = new KSWorkflowParams(new KSAction(Result: new(Value: string.Empty))); + await Client.ExecuteWorkflowAsync( + (IKitchenSinkWorkflow wf) => wf.RunAsync(arg), + new(id: workflowId, taskQueue: Env.KitchenSinkWorkerTaskQueue)); + } + + var firstPage = await Client.GetListWorkflowsPageAsync($"WorkflowId = '{workflowId}'", null, new(PageSize: 2)); + Assert.Equal(2, firstPage.Workflows.Count); + Assert.NotNull(firstPage.NextPageToken); + Assert.NotEmpty(firstPage.NextPageToken); + + var secondPage = await Client.GetListWorkflowsPageAsync($"WorkflowId = '{workflowId}'", firstPage.NextPageToken, new(PageSize: 2)); + Assert.Equal(2, secondPage.Workflows.Count); + Assert.NotNull(secondPage.NextPageToken); + Assert.NotEmpty(secondPage.NextPageToken); + + var thirdPage = await Client.GetListWorkflowsPageAsync($"WorkflowId = '{workflowId}'", secondPage.NextPageToken, new(PageSize: 2)); + Assert.Equal(1, thirdPage.Workflows.Count); + Assert.Null(thirdPage.NextPageToken); + } + internal record TracingEvent(string Name, object Input); internal class TracingClientInterceptor : IClientInterceptor From 6c6d1ad39b772fb8ddc23c027ebfa0dee96e4e94 Mon Sep 17 00:00:00 2001 From: Maciej Dudkowski Date: Thu, 23 Oct 2025 18:46:42 -0400 Subject: [PATCH 2/3] Renames --- src/Temporalio/Client/ITemporalClient.Workflow.cs | 8 ++++---- .../ClientOutboundInterceptor.Workflow.cs | 12 ++++++------ ...PageInput.cs => ListWorkflowsPaginatedInput.cs} | 6 +++--- src/Temporalio/Client/ListWorkflowsPage.cs | 14 -------------- ...Options.cs => ListWorkflowsPaginatedOptions.cs} | 4 ++-- src/Temporalio/Client/TemporalClient.Workflow.cs | 12 ++++++------ src/Temporalio/Client/WorkflowListPage.cs | 14 ++++++++++++++ .../Client/TemporalClientWorkflowTests.cs | 6 +++--- 8 files changed, 38 insertions(+), 38 deletions(-) rename src/Temporalio/Client/Interceptors/{FetchListWorkflowsPageInput.cs => ListWorkflowsPaginatedInput.cs} (75%) delete mode 100644 src/Temporalio/Client/ListWorkflowsPage.cs rename src/Temporalio/Client/{GetListWorkflowsPageOptions.cs => ListWorkflowsPaginatedOptions.cs} (57%) create mode 100644 src/Temporalio/Client/WorkflowListPage.cs diff --git a/src/Temporalio/Client/ITemporalClient.Workflow.cs b/src/Temporalio/Client/ITemporalClient.Workflow.cs index dcc49e8e..23f348b7 100644 --- a/src/Temporalio/Client/ITemporalClient.Workflow.cs +++ b/src/Temporalio/Client/ITemporalClient.Workflow.cs @@ -192,15 +192,15 @@ public Task CountWorkflowsAsync( /// /// /// Set to null for the initial call to retrieve the first page. - /// Set to returned by a previous call to retrieve the next page. + /// Set to returned by a previous call to retrieve the next page. /// /// Options for the list call. /// /// A single page of a list of workflows. - /// Repeat the call using to get more pages. + /// Repeat the call using to get more pages. /// /// Visibility docs. - Task GetListWorkflowsPageAsync( - string query, byte[]? nextPageToken, GetListWorkflowsPageOptions? options = null); + Task ListWorkflowsPaginatedAsync( + string query, byte[]? nextPageToken, ListWorkflowsPaginatedOptions? options = null); } } diff --git a/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs b/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs index 0d534788..dbffcad7 100644 --- a/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs +++ b/src/Temporalio/Client/Interceptors/ClientOutboundInterceptor.Workflow.cs @@ -101,9 +101,9 @@ public virtual Task FetchWorkflowHistoryEventPageAsync /// Async enumerator for the workflows. /// /// This method only gets called by . - /// It does not get called by . + /// It does not get called by . /// This method is called before the first page is fetched. Afterwards, before each page fetched - /// (including before the first page), is called. + /// (including before the first page), is called. /// public virtual IAsyncEnumerable ListWorkflowsAsync( ListWorkflowsInput input) => @@ -126,12 +126,12 @@ public virtual Task CountWorkflowsAsync( /// Input details of the call. /// A single page of query results. /// - /// This method is called each time is called. + /// This method is called each time is called. /// It also gets called for each page fetched when iterating the enumerable returned by . /// #pragma warning restore CS1574 - public virtual Task FetchListWorkflowsPageAsync( - FetchListWorkflowsPageInput input) => - Next.FetchListWorkflowsPageAsync(input); + public virtual Task ListWorkflowsPaginatedAsync( + ListWorkflowsPaginatedInput input) => + Next.ListWorkflowsPaginatedAsync(input); } } diff --git a/src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs b/src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs similarity index 75% rename from src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs rename to src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs index e1d1912d..018a8da1 100644 --- a/src/Temporalio/Client/Interceptors/FetchListWorkflowsPageInput.cs +++ b/src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs @@ -1,7 +1,7 @@ namespace Temporalio.Client.Interceptors { /// - /// Input for . + /// Input for . /// /// List query. /// Next page token from a previous response. Null if the request is for the first page. @@ -10,8 +10,8 @@ namespace Temporalio.Client.Interceptors /// WARNING: This constructor may have required properties added. Do not rely on the exact /// constructor, only use "with" clauses. /// - public record FetchListWorkflowsPageInput( + public record ListWorkflowsPaginatedInput( string Query, byte[]? NextPageToken, - GetListWorkflowsPageOptions? Options); + ListWorkflowsPaginatedOptions? Options); } diff --git a/src/Temporalio/Client/ListWorkflowsPage.cs b/src/Temporalio/Client/ListWorkflowsPage.cs deleted file mode 100644 index f57041e1..00000000 --- a/src/Temporalio/Client/ListWorkflowsPage.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Temporalio.Client -{ - /// - /// Result type of . - /// - /// A page of the list of workflows matching the query. - /// - /// Token to pass to to retrieve the next page. - /// Null if it's the last page. - /// - public record ListWorkflowsPage(IReadOnlyCollection Workflows, byte[]? NextPageToken); -} diff --git a/src/Temporalio/Client/GetListWorkflowsPageOptions.cs b/src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs similarity index 57% rename from src/Temporalio/Client/GetListWorkflowsPageOptions.cs rename to src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs index 3fc2049d..3105739d 100644 --- a/src/Temporalio/Client/GetListWorkflowsPageOptions.cs +++ b/src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs @@ -1,9 +1,9 @@ namespace Temporalio.Client { /// - /// Options for . + /// Options for . /// /// Number of results per page. Zero means server default. /// RPC options for listing workflows. - public record GetListWorkflowsPageOptions(int PageSize = 0, RpcOptions? Rpc = null); + public record ListWorkflowsPaginatedOptions(int PageSize = 0, RpcOptions? Rpc = null); } diff --git a/src/Temporalio/Client/TemporalClient.Workflow.cs b/src/Temporalio/Client/TemporalClient.Workflow.cs index d02d8213..347c07a7 100644 --- a/src/Temporalio/Client/TemporalClient.Workflow.cs +++ b/src/Temporalio/Client/TemporalClient.Workflow.cs @@ -123,9 +123,9 @@ public Task CountWorkflowsAsync( OutboundInterceptor.CountWorkflowsAsync(new(Query: query, Options: options)); /// - public Task GetListWorkflowsPageAsync( - string query, byte[]? nextPageToken, GetListWorkflowsPageOptions? options = null) => - OutboundInterceptor.FetchListWorkflowsPageAsync(new(Query: query, NextPageToken: nextPageToken, Options: options)); + public Task ListWorkflowsPaginatedAsync( + string query, byte[]? nextPageToken, ListWorkflowsPaginatedOptions? options = null) => + OutboundInterceptor.ListWorkflowsPaginatedAsync(new(Query: query, NextPageToken: nextPageToken, Options: options)); internal partial class Impl { @@ -651,7 +651,7 @@ public override async Task CountWorkflowsAsync( } /// - public override async Task FetchListWorkflowsPageAsync(FetchListWorkflowsPageInput input) + public override async Task ListWorkflowsPaginatedAsync(ListWorkflowsPaginatedInput input) { var req = new ListWorkflowExecutionsRequest { @@ -691,12 +691,12 @@ private async IAsyncEnumerable ListWorkflowsInternalAsync( WithAdditionalCancellationToken(cancellationToken); try { - var pageOpts = new GetListWorkflowsPageOptions { Rpc = rpcOptsAndCancelSource.Item1 }; + var pageOpts = new ListWorkflowsPaginatedOptions { Rpc = rpcOptsAndCancelSource.Item1 }; byte[]? nextPageToken = null; var yielded = 0; do { - var page = await Client.GetListWorkflowsPageAsync(input.Query, nextPageToken, pageOpts).ConfigureAwait(false); + var page = await Client.ListWorkflowsPaginatedAsync(input.Query, nextPageToken, pageOpts).ConfigureAwait(false); foreach (var exec in page.Workflows) { yield return exec; diff --git a/src/Temporalio/Client/WorkflowListPage.cs b/src/Temporalio/Client/WorkflowListPage.cs new file mode 100644 index 00000000..3f1b3ded --- /dev/null +++ b/src/Temporalio/Client/WorkflowListPage.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Temporalio.Client +{ + /// + /// Result type of . + /// + /// A page from the list of workflows matching the query. + /// + /// Token to pass to to retrieve the next page. + /// Null if there are no more pages. + /// + public record WorkflowListPage(IReadOnlyCollection Workflows, byte[]? NextPageToken); +} diff --git a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs index 25a0144e..bd464c35 100644 --- a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs +++ b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs @@ -342,17 +342,17 @@ await Client.ExecuteWorkflowAsync( new(id: workflowId, taskQueue: Env.KitchenSinkWorkerTaskQueue)); } - var firstPage = await Client.GetListWorkflowsPageAsync($"WorkflowId = '{workflowId}'", null, new(PageSize: 2)); + var firstPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", null, new(PageSize: 2)); Assert.Equal(2, firstPage.Workflows.Count); Assert.NotNull(firstPage.NextPageToken); Assert.NotEmpty(firstPage.NextPageToken); - var secondPage = await Client.GetListWorkflowsPageAsync($"WorkflowId = '{workflowId}'", firstPage.NextPageToken, new(PageSize: 2)); + var secondPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", firstPage.NextPageToken, new(PageSize: 2)); Assert.Equal(2, secondPage.Workflows.Count); Assert.NotNull(secondPage.NextPageToken); Assert.NotEmpty(secondPage.NextPageToken); - var thirdPage = await Client.GetListWorkflowsPageAsync($"WorkflowId = '{workflowId}'", secondPage.NextPageToken, new(PageSize: 2)); + var thirdPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", secondPage.NextPageToken, new(PageSize: 2)); Assert.Equal(1, thirdPage.Workflows.Count); Assert.Null(thirdPage.NextPageToken); } From 7a822e7b75f6d31466c0656b69774ea17b2a7871 Mon Sep 17 00:00:00 2001 From: Maciej Dudkowski Date: Fri, 24 Oct 2025 10:30:39 -0400 Subject: [PATCH 3/3] Renamed WorkflowListPaginatedOption and made it ICloneable --- .../Client/ITemporalClient.Workflow.cs | 2 +- .../ListWorkflowsPaginatedInput.cs | 2 +- .../Client/ListWorkflowsPaginatedOptions.cs | 9 ----- .../Client/TemporalClient.Workflow.cs | 4 +-- .../Client/WorkflowListPaginatedOptions.cs | 34 +++++++++++++++++++ .../Client/TemporalClientWorkflowTests.cs | 8 +++-- 6 files changed, 43 insertions(+), 16 deletions(-) delete mode 100644 src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs create mode 100644 src/Temporalio/Client/WorkflowListPaginatedOptions.cs diff --git a/src/Temporalio/Client/ITemporalClient.Workflow.cs b/src/Temporalio/Client/ITemporalClient.Workflow.cs index 23f348b7..f44022d6 100644 --- a/src/Temporalio/Client/ITemporalClient.Workflow.cs +++ b/src/Temporalio/Client/ITemporalClient.Workflow.cs @@ -201,6 +201,6 @@ public Task CountWorkflowsAsync( /// /// Visibility docs. Task ListWorkflowsPaginatedAsync( - string query, byte[]? nextPageToken, ListWorkflowsPaginatedOptions? options = null); + string query, byte[]? nextPageToken, WorkflowListPaginatedOptions? options = null); } } diff --git a/src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs b/src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs index 018a8da1..df504d5a 100644 --- a/src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs +++ b/src/Temporalio/Client/Interceptors/ListWorkflowsPaginatedInput.cs @@ -13,5 +13,5 @@ namespace Temporalio.Client.Interceptors public record ListWorkflowsPaginatedInput( string Query, byte[]? NextPageToken, - ListWorkflowsPaginatedOptions? Options); + WorkflowListPaginatedOptions? Options); } diff --git a/src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs b/src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs deleted file mode 100644 index 3105739d..00000000 --- a/src/Temporalio/Client/ListWorkflowsPaginatedOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Temporalio.Client -{ - /// - /// Options for . - /// - /// Number of results per page. Zero means server default. - /// RPC options for listing workflows. - public record ListWorkflowsPaginatedOptions(int PageSize = 0, RpcOptions? Rpc = null); -} diff --git a/src/Temporalio/Client/TemporalClient.Workflow.cs b/src/Temporalio/Client/TemporalClient.Workflow.cs index 347c07a7..791a478e 100644 --- a/src/Temporalio/Client/TemporalClient.Workflow.cs +++ b/src/Temporalio/Client/TemporalClient.Workflow.cs @@ -124,7 +124,7 @@ public Task CountWorkflowsAsync( /// public Task ListWorkflowsPaginatedAsync( - string query, byte[]? nextPageToken, ListWorkflowsPaginatedOptions? options = null) => + string query, byte[]? nextPageToken, WorkflowListPaginatedOptions? options = null) => OutboundInterceptor.ListWorkflowsPaginatedAsync(new(Query: query, NextPageToken: nextPageToken, Options: options)); internal partial class Impl @@ -691,7 +691,7 @@ private async IAsyncEnumerable ListWorkflowsInternalAsync( WithAdditionalCancellationToken(cancellationToken); try { - var pageOpts = new ListWorkflowsPaginatedOptions { Rpc = rpcOptsAndCancelSource.Item1 }; + var pageOpts = new WorkflowListPaginatedOptions { Rpc = rpcOptsAndCancelSource.Item1 }; byte[]? nextPageToken = null; var yielded = 0; do diff --git a/src/Temporalio/Client/WorkflowListPaginatedOptions.cs b/src/Temporalio/Client/WorkflowListPaginatedOptions.cs new file mode 100644 index 00000000..5d07ddbc --- /dev/null +++ b/src/Temporalio/Client/WorkflowListPaginatedOptions.cs @@ -0,0 +1,34 @@ +using System; + +namespace Temporalio.Client +{ + /// + /// Options for . + /// + public class WorkflowListPaginatedOptions : ICloneable + { + /// + /// Gets or sets the number of results per page. Zero means server default. + /// + public int PageSize { get; set; } + + /// + /// Gets or sets RPC options for listing workflows. + /// + public RpcOptions? Rpc { get; set; } + + /// + /// Create a shallow copy of these options. + /// + /// A shallow copy of these options and any transitive options fields. + public virtual object Clone() + { + var copy = (WorkflowListPaginatedOptions)MemberwiseClone(); + if (Rpc != null) + { + copy.Rpc = (RpcOptions)Rpc.Clone(); + } + return copy; + } + } +} diff --git a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs index bd464c35..73548f61 100644 --- a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs +++ b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs @@ -342,17 +342,19 @@ await Client.ExecuteWorkflowAsync( new(id: workflowId, taskQueue: Env.KitchenSinkWorkerTaskQueue)); } - var firstPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", null, new(PageSize: 2)); + var options = new WorkflowListPaginatedOptions { PageSize = 2 }; + + var firstPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", null, options); Assert.Equal(2, firstPage.Workflows.Count); Assert.NotNull(firstPage.NextPageToken); Assert.NotEmpty(firstPage.NextPageToken); - var secondPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", firstPage.NextPageToken, new(PageSize: 2)); + var secondPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", firstPage.NextPageToken, options); Assert.Equal(2, secondPage.Workflows.Count); Assert.NotNull(secondPage.NextPageToken); Assert.NotEmpty(secondPage.NextPageToken); - var thirdPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", secondPage.NextPageToken, new(PageSize: 2)); + var thirdPage = await Client.ListWorkflowsPaginatedAsync($"WorkflowId = '{workflowId}'", secondPage.NextPageToken, options); Assert.Equal(1, thirdPage.Workflows.Count); Assert.Null(thirdPage.NextPageToken); }