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
23 changes: 21 additions & 2 deletions src/Seq.Api/Client/SeqApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ public async Task<TResponse> PostAsync<TEntity, TResponse>(ILinked entity, strin
return _serializer.Deserialize<TResponse>(new JsonTextReader(new StreamReader(stream)));
}

// Callers are expected to derive 400 error information from the response stream. All other result status codes throw.
internal async Task<TResponse> TryPostAsync<TEntity, TResponse>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping these internal until it's clear there's a general pattern worth exposing.

{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpTrySendAsync(request, cancellationToken).ConfigureAwait(false);
return _serializer.Deserialize<TResponse>(new JsonTextReader(new StreamReader(stream)));
}
/// <summary>
/// Issue a <c>POST</c> request accepting a serialized <typeparamref name="TEntity"/> and returning a string by following <paramref name="link"/> from <paramref name="entity"/>.
/// </summary>
Expand Down Expand Up @@ -452,8 +460,19 @@ async Task<string> HttpGetStringAsync(string url, CancellationToken cancellation
#endif
).ConfigureAwait(false);
}


// Throws on 5xx errors; callers are expected to derive 400 error information from the response stream.
async Task<Stream> HttpTrySendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
return await HttpSendAsyncCore(request, throwOn400: false, cancellationToken);
}

async Task<Stream> HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
return await HttpSendAsyncCore(request, throwOn400: true, cancellationToken);
}

async Task<Stream> HttpSendAsyncCore(HttpRequestMessage request, bool throwOn400, CancellationToken cancellationToken)
{
var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync(
Expand All @@ -462,7 +481,7 @@ async Task<Stream> HttpSendAsync(HttpRequestMessage request, CancellationToken c
#endif
).ConfigureAwait(false);

if (response.IsSuccessStatusCode)
if (response.IsSuccessStatusCode || (!throwOn400 && response.StatusCode == HttpStatusCode.BadRequest))
{
return stream;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Seq.Api/Model/Alerting/AlertActivityPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ public class AlertActivityPart
/// <summary>
/// The most recent occurrences of the alert that triggered notifications.
/// </summary>
public List<AlertOccurrencePart> RecentOccurrences { get; set; } = new List<AlertOccurrencePart>();
public List<AlertOccurrencePart> RecentOccurrences { get; set; } = new();

/// <summary>
/// Minimal metrics for the most recent occurrences of the alert that triggered notifications.
/// The metrics in this list are a superset of <see cref="RecentOccurrences"/>.
/// </summary>
public List<AlertOccurrenceRangePart> RecentOccurrenceRanges { get; set; } =
new List<AlertOccurrenceRangePart>();
new();

/// <summary>
/// The number of times this alert has been triggered since its creation.
Expand Down
14 changes: 12 additions & 2 deletions src/Seq.Api/Model/Alerting/AlertEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,21 @@ public class AlertEntity : Entity
/// </summary>
public bool IsDisabled { get; set; }

/// <summary>
/// The source of the data for the query.
/// </summary>
public DataSource DataSource { get; set; } = DataSource.Stream;

/// <summary>
/// Lateral joins applied to the data source.
/// </summary>
public List<JoinPart> Joins { get; set; } = [];

/// <summary>
/// An optional <see cref="SignalExpressionPart"/> limiting the data source that triggers the alert.
/// </summary>
public SignalExpressionPart SignalExpression { get; set; }

/// <summary>
/// An optional <c>where</c> clause limiting the data source that triggers the alert.
/// </summary>
Expand All @@ -75,7 +85,7 @@ public class AlertEntity : Entity
/// <summary>
/// The individual measurements that will be tested by the alert condition.
/// </summary>
public List<ColumnPart> Select { get; set; } = new List<ColumnPart>();
public List<ColumnPart> Select { get; set; } = [];

/// <summary>
/// The alert condition. This is a <c>having</c> clause over the grouped results
Expand Down
2 changes: 1 addition & 1 deletion src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ public class AlertOccurrencePart
/// <summary>
/// The <see cref="NotificationChannelPart">NotificationChannelParts</see> that were alerted.
/// </summary>
public List<AlertNotificationPart> Notifications { get; set; } = new List<AlertNotificationPart>();
public List<AlertNotificationPart> Notifications { get; set; } = new();
}
}
2 changes: 1 addition & 1 deletion src/Seq.Api/Model/Alerting/NotificationChannelPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ public class NotificationChannelPart
/// by the alert.
/// </summary>
public Dictionary<string, string> NotificationAppSettingOverrides { get; set; } =
new Dictionary<string, string>();
new();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ public class AppInstanceOutputMetricsPart
/// it being processed by the app.
/// </summary>
public int DispatchedEventsPerMinute { get; set; }

/// <summary>
/// The number of events per minute that failed to reach the app from Seq. Includes streamed events (if enabled),
/// manual invocations, and alert notifications. This value doesn't track events the app may have internally
/// failed to process.
/// </summary>
public int FailedEventsPerMinute { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/Seq.Api/Model/Data/QueryResultPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class QueryResultPart
/// <summary>
/// The columns within the result set (at various levels of the hierarchy).
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string[] Columns { get; set; }

/// <summary>
Expand All @@ -43,6 +44,7 @@ public class QueryResultPart
/// <summary>
/// Metadata for the time grouping column.
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public ColumnMetadataPart TimeColumnMetadata { get; set; }

/// <summary>
Expand Down
26 changes: 18 additions & 8 deletions src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,37 @@ namespace Seq.Api.Model.Metrics;
public enum MetricAggregationPreference
{
/// <summary>
/// The <c>count()</c> aggregate function.
/// The total count of observed values.
/// </summary>
Count,

Total,
/// <summary>
/// The <c>sum()</c> aggregate function.
/// The sum of all observed values.
/// </summary>
Sum,

/// <summary>
/// The <c>min()</c> aggregate function.
/// The counts of values falling in each histogram bucket.
/// </summary>
BucketSum,

/// <summary>
/// The smallest observed value.
/// </summary>
Min,

/// <summary>
/// The <c>mean()</c> aggregate function.
/// The center observed value.
/// </summary>
Mean,

/// <summary>
/// The <c>max()</c> aggregate function.
/// The largest observed value.
/// </summary>
Max,

/// <summary>
/// The set of values greater than a percentage of all other observed values.
/// </summary>
Max
Percentiles
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@

using Seq.Api.Model.Security;

namespace Seq.Api.Model.SqlQueries
namespace Seq.Api.Model.Queries
{
/// <summary>
/// A saved SQL-style query.
/// A saved query.
/// </summary>
public class SqlQueryEntity : Entity
public class QueryEntity : Entity
{
/// <summary>
/// Construct a <see cref="SqlQueryEntity"/>.
/// Construct a <see cref="QueryEntity"/>.
/// </summary>
public SqlQueryEntity()
public QueryEntity()
{
Title = "New SQL Query";
Title = "New Query";
Sql = "";
}

Expand Down
31 changes: 31 additions & 0 deletions src/Seq.Api/Model/Shared/JoinKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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.

namespace Seq.Api.Model.Shared;

/// <summary>
/// A type of relational join.
/// </summary>
public enum JoinKind
{
/// <summary>
/// The unknown join kind.
/// </summary>
Unknown,

/// <summary>
/// A join type that joins each row to the output of a table function evaluated in the context of the row.
/// </summary>
Lateral
}
38 changes: 38 additions & 0 deletions src/Seq.Api/Model/Shared/JoinPart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.

namespace Seq.Api.Model.Shared;

#nullable enable

/// <summary>
/// The lateral cross join part of a from clause.
/// </summary>
public class JoinPart
{
/// <summary>
/// The type of relational join.
/// </summary>
public JoinKind Kind { get; set; }

/// <summary>
/// The set function call used in the lateral join.
/// </summary>
public string? SetFunctionCall { get; set; }

/// <summary>
/// The alias of the set function call.
/// </summary>
public string? Alias { get; set; }
}
4 changes: 2 additions & 2 deletions src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
using Seq.Api.Model.Dashboarding;
using Seq.Api.Model.Metrics;
using Seq.Api.Model.Signals;
using Seq.Api.Model.SqlQueries;
using Seq.Api.Model.Queries;

namespace Seq.Api.Model.Workspaces
{
Expand All @@ -31,7 +31,7 @@ public class WorkspaceContentPart
public List<string> SignalIds { get; set; } = [];

/// <summary>
/// A list of <see cref="SqlQueryEntity"/> ids to include in the workspace.
/// A list of <see cref="QueryEntity"/> ids to include in the workspace.
/// </summary>
public List<string> QueryIds { get; set; } = [];

Expand Down
7 changes: 7 additions & 0 deletions src/Seq.Api/ResourceGroups/ApiResourceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ protected async Task<TResponse> GroupPostAsync<TEntity, TResponse>(string link,
var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false);
return await Client.PostAsync<TEntity, TResponse>(group, link, content, parameters, cancellationToken).ConfigureAwait(false);
}

// Callers are expected to derive 400 error information from the response stream. All other result status codes throw.
internal async Task<TResponse> GroupTryPostAsync<TEntity, TResponse>(string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false);
return await Client.TryPostAsync<TEntity, TResponse>(group, link, content, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Update an entity.
Expand Down
34 changes: 33 additions & 1 deletion src/Seq.Api/ResourceGroups/DataResourceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ internal DataResourceGroup(ILoadResourceGroup connection)
}

/// <summary>
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>.
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>. For non-throwing
/// syntax error reporting, see <see cref="TryQueryAsync"/>.
/// </summary>
/// <param name="query">The query to execute.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include events in the query result.</param>
Expand Down Expand Up @@ -61,6 +62,37 @@ public async Task<QueryResultPart> QueryAsync(
return await GroupPostAsync<EvaluationContextPart, QueryResultPart>("Query", body, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>. This method
/// differs from <see cref="QueryAsync"/> by returning a result object with error information instead of throwing
/// when the query syntax is invalid.
/// </summary>
/// <param name="query">The query to execute.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include events in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which events are included in the query result. The default is the current time.</param>
/// <param name="signal">A signal expression over which the query will be executed.</param>
/// <param name="unsavedSignal">A constructed signal that may not appear on the server, for example, a <see cref="SignalEntity"/> that has been
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.</param>
/// <param name="timeout">The query timeout; if not specified, the query will run until completion.</param>
/// <param name="variables">Values for any free variables that appear in <paramref name="query"/>.</param>
/// <param name="trace">Enable detailed (server-side) query tracing.</param>
/// <param name="cancellationToken">Token through which the operation can be cancelled.</param>
/// <returns>A structured result set or syntax/execution error information.</returns>
public async Task<QueryResultPart> TryQueryAsync(
string query,
DateTime? rangeStartUtc = null,
DateTime? rangeEndUtc = null,
SignalExpressionPart signal = null,
SignalEntity unsavedSignal = null,
TimeSpan? timeout = null,
Dictionary<string, object> variables = null,
bool trace = false,
CancellationToken cancellationToken = default)
{
MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, variables, trace, out var body, out var parameters);
return await GroupTryPostAsync<EvaluationContextPart, QueryResultPart>("Query", body, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>.
/// </summary>
Expand Down
Loading
Loading