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