diff --git a/appveyor.yml b/appveyor.yml index 7821de9..d07e56f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: '{build}' skip_tags: true -image: Visual Studio 2019 +image: Visual Studio 2022 build_script: - ps: ./Build.ps1 test: off diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs index 12d8f88..f0b690b 100644 --- a/src/Seq.Api/Client/SeqApiClient.cs +++ b/src/Seq.Api/Client/SeqApiClient.cs @@ -35,20 +35,22 @@ namespace Seq.Api.Client /// /// A low-level client that provides navigation over the linked resource structure of the Seq HTTP API. /// - public class SeqApiClient : IDisposable + public sealed class SeqApiClient : IDisposable { readonly string _apiKey; - // Future versions of Seq may not completely support v1 features, however + // Future versions of Seq may not completely support vN-1 features, however // providing this as an Accept header will ensure what compatibility is available // can be utilized. - const string SeqApiV8MediaType = "application/vnd.datalust.seq.v8+json"; + const string SeqApiV9MediaType = "application/vnd.datalust.seq.v9+json"; - readonly CookieContainer _cookies = new CookieContainer(); + readonly CookieContainer _cookies = new(); readonly JsonSerializer _serializer = JsonSerializer.Create( new JsonSerializerSettings { - Converters = { new StringEnumConverter(), new LinkCollectionConverter() } + Converters = { new StringEnumConverter(), new LinkCollectionConverter() }, + DateParseHandling = DateParseHandling.None, + FloatParseHandling = FloatParseHandling.Decimal }); /// @@ -89,7 +91,7 @@ public SeqApiClient(string serverUrl, string apiKey = null, Action(ILinked entity, string link, TEntity conten var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - reader.ReadToEnd(); + using var reader = new StreamReader(stream); + await reader.ReadToEndAsync(); } /// @@ -213,8 +215,8 @@ public async Task PostReadStringAsync(ILinked entity, string li var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - return await reader.ReadToEndAsync(); + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync(); } /// diff --git a/src/Seq.Api/Model/Alerting/AlertEntity.cs b/src/Seq.Api/Model/Alerting/AlertEntity.cs index 5dc1745..ad8bab1 100644 --- a/src/Seq.Api/Model/Alerting/AlertEntity.cs +++ b/src/Seq.Api/Model/Alerting/AlertEntity.cs @@ -42,7 +42,7 @@ public class AlertEntity : Entity public string OwnerId { get; set; } /// - /// If true, the alert can only be modified by users with the permission. + /// If true, the alert can only be modified by users with the permission. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs index b1e0739..e563308 100644 --- a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs +++ b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs @@ -20,6 +20,8 @@ using Seq.Api.Model.Signals; using Seq.Api.ResourceGroups; +#nullable enable + namespace Seq.Api.Model.AppInstances { /// @@ -49,17 +51,17 @@ public AppInstanceEntity() /// /// The id of the that this is an instance of. /// - public string AppId { get; set; } + public string? AppId { get; set; } /// /// The user-friendly title of the app instance. /// - public string Title { get; set; } + public string? Title { get; set; } /// /// Values for the settings exposed by the app. /// - public Dictionary Settings { get; set; } + public Dictionary? Settings { get; set; } /// /// If true, administrative users may invoke the app manually or through alerts. @@ -77,13 +79,13 @@ public AppInstanceEntity() /// The settings that can be overridden at invocation time (when an event is sent to /// the instance). /// - public List InvocationOverridableSettings { get; set; } + public List? InvocationOverridableSettings { get; set; } /// /// Metadata describing the overridable settings. This field is provided by the server /// and cannot be modified. /// - public List InvocationOverridableSettingDefinitions { get; set; } + public List? InvocationOverridableSettingDefinitions { get; set; } /// /// If true, events will be streamed to the app. Otherwise, events will be @@ -95,7 +97,7 @@ public AppInstanceEntity() /// The signal expression describing which events will be sent to the app; if null, /// all events will reach the app. /// - public SignalExpressionPart StreamedSignalExpression { get; set; } + public SignalExpressionPart? StreamedSignalExpression { get; set; } /// /// If a value is specified, events will be buffered to disk and sorted by timestamp-order @@ -121,31 +123,31 @@ public AppInstanceEntity() /// Settings that control how events are ingested through the app. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputSettingsPart InputSettings { get; set; } + public InputSettingsPart? InputSettings { get; set; } /// /// Metrics describing the state and activity of the app process. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public AppInstanceProcessMetricsPart ProcessMetrics { get; set; } + public AppInstanceProcessMetricsPart? ProcessMetrics { get; set; } /// /// Information about ingestion activity through this app. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputMetricsPart InputMetrics { get; set; } + public InputMetricsPart? InputMetrics { get; set; } /// /// Information about the app's diagnostic input. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputMetricsPart DiagnosticInputMetrics { get; set; } + public InputMetricsPart? DiagnosticInputMetrics { get; set; } /// /// Information about events output through the app. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public AppInstanceOutputMetricsPart OutputMetrics { get; set; } + public AppInstanceOutputMetricsPart? OutputMetrics { get; set; } /// /// Obsolete. @@ -160,5 +162,17 @@ public AppInstanceEntity() [Obsolete("Use !AcceptDirectInvocation instead.")] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool? DisallowManualInput { get; set; } + + /// + /// The name of the app. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? AppName { get; set; } + + /// + /// If true, then the app is able to write events to the log. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? IsInput { get; set; } } } diff --git a/src/Seq.Api/Model/Apps/AppSettingPart.cs b/src/Seq.Api/Model/Apps/AppSettingPart.cs index 47dd5f7..8cc3343 100644 --- a/src/Seq.Api/Model/Apps/AppSettingPart.cs +++ b/src/Seq.Api/Model/Apps/AppSettingPart.cs @@ -54,5 +54,16 @@ public class AppSettingPart /// for the setting. /// public List AllowedValues { get; set; } = new List(); + + /// + /// If the setting value contains code in a programming or markup language, the + /// language name. + /// + /// Valid names are a subset of the names and aliases recognized by + /// GitHub + /// Linguist. The generic value code will be specified if the language is non-specific but + /// the value should be displayed in fixed-width font. Seq also recognizes the special Seq-specific + /// template and expression syntaxes. + public string Syntax { get; set; } } } diff --git a/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs b/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs index edac684..c9da631 100644 --- a/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs +++ b/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs @@ -34,7 +34,7 @@ public class DashboardEntity : Entity public string Title { get; set; } /// - /// If true, only users with can modify the dashboard. + /// If true, only users with can modify the dashboard. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/Data/QueryResultPart.cs b/src/Seq.Api/Model/Data/QueryResultPart.cs index dcbefd6..2007a80 100644 --- a/src/Seq.Api/Model/Data/QueryResultPart.cs +++ b/src/Seq.Api/Model/Data/QueryResultPart.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Collections.Generic; using Newtonsoft.Json; namespace Seq.Api.Model.Data @@ -50,6 +51,12 @@ public class QueryResultPart /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public TimeseriesPart[] Series { get; set; } + + /// + /// Result variables. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary Variables { get; set; } /// /// On error only, a description of the error. diff --git a/src/Seq.Api/Model/Link.cs b/src/Seq.Api/Model/Link.cs index 6d48fc8..4e95246 100644 --- a/src/Seq.Api/Model/Link.cs +++ b/src/Seq.Api/Model/Link.cs @@ -26,7 +26,7 @@ namespace Seq.Api.Model /// parameterized in order to produce a complete URI (if the template contains no /// parameters then it may also be a literal URI). /// - public struct Link + public readonly struct Link { /// /// An empty link. diff --git a/src/Seq.Api/Model/Security/Permission.cs b/src/Seq.Api/Model/Security/Permission.cs index 8c8b015..9ed273e 100644 --- a/src/Seq.Api/Model/Security/Permission.cs +++ b/src/Seq.Api/Model/Security/Permission.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; + namespace Seq.Api.Model.Security { /// @@ -46,10 +48,29 @@ public enum Permission /// Write-access to signals, alerts, preferences etc. /// Write, - + /// /// Access to administrative features of Seq, management of other users, app installation, backups. /// + [Obsolete("The `Setup` permission has been replaced by `Project` and `System`.")] Setup, + + /// + /// Access to settings that control data ingestion, storage, dashboarding and alerting. + /// + Project, + + /// + /// Access to settings and features that interact with, or provide access to, the underlying host server, + /// such as app (plug-in) installation, backup settings, cluster configuration, diagnostics, and features + /// relying on outbound network access like package feeds and update checks. This permission is required for + /// configuration of the authentication provider and related settings. + /// + System, + + /// + /// Create, edit, and delete user accounts, reset local user passwords. + /// + Organization } } diff --git a/src/Seq.Api/Model/Security/RoleEntity.cs b/src/Seq.Api/Model/Security/RoleEntity.cs index 57ad0e8..e8a66e5 100644 --- a/src/Seq.Api/Model/Security/RoleEntity.cs +++ b/src/Seq.Api/Model/Security/RoleEntity.cs @@ -29,6 +29,11 @@ public class RoleEntity : Entity /// /// Permissions granted to users in the role. /// - public HashSet Permissions { get; set; } = new HashSet(); + public HashSet Permissions { get; set; } = new(); + + /// + /// Optionally, an extended description of the role. + /// + public string Description { get; set; } } } diff --git a/src/Seq.Api/Model/Shared/EvaluationContextPart.cs b/src/Seq.Api/Model/Shared/EvaluationContextPart.cs new file mode 100644 index 0000000..7538029 --- /dev/null +++ b/src/Seq.Api/Model/Shared/EvaluationContextPart.cs @@ -0,0 +1,39 @@ +// Copyright © Datalust and contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; +using Seq.Api.Model.Signals; + +#nullable enable + +namespace Seq.Api.Model.Shared +{ + /// + /// Specifies the context that queries and searches execute within. + /// + public class EvaluationContextPart + { + /// + /// An unsaved or modified signal. + /// + public SignalEntity? Signal { get; set; } + + /// + /// Values for free variables that appear in the query or search condition. + /// + /// Variables will only be visible in the query or search being executed: any free variables + /// in signal filters will remain undefined. + public Dictionary? Variables { get; set; } + } +} diff --git a/src/Seq.Api/Model/Signals/SignalEntity.cs b/src/Seq.Api/Model/Signals/SignalEntity.cs index 38f8e62..9c90ba2 100644 --- a/src/Seq.Api/Model/Signals/SignalEntity.cs +++ b/src/Seq.Api/Model/Signals/SignalEntity.cs @@ -64,7 +64,7 @@ public SignalEntity() public bool? IsRestricted { get; set; } /// - /// If true, the signal can only be modified by users with the permission. + /// If true, the signal can only be modified by users with the permission. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs b/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs index 528c865..49b3b15 100644 --- a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs +++ b/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs @@ -46,7 +46,7 @@ public SqlQueryEntity() public string Sql { get; set; } /// - /// If true, only users with permission can edit the signal. + /// If true, only users with permission can edit the SQL query. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/Users/SearchHistoryItemStatus.cs b/src/Seq.Api/Model/Users/SearchHistoryItemAction.cs similarity index 90% rename from src/Seq.Api/Model/Users/SearchHistoryItemStatus.cs rename to src/Seq.Api/Model/Users/SearchHistoryItemAction.cs index 7944494..b374beb 100644 --- a/src/Seq.Api/Model/Users/SearchHistoryItemStatus.cs +++ b/src/Seq.Api/Model/Users/SearchHistoryItemAction.cs @@ -17,7 +17,7 @@ namespace Seq.Api.Model.Users /// /// An operation applied to a search history item. /// - public enum SearchHistoryItemStatus + public enum SearchHistoryItemAction { /// /// The item was used (make it more recent). @@ -28,10 +28,10 @@ public enum SearchHistoryItemStatus /// The item has been pinned. /// Pinned, - + /// - /// The item has been un-pinned. + /// The item has been unpinned. /// - Forgotten + Unpinned } } \ No newline at end of file diff --git a/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs b/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs index 1014a7a..6465910 100644 --- a/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs +++ b/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ReSharper disable ClassNeverInstantiated.Global + namespace Seq.Api.Model.Users { /// @@ -20,13 +22,13 @@ namespace Seq.Api.Model.Users public class SearchHistoryItemPart { /// - /// The filter entered by the user into the filter bar. + /// The search or query entered by the user into the search bar. /// public string Search { get; set; } /// /// Status to apply to the search history item. /// - public SearchHistoryItemStatus Status { get; set; } + public SearchHistoryItemAction Action { get; set; } } } diff --git a/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs b/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs index 23bd0cf..a674084 100644 --- a/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs +++ b/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs @@ -39,7 +39,7 @@ public class WorkspaceEntity : Entity public string OwnerId { get; set; } /// - /// If true, only users with the permission can modify the workspace. + /// If true, only users with the permission can modify the workspace. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs index d8cc53a..a2a7a70 100644 --- a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Data; +using Seq.Api.Model.Shared; using Seq.Api.Model.Signals; namespace Seq.Api.ResourceGroups @@ -41,6 +42,7 @@ internal DataResourceGroup(ILoadResourceGroup connection) /// A constructed signal that may not appear on the server, for example, a that has been /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. /// The query timeout; if not specified, the query will run until completion. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. /// A structured result set. public async Task QueryAsync( @@ -50,10 +52,11 @@ public async Task QueryAsync( SignalExpressionPart signal = null, SignalEntity unsavedSignal = null, TimeSpan? timeout = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, out var body, out var parameters); - return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); + MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, variables, out var body, out var parameters); + return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } /// @@ -66,6 +69,7 @@ public async Task QueryAsync( /// A constructed signal that may not appear on the server, for example, a that has been /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. /// The query timeout; if not specified, the query will run until completion. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. /// A CSV result set. public async Task QueryCsvAsync( @@ -75,9 +79,10 @@ public async Task QueryCsvAsync( SignalExpressionPart signal = null, SignalEntity unsavedSignal = null, TimeSpan? timeout = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, out var body, out var parameters); + MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, variables, out var body, out var parameters); parameters.Add("format", "text/csv"); return await GroupPostReadStringAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } @@ -89,7 +94,8 @@ static void MakeParameters( SignalExpressionPart signal, SignalEntity unsavedSignal, TimeSpan? timeout, - out SignalEntity body, + Dictionary variables, + out EvaluationContextPart body, out Dictionary parameters) { parameters = new Dictionary @@ -109,7 +115,7 @@ static void MakeParameters( if (timeout != null) parameters.Add("timeoutMS", timeout.Value.TotalMilliseconds.ToString("0")); - body = unsavedSignal ?? new SignalEntity(); + body = new EvaluationContextPart { Signal = unsavedSignal, Variables = variables }; } } } diff --git a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs index 96ee76f..bcb0194 100644 --- a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs @@ -14,7 +14,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Events; @@ -58,9 +58,10 @@ public async Task FindAsync( } /// - /// Retrieve a list of events that match a set of conditions. The complete result is buffered into memory, - /// so if a large result set is expected, use InSignalAsync() and lastReadEventId to page the results. + /// Efficiently retrieve all events that match a set of conditions. /// + /// A constructed signal that may not appear on the server, for example, a that has been + /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. /// If provided, a signal expression describing the set of events that will be filtered for the result. /// A strict Seq filter expression to match (text expressions must be in double quotes). To /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use connection.Expressions.ToStrictAsync(). @@ -77,8 +78,10 @@ public async Task FindAsync( /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility /// with versions prior to 5.0, which did not mark permalinks explicitly. /// Token through which the operation can be cancelled. + /// Values for any free variables that appear in . /// The complete list of events, ordered from least to most recent. - public async Task> ListAsync( + public async IAsyncEnumerable EnumerateAsync( + SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, string filter = null, int count = 30, @@ -89,48 +92,48 @@ public async Task> ListAsync( DateTime? toDateUtc = null, int? shortCircuitAfter = null, string permalinkId = null, + Dictionary variables = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var parameters = new Dictionary { { "count", count } }; - if (signal != null) { parameters.Add("signal", signal.ToString()); } - if (filter != null) { parameters.Add("filter", filter); } - if (startAtId != null) { parameters.Add("startAtId", startAtId); } - if (afterId != null) { parameters.Add("afterId", afterId); } - if (render) { parameters.Add("render", true); } - if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } - if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } - if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } - if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } - - var chunks = new List>(); + // Limit server resource consumption. Assuming events are between 1000 and 10,000 bytes each, + // responses will be between 5 and 50 MB. + const int pageSize = 5000; + + var nextAfterId = afterId; var remaining = count; + var nextCount = Math.Min(remaining, pageSize); while (true) { - var resultSet = await GroupGetAsync("InSignal", parameters, cancellationToken).ConfigureAwait(false); - chunks.Add(resultSet.Events); - remaining -= resultSet.Events.Count; - - if (remaining <= 0) - break; + var resultSet = await PageAsync(unsavedSignal, signal, filter, nextCount, startAtId, nextAfterId, render, + fromDateUtc, toDateUtc, shortCircuitAfter, permalinkId, variables, cancellationToken).ConfigureAwait(false); - if (resultSet.Statistics.Status != ResultSetStatus.Partial) - break; + foreach (var evt in resultSet.Events) + { + yield return evt; + remaining -= 1; - parameters["afterId"] = resultSet.Statistics.LastReadEventId; - parameters["count"] = remaining; - } + if (remaining <= 0) + yield break; + } - var result = new List(chunks.Sum(c => c.Count)); - foreach (var evt in chunks.SelectMany(c => c)) - result.Add(evt); + if (remaining <= 0) + yield break; - return result; + if (resultSet.Statistics.Status == ResultSetStatus.Complete) + yield break; + + nextAfterId = resultSet.Statistics.LastReadEventId; + nextCount = Math.Min(remaining, pageSize); + } } + /// - /// Retrieve a list of events that match a set of conditions. The complete result is buffered into memory, - /// so if a large result set is expected, use InSignalAsync() and lastReadEventId to page the results. + /// Retrieve all events that match a set of conditions. The complete result is buffered into memory, + /// so if a large result set is expected, use EnumerateAsync(), or PageAsync() with + /// and to page the results. /// /// A constructed signal that may not appear on the server, for example, a that has been /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. @@ -149,9 +152,10 @@ public async Task> ListAsync( /// If the request is for a permalinked event, specifying the id of the permalink here will /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility /// with versions prior to 5.0, which did not mark permalinks explicitly. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. - /// The complete list of events, ordered from least to most recent. - public async Task InSignalAsync( + /// The result set with a page of events. + public async Task> ListAsync( SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, string filter = null, @@ -163,27 +167,23 @@ public async Task InSignalAsync( DateTime? toDateUtc = null, int? shortCircuitAfter = null, string permalinkId = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - var parameters = new Dictionary{{ "count", count }}; - if (signal != null) { parameters.Add("signal", signal.ToString()); } - if (filter != null) { parameters.Add("filter", filter); } - if (startAtId != null) { parameters.Add("startAtId", startAtId); } - if (afterId != null) { parameters.Add("afterId", afterId); } - if (render) { parameters.Add("render", true); } - if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } - if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } - if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } - if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } - - var body = unsavedSignal ?? new SignalEntity(); - return await GroupPostAsync("InSignal", body, parameters, cancellationToken).ConfigureAwait(false); + var results = new List(); + await foreach (var item in EnumerateAsync(unsavedSignal, signal, filter, count, startAtId, afterId, render, + fromDateUtc, toDateUtc, shortCircuitAfter, permalinkId, variables, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + results.Add(item); + return results; } /// - /// Retrieve a list of events that match a set of conditions. The complete result is buffered into memory, - /// so if a large result set is expected, use InSignalAsync() and lastReadEventId to page the results. + /// Retrieve a page of events that match a set of conditions. /// + /// A constructed signal that may not appear on the server, for example, a that has been + /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. /// If provided, a signal expression describing the set of events that will be filtered for the result. /// A strict Seq filter expression to match (text expressions must be in double quotes). To /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use connection.Expressions.ToStrictAsync(). @@ -199,10 +199,12 @@ public async Task InSignalAsync( /// If the request is for a permalinked event, specifying the id of the permalink here will /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility /// with versions prior to 5.0, which did not mark permalinks explicitly. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. - /// The complete list of events, ordered from least to most recent. - public async Task InSignalAsync( - SignalExpressionPart signal, + /// The result set with a page of events. + public async Task PageAsync( + SignalEntity unsavedSignal = null, + SignalExpressionPart signal = null, string filter = null, int count = 30, string startAtId = null, @@ -212,15 +214,11 @@ public async Task InSignalAsync( DateTime? toDateUtc = null, int? shortCircuitAfter = null, string permalinkId = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - if (signal == null) throw new ArgumentNullException(nameof(signal)); - - var parameters = new Dictionary - { - { "signal", signal.ToString() }, - { "count", count } - }; + var parameters = new Dictionary{{ "count", count }}; + if (signal != null) { parameters.Add("signal", signal.ToString()); } if (filter != null) { parameters.Add("filter", filter); } if (startAtId != null) { parameters.Add("startAtId", startAtId); } if (afterId != null) { parameters.Add("afterId", afterId); } @@ -230,7 +228,8 @@ public async Task InSignalAsync( if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } - return await GroupGetAsync("InSignal", parameters, cancellationToken).ConfigureAwait(false); + var body = new EvaluationContextPart { Signal = unsavedSignal, Variables = variables }; + return await GroupPostAsync("InSignal", body, parameters, cancellationToken).ConfigureAwait(false); } /// @@ -243,14 +242,16 @@ public async Task InSignalAsync( /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use connection.Expressions.ToStrictAsync(). /// Earliest (inclusive) date/time from which to delete. /// Latest (exclusive) date/time from which to delete. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. /// A result carrying the count of events deleted. - public async Task DeleteInSignalAsync( + public async Task DeleteAsync( SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, string filter = null, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { var parameters = new Dictionary(); @@ -259,8 +260,8 @@ public async Task DeleteInSignalAsync( if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } - var body = unsavedSignal ?? new SignalEntity(); - return await GroupDeleteAsync("DeleteInSignal", body, parameters, cancellationToken).ConfigureAwait(false); + var body = new EvaluationContextPart { Signal = unsavedSignal, Variables = variables }; + return await GroupDeleteAsync("DeleteInSignal", body, parameters, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index 140e72b..caa9edf 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,9 +1,9 @@ Client library for the Seq HTTP API. - 2021.4.0 + 2022.1.0 Datalust;Contributors - netstandard2.0 + netstandard2.0;net6.0 true true seq @@ -11,7 +11,7 @@ seq-api-icon.png https://github.com/datalust/seq-api Apache-2.0 - 8 + 9 @@ -19,6 +19,11 @@ + + + + + diff --git a/test/Seq.Api.Tests/Seq.Api.Tests.csproj b/test/Seq.Api.Tests/Seq.Api.Tests.csproj index 6545b6f..ce38adc 100644 --- a/test/Seq.Api.Tests/Seq.Api.Tests.csproj +++ b/test/Seq.Api.Tests/Seq.Api.Tests.csproj @@ -1,7 +1,7 @@  - net461;netcoreapp2.2 + net461;netcoreapp2.2;net6.0 true