diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6376137 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +# If this file is renamed, the incrementing run attempt number will be reset. + +name: CI + +on: + push: + branches: [ "dev", "main" ] + pull_request: + branches: [ "dev", "main" ] + +env: + CI_BUILD_NUMBER_BASE: ${{ github.run_number }} + CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} + +jobs: + build: + + runs-on: ubuntu-24.04 + + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + - name: Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + - name: Compute build number + run: | + echo "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER_BASE+200))" >> $GITHUB_ENV + - name: Build and Publish + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: pwsh + run: | + ./Build.ps1 diff --git a/Build.ps1 b/Build.ps1 index 40b95c7..952dce0 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,69 +1,77 @@ -# This script originally (c) 2016 Serilog Contributors - license Apache 2.0 - -echo "build: Build started" +Write-Output "build: Build started" Push-Location $PSScriptRoot +Write-Output "build: Tool versions follow" + +dotnet --version +dotnet --list-sdks + if(Test-Path .\artifacts) { - echo "build: Cleaning .\artifacts" - Remove-Item .\artifacts -Force -Recurse + Write-Output "build: Cleaning ./artifacts" + Remove-Item ./artifacts -Force -Recurse } & dotnet restore --no-cache -if($LASTEXITCODE -ne 0) { exit 1 } -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; -$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"] +$dbp = [Xml] (Get-Content .\Directory.Build.props) +$versionPrefix = $dbp.Project.PropertyGroup.VersionPrefix + +Write-Output "build: Package version prefix is $versionPrefix" + +$branch = @{ $true = $env:CI_TARGET_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:CI_TARGET_BRANCH]; +$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:CI_BUILD_NUMBER]; +$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"}[$branch -eq "main" -and $revision -ne "local"] -echo "build: Version suffix is $suffix" +Write-Output "build: Package version suffix is $suffix" -foreach ($src in ls src/*) { +foreach ($src in Get-ChildItem src/*) { Push-Location $src - echo "build: Packaging project in $src" + Write-Output "build: Packaging project in $src" if ($suffix) { - & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix + & dotnet pack -c Release -o ../../artifacts --version-suffix=$suffix } else { - & dotnet pack -c Release -o ..\..\artifacts + & dotnet pack -c Release -o ../../artifacts } - if($LASTEXITCODE -ne 0) { exit 1 } + if($LASTEXITCODE -ne 0) { throw "Packaging failed" } Pop-Location } -foreach ($test in ls test/*.Tests) { - Push-Location $test - - echo "build: Testing project in $test" - - & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } +Write-Output "build: Checking complete solution builds" +& dotnet build +if($LASTEXITCODE -ne 0) { throw "Solution build failed" } - Pop-Location -} -foreach ($test in ls test/*.Benchmarks) { +foreach ($test in Get-ChildItem test/*.Tests) { Push-Location $test - echo "build: Building performance test project in $test" + Write-Output "build: Testing project in $test" - & dotnet build -c Release - if($LASTEXITCODE -ne 0) { exit 2 } + & dotnet test -c Release + if($LASTEXITCODE -ne 0) { throw "Testing failed" } Pop-Location } -foreach ($sample in ls example/*) { - Push-Location $sample +Pop-Location - echo "build: Building sample project in $sample" +if ($env:NUGET_API_KEY) { + # GitHub Actions will only supply this to branch builds and not PRs. We publish + # builds from any branch this action targets (i.e. main and dev). + + Write-Output "build: Publishing NuGet packages" + + foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) { + & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg" + if($LASTEXITCODE -ne 0) { throw "Publishing failed" } + } - & dotnet build -c Release - if($LASTEXITCODE -ne 0) { exit 2 } + if (!($suffix)) { + Write-Output "build: Creating release for version $versionPrefix" - Pop-Location + iex "gh release create v$versionPrefix --title v$versionPrefix --generate-notes $(get-item ./artifacts/*.nupkg) $(get-item ./artifacts/*.snupkg)" + } } - -Pop-Location diff --git a/Directory.Build.props b/Directory.Build.props index 2deb00d..c86a925 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,11 @@ - - - latest - - \ No newline at end of file + + latest + true + 2026.1.0 + true + true + snupkg + NETSDK1138 + + diff --git a/README.md b/README.md index acda6e5..60f29ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Seq HTTP API Client [![Build status](https://ci.appveyor.com/api/projects/status/bhtx25hyqmmdqhvt?svg=true)](https://ci.appveyor.com/project/datalust/seq-api) [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Seq.Api.svg)](https://nuget.org/packages/seq.api) +# Seq HTTP API Client [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Seq.Api.svg)](https://nuget.org/packages/seq.api) This library includes: diff --git a/seq-api.sln b/seq-api.sln index 883a5c6..2005812 100644 --- a/seq-api.sln +++ b/seq-api.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{1C66 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{7DCED657-7E83-4E6F-AB14-5753E044304D}" ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 LICENSE = LICENSE README.md = README.md diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs index fed86cb..14f2c70 100644 --- a/src/Seq.Api/Client/SeqApiClient.cs +++ b/src/Seq.Api/Client/SeqApiClient.cs @@ -41,7 +41,7 @@ public sealed class SeqApiClient : IDisposable // 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 SeqApiVersionedMediaType = "application/vnd.datalust.seq.v13+json"; + const string SeqApiVersionedMediaType = "application/vnd.datalust.seq.v14+json"; const string ApiKeyHeaderName = "X-Seq-ApiKey"; const string CsrfTokenHeaderName = "X-Seq-CsrfToken"; @@ -191,6 +191,14 @@ public async Task PostAsync(ILinked entity, strin return _serializer.Deserialize(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 TryPostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) + { + 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(new JsonTextReader(new StreamReader(stream))); + } /// /// Issue a POST request accepting a serialized and returning a string by following from . /// @@ -452,8 +460,19 @@ async Task 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 HttpTrySendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) + { + return await HttpSendAsyncCore(request, throwOn400: false, cancellationToken); + } + async Task HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) + { + return await HttpSendAsyncCore(request, throwOn400: true, cancellationToken); + } + + async Task HttpSendAsyncCore(HttpRequestMessage request, bool throwOn400, CancellationToken cancellationToken) { var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync( @@ -462,7 +481,7 @@ async Task HttpSendAsync(HttpRequestMessage request, CancellationToken c #endif ).ConfigureAwait(false); - if (response.IsSuccessStatusCode) + if (response.IsSuccessStatusCode || (!throwOn400 && response.StatusCode == HttpStatusCode.BadRequest)) { return stream; } @@ -531,7 +550,7 @@ static string EnsureAbsoluteWebSocketUri(string target, Uri apiBaseAddress) absoluteUnknownScheme.Scheme = absoluteUnknownScheme.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) || absoluteUnknownScheme.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase) ? "ws" : "wss"; - + return absoluteUnknownScheme.ToString(); } diff --git a/src/Seq.Api/Model/Alerting/AlertActivityPart.cs b/src/Seq.Api/Model/Alerting/AlertActivityPart.cs index 97f25f5..340e9f1 100644 --- a/src/Seq.Api/Model/Alerting/AlertActivityPart.cs +++ b/src/Seq.Api/Model/Alerting/AlertActivityPart.cs @@ -38,14 +38,14 @@ public class AlertActivityPart /// /// The most recent occurrences of the alert that triggered notifications. /// - public List RecentOccurrences { get; set; } = new List(); + public List RecentOccurrences { get; set; } = new(); /// /// Minimal metrics for the most recent occurrences of the alert that triggered notifications. /// The metrics in this list are a superset of . /// public List RecentOccurrenceRanges { get; set; } = - new List(); + new(); /// /// The number of times this alert has been triggered since its creation. diff --git a/src/Seq.Api/Model/Alerting/AlertEntity.cs b/src/Seq.Api/Model/Alerting/AlertEntity.cs index ad8bab1..d39927d 100644 --- a/src/Seq.Api/Model/Alerting/AlertEntity.cs +++ b/src/Seq.Api/Model/Alerting/AlertEntity.cs @@ -51,11 +51,21 @@ public class AlertEntity : Entity /// public bool IsDisabled { get; set; } + /// + /// The source of the data for the query. + /// + public DataSource DataSource { get; set; } = DataSource.Stream; + + /// + /// Lateral joins applied to the data source. + /// + public List Joins { get; set; } = []; + /// /// An optional limiting the data source that triggers the alert. /// public SignalExpressionPart SignalExpression { get; set; } - + /// /// An optional where clause limiting the data source that triggers the alert. /// @@ -75,7 +85,7 @@ public class AlertEntity : Entity /// /// The individual measurements that will be tested by the alert condition. /// - public List Select { get; set; } = new List(); + public List Select { get; set; } = []; /// /// The alert condition. This is a having clause over the grouped results diff --git a/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs b/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs index 8104e3d..fd04f7a 100644 --- a/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs +++ b/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs @@ -28,6 +28,6 @@ public class AlertOccurrencePart /// /// The NotificationChannelParts that were alerted. /// - public List Notifications { get; set; } = new List(); + public List Notifications { get; set; } = new(); } } \ No newline at end of file diff --git a/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs b/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs index 37988dc..bd901a9 100644 --- a/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs +++ b/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs @@ -58,6 +58,6 @@ public class NotificationChannelPart /// by the alert. /// public Dictionary NotificationAppSettingOverrides { get; set; } = - new Dictionary(); + new(); } } diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs b/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs index d0f36bb..4e1c114 100644 --- a/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs +++ b/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs @@ -11,5 +11,12 @@ public class AppInstanceOutputMetricsPart /// it being processed by the app. /// public int DispatchedEventsPerMinute { get; set; } + + /// + /// 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. + /// + public int FailedEventsPerMinute { get; set; } } } diff --git a/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs b/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs index 2f84f5b..08086b1 100644 --- a/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs +++ b/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs @@ -28,10 +28,20 @@ public class ChartQueryPart /// public string Id { get; set; } + /// + /// The source of the data displayed by the chart. + /// + public DataSource DataSource { get; set; } = DataSource.Stream; + + /// + /// Lateral joins applied to the data source. + /// + public List Joins { get; set; } = []; + /// /// Individual measurements included in the query. These are effectively projected columns. /// - public List Measurements { get; set; } = new List(); + public List Measurements { get; set; } = []; /// /// An optional filtering where clause limiting the data that contributes to the chart. @@ -46,12 +56,12 @@ public class ChartQueryPart /// /// A series of expressions used to group data returned by the query. /// - public List GroupBy { get; set; } = new List(); + public List GroupBy { get; set; } = []; /// /// How measurements included in the chart will be displayed. /// - public MeasurementDisplayStylePart DisplayStyle { get; set; } = new MeasurementDisplayStylePart(); + public MeasurementDisplayStylePart DisplayStyle { get; set; } = new(); /// /// A filter that limits which groups will be displayed on the chart. Not supported by all chart types. @@ -61,7 +71,7 @@ public class ChartQueryPart /// /// An ordering applied to the results of the query; not supported by all chart types. /// - public List OrderBy { get; set; } = new List(); + public List OrderBy { get; set; } = []; /// /// The row limit used for the query. By default, a server-determined limit will be applied. diff --git a/src/Seq.Api/Model/Data/QueryResultPart.cs b/src/Seq.Api/Model/Data/QueryResultPart.cs index cbd14ea..0cbffa4 100644 --- a/src/Seq.Api/Model/Data/QueryResultPart.cs +++ b/src/Seq.Api/Model/Data/QueryResultPart.cs @@ -32,6 +32,7 @@ public class QueryResultPart /// /// The columns within the result set (at various levels of the hierarchy). /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public string[] Columns { get; set; } /// @@ -43,6 +44,7 @@ public class QueryResultPart /// /// Metadata for the time grouping column. /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public ColumnMetadataPart TimeColumnMetadata { get; set; } /// diff --git a/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStatisticsPart.cs b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStatisticsPart.cs new file mode 100644 index 0000000..918ec4a --- /dev/null +++ b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStatisticsPart.cs @@ -0,0 +1,45 @@ +// 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.Diagnostics.ColumnStore; + +/// +/// Describes a particular (column name, encoding) pair. Storage for each column is broken down by encoding because +/// the summary information available for each encoding differs. +/// +public class ColumnStatisticsPart +{ + /// + /// The name of the column. For most user-provided data, such as metrics or labels, the column name will + /// correspond to the metric or label name. Complex objects are decomposed into individual columns per sub-property. + /// Note that these names approximate the Seq query language syntax for referring to the column, but this is + /// inexact. + /// + public string Name { get; set; } + + /// + /// A textual description of the native encoding scheme used for the column data. + /// + public string Encoding { get; set; } + + /// + /// The number of rows in which the column has a defined value. + /// + public long Count { get; set; } + + /// + /// The full on-disk size of the encoded column data, excluding framing details such as CRC blocks, padding, and + /// file layout overhead (these are usually trivial). + /// + public long SizeBytes { get; set; } +} diff --git a/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStoreStatisticsPart.cs b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStoreStatisticsPart.cs new file mode 100644 index 0000000..c40240a --- /dev/null +++ b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStoreStatisticsPart.cs @@ -0,0 +1,28 @@ +// 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; + +namespace Seq.Api.Model.Diagnostics.ColumnStore; + +/// +/// Provides information about the internal column store that drives Seq's metrics features. +/// +public class ColumnStoreStatisticsPart +{ + /// + /// The columns stored in the column store. + /// + public List Columns { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Events/EventEntity.cs b/src/Seq.Api/Model/Events/EventEntity.cs index 5d8a7ab..fbfa96b 100644 --- a/src/Seq.Api/Model/Events/EventEntity.cs +++ b/src/Seq.Api/Model/Events/EventEntity.cs @@ -116,5 +116,11 @@ public class EventEntity : Entity /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public TimeSpan? Elapsed { get; set; } + + /// + /// If the event is a metric, the definitions of its metric samples. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public List Definitions { get; set; } } } diff --git a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs index bb0c4a6..bb9b284 100644 --- a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs +++ b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs @@ -47,7 +47,7 @@ public class ApiKeyEntity : Entity /// /// Settings that control how events are ingested through the API key. /// - public InputSettingsPart InputSettings { get; set; } = new InputSettingsPart(); + public InputSettingsPart InputSettings { get; set; } = new(); /// /// If true, the key is the built-in (tokenless) API key representing unauthenticated HTTP ingestion. @@ -63,12 +63,12 @@ public class ApiKeyEntity : Entity /// The s assigned to the API key. Note that, if the API key is owned by an individual user, permissions /// not held by the user will be ignored by the server. /// - public HashSet AssignedPermissions { get; set; } = new HashSet(); + public HashSet AssignedPermissions { get; set; } = []; /// /// Information about the ingestion activity using this API key. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputMetricsPart InputMetrics { get; set; } = new InputMetricsPart(); + public InputMetricsPart InputMetrics { get; set; } = new(); } } diff --git a/src/Seq.Api/Model/Inputs/InputSettingsPart.cs b/src/Seq.Api/Model/Inputs/InputSettingsPart.cs index 3eae56d..c0159f3 100644 --- a/src/Seq.Api/Model/Inputs/InputSettingsPart.cs +++ b/src/Seq.Api/Model/Inputs/InputSettingsPart.cs @@ -27,12 +27,12 @@ public class InputSettingsPart /// Properties that will be automatically added to all events ingested using the key. These will override any properties with /// the same names already present on the event. /// - public List AppliedProperties { get; set; } = new List(); + public List AppliedProperties { get; set; } = []; /// /// A filter that selects events to ingest. If null, all events received using the key will be ingested. /// - public DescriptiveFilterPart Filter { get; set; } = new DescriptiveFilterPart(); + public DescriptiveFilterPart Filter { get; set; } = new(); /// /// A minimum level at which events received using the key will be ingested. The level hierarchy understood by Seq is fuzzy diff --git a/src/Seq.Api/Model/Metrics/DimensionFilterOperator.cs b/src/Seq.Api/Model/Metrics/DimensionFilterOperator.cs new file mode 100644 index 0000000..2f5720c --- /dev/null +++ b/src/Seq.Api/Model/Metrics/DimensionFilterOperator.cs @@ -0,0 +1,23 @@ +#nullable enable +namespace Seq.Api.Model.Metrics; + +/// +/// A simplified representation of the expression encoded in a . +/// +public enum DimensionFilterOperator +{ + /// + /// The filter includes samples with for the dimension. + /// + Include, + + /// + /// The filter excludes samples with for the dimension. + /// + Exclude, + + /// + /// The filter includes samples with any value for the dimension. + /// + Has, +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Metrics/DimensionFilterPart.cs b/src/Seq.Api/Model/Metrics/DimensionFilterPart.cs new file mode 100644 index 0000000..37e248f --- /dev/null +++ b/src/Seq.Api/Model/Metrics/DimensionFilterPart.cs @@ -0,0 +1,24 @@ +#nullable enable +namespace Seq.Api.Model.Metrics; + +/// +/// A filter (requirement) applied to a metric dimension. +/// +public class DimensionFilterPart +{ + /// + /// The dimension. See . + /// + public required string Accessor { get; set; } + + /// + /// How the filter applies to the dimension. + /// + public DimensionFilterOperator Operator { get; set; } + + /// + /// When is or + /// , the value included or excluded by the filter. Ignored otherwise. + /// + public object? Value { get; set; } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs b/src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs new file mode 100644 index 0000000..e077c02 --- /dev/null +++ b/src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs @@ -0,0 +1,42 @@ +namespace Seq.Api.Model.Metrics; + +/// +/// The aggregate function(s) used when aggregating a metric by time interval for display. +/// +public enum MetricAggregationPreference +{ + /// + /// The total count of observed values. + /// + Total, + + /// + /// The sum of all observed values. + /// + Sum, + + /// + /// The counts of values falling in each histogram bucket. + /// + BucketSum, + + /// + /// The smallest observed value. + /// + Min, + + /// + /// The center observed value. + /// + Mean, + + /// + /// The largest observed value. + /// + Max, + + /// + /// The set of values greater than a percentage of all other observed values. + /// + Percentiles +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Metrics/MetricDimensionPart.cs b/src/Seq.Api/Model/Metrics/MetricDimensionPart.cs new file mode 100644 index 0000000..ed31e44 --- /dev/null +++ b/src/Seq.Api/Model/Metrics/MetricDimensionPart.cs @@ -0,0 +1,43 @@ +// 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 Newtonsoft.Json; + +namespace Seq.Api.Model.Metrics; + +/// +/// A description of a dimension (property/label, scope, or resource attribute) applied to metric samples. +/// +public class MetricDimensionPart +{ + /// + /// The dimension's accessor path, including the namespace, and using indexer syntax to refer to path elements that + /// are not valid identifiers in the Seq query language. This member is guaranteed to contain a syntactically-valid + /// expression that can be used directly in queries without manipulation. + /// + public string Accessor { get; set; } + + /// + /// The dimension's human-readable name, without namespace qualifiers such as @Resource, and potentially + /// including syntactically-invalid elements. Omitted if identical to the value of . + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string Name { get; set; } + + /// + /// The dimension's namespace. Omitted if the namespace is the default @Properties. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string Namespace { get; set; } +} diff --git a/src/Seq.Api/Model/Metrics/MetricPart.cs b/src/Seq.Api/Model/Metrics/MetricPart.cs new file mode 100644 index 0000000..dda104a --- /dev/null +++ b/src/Seq.Api/Model/Metrics/MetricPart.cs @@ -0,0 +1,75 @@ +// 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. + +#nullable enable +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Seq.Api.Model.Metrics; + +/// +/// The definition of a metric present in the server's series metrics store. +/// +/// +/// Because Seq's internal metric store uses a columnar, rather than timeseries, architecture, it does not impose a +/// fixed notion of metric identity. Instead, metric identity can be flexibly defined in the context of a metrics search. +/// A is the result of such a search, and carries the implied metric identity in its combination +/// of , , , and fields. +/// +public class MetricPart +{ + /// + /// The name of the metric, as a property accessor expression, using indexer syntax to refer to path elements that + /// are not valid identifiers in the Seq query language. This member is guaranteed to contain a syntactically-valid + /// expression that can be used directly in queries without manipulation. + /// + public required string Accessor { get; set; } + + /// + /// The metric's human-readable name, without namespace qualifiers such as @Resource, and potentially + /// including syntactically-invalid elements. Omitted if identical to the value of . + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? Name { get; set; } + + /// + /// The group within which this metric appears. The indexes into the array will correspond to the indexes of + /// the list of group key expressions provided when retrieving the definition. For example, if the definitions + /// are retrieved with ?group=@Resource.service.name,@Scope.name, then the contents of a returned group + /// key might resemble ["users_api", "AspNetCore.Server.Kestrel"]. + /// + public List GroupKey { get; set; } = []; + + /// + /// The type of data this metric records. This will typically be one of either + /// Sum, Gauge, or (exponential) Histogram. + /// + public required string Kind { get; set; } + + /// + /// A user-facing description of the metric, if one is supplied. + /// + public string? Description { get; set; } + + /// + /// The unit of measure for the metric samples, if one is supplied. + /// + public string? Unit { get; set; } + + /// + /// A where-clause compatible expression that restricts queries for metric data to only the samples + /// with the appropriate kind and unit, matched in the filter, and belonging to the group. + /// + public string? Condition { get; set; } +} diff --git a/src/Seq.Api/Model/Metrics/MetricSearchResultPart.cs b/src/Seq.Api/Model/Metrics/MetricSearchResultPart.cs new file mode 100644 index 0000000..e2f0aae --- /dev/null +++ b/src/Seq.Api/Model/Metrics/MetricSearchResultPart.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +#nullable enable + +namespace Seq.Api.Model.Metrics; + +/// +/// The result of a metric search. +/// +public class MetricSearchResultPart +{ + /// + /// The metrics matched by the search. + /// + public List Metrics { get; set; } = []; + + /// + /// The subset of each included metric's not derived + /// from , if any. Note that this does not + /// need to be included when retrieving samples for the metric: this field is supplied so that additional queries + /// can be generated utilizing the same data condition in the way already facilitated for groupings by + /// the property. + /// + public string? NonGroupingCondition { get; set; } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Metrics/StandaloneMetricPart.cs b/src/Seq.Api/Model/Metrics/StandaloneMetricPart.cs new file mode 100644 index 0000000..c9b0e98 --- /dev/null +++ b/src/Seq.Api/Model/Metrics/StandaloneMetricPart.cs @@ -0,0 +1,74 @@ +#nullable enable +using System.Collections.Generic; +using Newtonsoft.Json; +using Seq.Api.Model.Shared; + +namespace Seq.Api.Model.Metrics; + +/// +/// A captured for computation and display outside the context of the original search result in +/// which it appears. +/// +public class StandaloneMetricPart +{ + /// + /// The metric. + /// + public required MetricPart Metric { get; set; } + + /// + /// The expressions used to compute the metric's values. The + /// and values are combined with + /// in the metric's . + /// + /// + /// When an ephemeral metric search result is pinned, the group keys and conditions used when retrieving it + /// are "frozen", and attached to the pinned metric. + /// + public List GroupKeyExpressions { get; set; } = []; + + /// + /// The non-grouping portion of . The metric's , + /// , + /// and values are combined with + /// in the metric's . + /// + /// + /// When an ephemeral metric search result is pinned, the group keys and conditions used when retrieving it + /// are "frozen", and attached to the pinned metric. + /// + public string? NonGroupingCondition { get; set; } + + /// + /// When displaying the metric, whether the y-axis should use logarithmic tick intervals. If , + /// the y-axis ticks will be at intervals (linear). + /// + public bool UseLogarithmicScale { get; set; } + + /// + /// When displaying the metric, whether the y-axis should begin from zero. If , the y-axis + /// will begin at the value of the smallest data point. + /// + public bool ShowZero { get; set; } = true; + + /// + /// When displaying the metric, the aggregate function(s) used when aggregating a metric by time interval. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public MetricAggregationPreference? AggregationPreference { get; set; } + + /// + /// When displaying the metric, additional filters applied to the underlying data. + /// + public List DisplayExpressionFilters { get; set; } = []; + + /// + /// When displaying the metric, additional keys used to create on-chart group series. + /// + public List DisplayGroupKeyExpressions { get; set; } = []; + + /// + /// When displaying the metric, additional dimension-based filters applied to the underlying data. + /// + public List DisplayDimensionFilters { get; set; } = []; +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Metrics/ViewEntity.cs b/src/Seq.Api/Model/Metrics/ViewEntity.cs new file mode 100644 index 0000000..1c3e014 --- /dev/null +++ b/src/Seq.Api/Model/Metrics/ViewEntity.cs @@ -0,0 +1,61 @@ +#nullable enable +using System.Collections.Generic; +using Seq.Api.Model.Security; +using Seq.Api.Model.Shared; + +namespace Seq.Api.Model.Metrics; + +/// +/// A saved view in the Seq Metrics screen. +/// +public class ViewEntity: Entity +{ + /// + /// Construct a . + /// + public ViewEntity() + { + Title = "New View"; + } + + /// + /// The friendly, human-readable title of the view. + /// + public string Title { get; set; } + + /// + /// A long-form description of the view's purpose and contents. + /// + public string? Description { get; set; } + + /// + /// The user id of the user who owns the view; if null, the view is shared. + /// + public string? OwnerId { get; set; } + + /// + /// If true, the view can only be modified by users with the permission. + /// + public bool IsProtected { get; set; } + + /// + /// One or more expressions (normally, dimension accessor expressions such as @Scope.name) around which + /// metrics are split into separate charts. + /// + public List GroupKeyExpressions { get; set; } = []; + + /// + /// Expressions used to restrict the underlying metric data considered by the view. + /// + public List Filters { get; set; } = []; + + /// + /// Per-dimension filters used to restrict the underlying metric data considered by the view. + /// + public List DimensionFilters { get; set; } = []; + + /// + /// Individual metrics pinned in the view. + /// + public List PinnedMetrics { get; set; } = []; +} \ No newline at end of file diff --git a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs b/src/Seq.Api/Model/Queries/QueryEntity.cs similarity index 87% rename from src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs rename to src/Seq.Api/Model/Queries/QueryEntity.cs index 49b3b15..9630fc8 100644 --- a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs +++ b/src/Seq.Api/Model/Queries/QueryEntity.cs @@ -14,19 +14,19 @@ using Seq.Api.Model.Security; -namespace Seq.Api.Model.SqlQueries +namespace Seq.Api.Model.Queries { /// - /// A saved SQL-style query. + /// A saved query. /// - public class SqlQueryEntity : Entity + public class QueryEntity : Entity { /// - /// Construct a . + /// Construct a . /// - public SqlQueryEntity() + public QueryEntity() { - Title = "New SQL Query"; + Title = "New Query"; Sql = ""; } diff --git a/src/Seq.Api/Model/Retention/RetentionPolicyEntity.cs b/src/Seq.Api/Model/Retention/RetentionPolicyEntity.cs index 380f850..5efb028 100644 --- a/src/Seq.Api/Model/Retention/RetentionPolicyEntity.cs +++ b/src/Seq.Api/Model/Retention/RetentionPolicyEntity.cs @@ -14,33 +14,38 @@ using System; using Newtonsoft.Json; +using Seq.Api.Model.Shared; using Seq.Api.Model.Signals; -namespace Seq.Api.Model.Retention +namespace Seq.Api.Model.Retention; + +/// +/// A retention policy. Identifies a subset of events to delete at a specified age. +/// +public class RetentionPolicyEntity : Entity { /// - /// A retention policy. Identifies a subset of events to delete at a specified age. + /// The age at which events will be deleted by the policy. This is based on the + /// events' timestamps relative to the server's clock. + /// + public TimeSpan RetentionTime { get; set; } + + /// + /// An optional describing the set of events + /// to delete. If null, the policy will efficiently truncate the event store, + /// deleting all events. Supported only when is . /// - public class RetentionPolicyEntity : Entity - { - /// - /// The age at which events will be deleted by the policy. This is based on the - /// events' timestamps relative to the server's clock. - /// - public TimeSpan RetentionTime { get; set; } + public SignalExpressionPart RemovedSignalExpression { get; set; } - /// - /// An optional describing the set of events - /// to delete. If null, the policy will efficiently truncate the event store, - /// deleting all events. - /// - public SignalExpressionPart RemovedSignalExpression { get; set; } + /// + /// The data source to delete from. If unspecified, defaults to + /// + public DataSource DataSource { get; set; } = DataSource.Stream; - /// - /// Obsolete. - /// - [Obsolete("Replaced by RemovedSignalExpression."), - JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string SignalId { get; set; } - } -} + /// + /// Obsolete. + /// + [Obsolete("Replaced by RemovedSignalExpression."), + JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string SignalId { get; set; } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Settings/SettingName.cs b/src/Seq.Api/Model/Settings/SettingName.cs index bca9984..3fc11d4 100644 --- a/src/Seq.Api/Model/Settings/SettingName.cs +++ b/src/Seq.Api/Model/Settings/SettingName.cs @@ -165,6 +165,12 @@ public enum SettingName /// NewUserShowDashboardIds, + /// + /// A comma-separated list of (shared) metrics view ids that will be included in any new user's + /// personal default workspace. + /// + NewUserShowViewIds, + /// /// If using OpenID Connect authentication, the URL of the authorization endpoint. For example, /// https://example.com. diff --git a/src/Seq.Api/Model/Shared/DataSource.cs b/src/Seq.Api/Model/Shared/DataSource.cs new file mode 100644 index 0000000..e21dd66 --- /dev/null +++ b/src/Seq.Api/Model/Shared/DataSource.cs @@ -0,0 +1,33 @@ +// 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; + +/// +/// Identifies the storage objects present in Seq's underlying telemetry database. +/// +public enum DataSource +{ + /// + /// The stream object, which stores log events and trace spans. The stream object is implemented + /// over row-oriented storage, optimized for general search and row-by-row retrieval. + /// + Stream, + + /// + /// The series object, which stores metrics. The series object is implemented over columnar storage, + /// optimized for aggregate queries that each touch a small number of columns. + /// + Series, +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs b/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs index 05761de..c4fd012 100644 --- a/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs +++ b/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs @@ -36,7 +36,7 @@ public class DescriptiveFilterPart public string Filter { get; set; } /// - /// The original ("fuzzy") text entered by the user into the filter bar when + /// Optionally, the original ("fuzzy") text entered by the user into the filter bar when /// creating the filter. This may not be syntactically valid, e.g. it may be /// interpreted as free text - hence while it's displayed in the UI and forms the /// basis of user editing of the filter, the value is executed. diff --git a/src/Seq.Api/Model/Shared/JoinKind.cs b/src/Seq.Api/Model/Shared/JoinKind.cs new file mode 100644 index 0000000..59328d5 --- /dev/null +++ b/src/Seq.Api/Model/Shared/JoinKind.cs @@ -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; + +/// +/// A type of relational join. +/// +public enum JoinKind +{ + /// + /// The unknown join kind. + /// + Unknown, + + /// + /// A join type that joins each row to the output of a table function evaluated in the context of the row. + /// + Lateral +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Shared/JoinPart.cs b/src/Seq.Api/Model/Shared/JoinPart.cs new file mode 100644 index 0000000..51a439c --- /dev/null +++ b/src/Seq.Api/Model/Shared/JoinPart.cs @@ -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 + +/// +/// The lateral cross join part of a from clause. +/// +public class JoinPart +{ + /// + /// The type of relational join. + /// + public JoinKind Kind { get; set; } + + /// + /// The set function call used in the lateral join. + /// + public string? SetFunctionCall { get; set; } + + /// + /// The alias of the set function call. + /// + public string? Alias { get; set; } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Signals/SignalEntity.cs b/src/Seq.Api/Model/Signals/SignalEntity.cs index 82fd86d..1b882a8 100644 --- a/src/Seq.Api/Model/Signals/SignalEntity.cs +++ b/src/Seq.Api/Model/Signals/SignalEntity.cs @@ -31,12 +31,12 @@ public class SignalEntity : Entity public SignalEntity() { Title = "New Signal"; - Filters = new List(); - Columns = new List(); + Filters = []; + Columns = []; } /// - /// The friendly, human readable title of the signal. + /// The friendly, human-readable title of the signal. /// public string Title { get; set; } @@ -46,7 +46,7 @@ public SignalEntity() public string Description { get; set; } /// - /// Filters that are combined (using the and operator) to identify events matching the filter. + /// Filters that are combined (using the and operator) to identify events present in the signal. /// public List Filters { get; set; } diff --git a/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs b/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs index e3c635f..ab4940a 100644 --- a/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs +++ b/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs @@ -14,8 +14,9 @@ using System.Collections.Generic; 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 { @@ -27,16 +28,21 @@ public class WorkspaceContentPart /// /// A list of ids to include in the workspace. /// - public List SignalIds { get; set; } = new List(); + public List SignalIds { get; set; } = []; /// - /// A list of ids to include in the workspace. + /// A list of ids to include in the workspace. /// - public List QueryIds { get; set; } = new List(); + public List QueryIds { get; set; } = []; /// /// A list of ids to include in the workspace. /// - public List DashboardIds { get; set; } = new List(); + public List DashboardIds { get; set; } = []; + + /// + /// A list of ids to include in the workspace. + /// + public List ViewIds { get; set; } = []; } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs index 4347f64..48da13d 100644 --- a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs @@ -159,6 +159,13 @@ protected async Task GroupPostAsync(string link, var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); return await Client.PostAsync(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 GroupTryPostAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) + { + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.TryPostAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); + } /// /// Update an entity. diff --git a/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs b/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs index 18d71be..ed9c6ce 100644 --- a/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs @@ -36,12 +36,13 @@ internal DashboardsResourceGroup(ILoadResourceGroup connection) /// Retrieve the dashboard with the given id; throws if the entity does not exist. /// /// The id of the dashboard. + /// If true, include only partial details in the result. /// A allowing the operation to be canceled. /// The dashboard. - public async Task FindAsync(string id, CancellationToken cancellationToken = default) + public async Task FindAsync(string id, bool partial = false, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id }, { "partial", partial } }, cancellationToken).ConfigureAwait(false); } /// @@ -50,11 +51,12 @@ public async Task FindAsync(string id, CancellationToken cancel /// If the id of a is provided, only dashboards owned by them will be included in the result; if /// not specified, personal dashboards for all owners will be listed. /// If true, shared dashboards will be included in the result. + /// If true, include only partial details in the result. /// allowing the operation to be canceled. /// A list containing matching dashboards. - public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) + public async Task> ListAsync(string ownerId = null, bool shared = false, bool partial = false, CancellationToken cancellationToken = default) { - var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; + var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared }, { "partial", partial } }; return await GroupListAsync("Items", parameters, cancellationToken).ConfigureAwait(false); } diff --git a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs index df10b64..5a9d0e3 100644 --- a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs @@ -33,7 +33,8 @@ internal DataResourceGroup(ILoadResourceGroup connection) } /// - /// Execute an SQL query and retrieve the result set as a structured . + /// Execute an SQL query and retrieve the result set as a structured . For non-throwing + /// syntax error reporting, see . /// /// The query to execute. /// The earliest timestamp from which to include events in the query result. @@ -61,6 +62,37 @@ public async Task QueryAsync( return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } + /// + /// Execute an SQL query and retrieve the result set as a structured . This method + /// differs from by returning a result object with error information instead of throwing + /// when the query syntax is invalid. + /// + /// The query to execute. + /// The earliest timestamp from which to include events in the query result. + /// The exclusive latest timestamp to which events are included in the query result. The default is the current time. + /// A signal expression over which the query will be executed. + /// 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 . + /// Enable detailed (server-side) query tracing. + /// Token through which the operation can be cancelled. + /// A structured result set or syntax/execution error information. + public async Task TryQueryAsync( + string query, + DateTime? rangeStartUtc = null, + DateTime? rangeEndUtc = null, + SignalExpressionPart signal = null, + SignalEntity unsavedSignal = null, + TimeSpan? timeout = null, + Dictionary 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("Query", body, parameters, cancellationToken).ConfigureAwait(false); + } + /// /// Execute an SQL query and retrieve the result set as a structured . /// diff --git a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs index 514e552..31ee28e 100644 --- a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs @@ -70,7 +70,7 @@ public async Task FindAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// The number of events to retrieve. If not specified will default to 30. /// An event id from which to start searching (inclusively). /// An event id to search after (exclusively). @@ -141,7 +141,7 @@ public async IAsyncEnumerable EnumerateAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// The number of events to retrieve. If not specified will default to 30. /// An event id from which to start searching (inclusively). /// An event id to search after (exclusively). @@ -220,7 +220,7 @@ public async IAsyncEnumerable PagedEnumerateAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// The number of events to retrieve. If not specified will default to 30. /// An event id from which to start searching (inclusively). /// An event id to search after (exclusively). @@ -267,7 +267,7 @@ public async Task> ListAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// The number of events to retrieve. If not specified will default to 30. /// An event id from which to start searching (inclusively). /// An event id to search after (exclusively). @@ -327,7 +327,7 @@ public async Task PageAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// Earliest (inclusive) date/time from which to delete. /// Latest (exclusive) date/time from which to delete. /// Values for any free variables that appear in . @@ -359,7 +359,7 @@ public async Task DeleteAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// If specified, the event's message template and properties will be rendered into its property. /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. @@ -405,7 +405,7 @@ public async IAsyncEnumerable StreamAsync( /// 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(). + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use . /// If specified, the event's message template and properties will be rendered into its property. /// If specified, compact JSON format will be requested instead of the default format. /// Values for any free variables that appear in . diff --git a/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs b/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs index 7474a74..16ad9f2 100644 --- a/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs @@ -17,46 +17,50 @@ using System.Threading.Tasks; using Seq.Api.Model.Expressions; -namespace Seq.Api.ResourceGroups +namespace Seq.Api.ResourceGroups; + +/// +/// Perform operations on queries and filter expressions. +/// +public class ExpressionsResourceGroup : ApiResourceGroup { + internal ExpressionsResourceGroup(ILoadResourceGroup connection) + : base("Expressions", connection) + { + } + /// - /// Perform operations on queries and filter expressions. + /// Convert an expression in the relaxed syntax supported by the filter bar, to the strictly-valid + /// syntax required by API interactions. /// - public class ExpressionsResourceGroup : ApiResourceGroup + /// The (potentially) relaxed-syntax expression. + /// A allowing the operation to be canceled. + /// The expression in a strictly-valid form. + public Task ToStrictAsync(string fuzzy, CancellationToken cancellationToken = default) { - internal ExpressionsResourceGroup(ILoadResourceGroup connection) - : base("Expressions", connection) - { - } - - /// - /// Convert an expression in the relaxed syntax supported by the filter bar, to the strictly-valid - /// syntax required by API interactions. - /// - /// The (potentially) relaxed-syntax expression. - /// A allowing the operation to be canceled. - /// The expression in a strictly-valid form. - public Task ToStrictAsync(string fuzzy, CancellationToken cancellationToken = default) + var parameters = new Dictionary { - return GroupGetAsync("ToStrict", new Dictionary - { - {"fuzzy", fuzzy} - }, cancellationToken); - } + [nameof(fuzzy)] = fuzzy + }; + return GroupGetAsync("ToStrict", parameters, cancellationToken); + } - /// - /// Convert an expression in the relaxed syntax supported by the filter bar, to the strict and limited - /// syntax required within SQL queries. - /// - /// The (potentially) relaxed-syntax expression. - /// A allowing the operation to be canceled. - /// The expression in a form that can be used within SQL queries. - public Task ToSqlAsync(string fuzzy, CancellationToken cancellationToken = default) + /// + /// Convert an expression in the relaxed syntax supported by the filter bar, to the strict and limited + /// syntax required within SQL queries. + /// + /// The (potentially) relaxed-syntax expression. + /// The name of the storage object that the expression will be evaluated against; accepted + /// values are stream and series. This influences the processing of "free text" expressions. + /// A allowing the operation to be canceled. + /// The expression in a form that can be used within SQL queries. + public Task ToSqlAsync(string fuzzy, string schema = "stream", CancellationToken cancellationToken = default) + { + var parameters = new Dictionary { - return GroupGetAsync("ToSql", new Dictionary - { - {"fuzzy", fuzzy} - }, cancellationToken); - } + [nameof(fuzzy)] = fuzzy, + [nameof(schema)] = schema + }; + return GroupGetAsync("ToSql", parameters, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs b/src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs new file mode 100644 index 0000000..29c5df5 --- /dev/null +++ b/src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs @@ -0,0 +1,144 @@ +// 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; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Seq.Api.Model.Metrics; +using Seq.Api.Model.Shared; +namespace Seq.Api.ResourceGroups; + +/// +/// Retrieve information about the metrics stored in this Seq instance. +/// +public class MetricsResourceGroup : ApiResourceGroup +{ + internal MetricsResourceGroup(ILoadResourceGroup connection) + : base("Metrics", connection) + { + } + + /// + /// Retrieve metric definitions for samples matching a set of search criteria. + /// + /// Expressions naming any properties that determine the uniqueness/identity of metric definitions. + /// 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 . + /// The number of definitions to retrieve. If not specified will default to 30. + /// The earliest timestamp from which to include definitions in the query result. + /// The exclusive latest timestamp to which definitions are included in the query result. The default is the current time. + /// The query timeout; if not specified, the query will run until completion. + /// Values for any free variables that appear in . + /// Enable detailed (server-side) query tracing. + /// Token through which the operation can be cancelled. + /// A structured result set. + public async Task SearchAsync( + List groups = null, + string filter = null, + int count = 30, + DateTime? rangeStartUtc = null, + DateTime? rangeEndUtc = null, + TimeSpan? timeout = null, + Dictionary variables = null, + bool trace = false, + CancellationToken cancellationToken = default) + { + var parameters = MakeParameters(groups, filter, count, rangeStartUtc, rangeEndUtc, timeout, trace); + var body = new EvaluationContextPart { Variables = variables }; + return await GroupPostAsync("Search", body, parameters, cancellationToken).ConfigureAwait(false); + } + + /// + /// Retrieve information about the dimensions available for filtering samples matching a set of search criteria. + /// + /// The number of dimensions to retrieve. If not specified will default to 30. + /// Optionally, the name of a metric to limit dimension search to. By default, dimensions + /// for all metrics are returned. + /// The earliest timestamp from which to include dimensions in the query result. + /// The exclusive latest timestamp to which dimensions are included in the query result. The default is the current time. + /// The query timeout; if not specified, the query will run until completion. + /// Enable detailed (server-side) query tracing. + /// Token through which the operation can be cancelled. + /// A structured result set. + public async Task> ListDimensionsAsync( + int count = 30, + string metric = null, + DateTime? rangeStartUtc = null, + DateTime? rangeEndUtc = null, + TimeSpan? timeout = null, + bool trace = false, + CancellationToken cancellationToken = default) + { + var parameters = MakeParameters(null, null, count, rangeStartUtc, rangeEndUtc, timeout, trace); + if (metric != null) + parameters.Add(nameof(metric), metric); + var body = new EvaluationContextPart(); + return await GroupPostAsync>("Dimensions", body, parameters, cancellationToken).ConfigureAwait(false); + } + + /// + /// Retrieve the top values associated with the metric dimension . + /// + /// The dimension's accessor. This is generally a simple property path (cluster.node.name) but can + /// use explict root namespaces and indexer notation (@Resource.cluster['node name']) if necessary. + /// The number of values to retrieve. If not specified will default to 30. + /// The earliest timestamp from which to include values in the query result. + /// The exclusive latest timestamp to which values are included in the query result. The default is the current time. + /// The query timeout; if not specified, the query will run until completion. + /// Enable detailed (server-side) query tracing. + /// Token through which the operation can be cancelled. + /// A structured result set. + public async Task> ListDimensionValuesAsync( + string accessor, + int count = 30, + DateTime? rangeStartUtc = null, + DateTime? rangeEndUtc = null, + TimeSpan? timeout = null, + bool trace = false, + CancellationToken cancellationToken = default) + { + var parameters = MakeParameters(null, null, count, rangeStartUtc, rangeEndUtc, timeout, trace); + parameters.Add(nameof(accessor), accessor); + var body = new EvaluationContextPart(); + return await GroupPostAsync>("DimensionValues", body, parameters, cancellationToken).ConfigureAwait(false); + } + + static Dictionary MakeParameters(List groups, string filter, int count, DateTime? rangeStartUtc, DateTime? rangeEndUtc, TimeSpan? timeout, bool trace) + { + var parameters = new Dictionary + { + [nameof(count)] = count + }; + + if (groups?.Count > 0) + parameters.Add(nameof(groups), string.Join(",", groups)); + + if (!string.IsNullOrWhiteSpace(filter)) + parameters.Add(nameof(filter), filter); + + if (rangeStartUtc != null) + parameters.Add(nameof(rangeStartUtc), rangeStartUtc); + + if (rangeEndUtc != null) + parameters.Add(nameof(rangeEndUtc), rangeEndUtc.Value); + + if (timeout != null) + parameters.Add("timeoutMS", timeout.Value.TotalMilliseconds.ToString("0")); + + if (trace) + parameters.Add(nameof(trace), true); + return parameters; + } +} diff --git a/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs b/src/Seq.Api/ResourceGroups/QueriesResourceGroup.cs similarity index 71% rename from src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs rename to src/Seq.Api/ResourceGroups/QueriesResourceGroup.cs index 83414ed..671fab2 100644 --- a/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/QueriesResourceGroup.cs @@ -17,17 +17,17 @@ using System.Threading; using System.Threading.Tasks; using Seq.Api.Model; -using Seq.Api.Model.SqlQueries; +using Seq.Api.Model.Queries; using Seq.Api.Model.Users; namespace Seq.Api.ResourceGroups { /// - /// Perform operations on saved SQL queries. + /// Perform operations on saved queries. /// - public class SqlQueriesResourceGroup : ApiResourceGroup + public class QueriesResourceGroup : ApiResourceGroup { - internal SqlQueriesResourceGroup(ILoadResourceGroup connection) + internal QueriesResourceGroup(ILoadResourceGroup connection) : base("SqlQueries", connection) { } @@ -38,10 +38,10 @@ internal SqlQueriesResourceGroup(ILoadResourceGroup connection) /// The id of the query. /// A allowing the operation to be canceled. /// The query. - public async Task FindAsync(string id, CancellationToken cancellationToken = default) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } /// @@ -52,10 +52,10 @@ public async Task FindAsync(string id, CancellationToken cancell /// If true, shared queries will be included in the result. /// allowing the operation to be canceled. /// A list containing matching queries. - public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) + public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) { var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; - return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false); + return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false); } /// @@ -63,9 +63,9 @@ public async Task> ListAsync(string ownerId = null, bool sh /// /// allowing the operation to be canceled. /// The unsaved query. - public async Task TemplateAsync(CancellationToken cancellationToken = default) + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } /// @@ -74,9 +74,9 @@ public async Task TemplateAsync(CancellationToken cancellationTo /// The query to add. /// A allowing the operation to be canceled. /// The query, with server-allocated properties such as initialized. - public async Task AddAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default) + public async Task AddAsync(QueryEntity entity, CancellationToken cancellationToken = default) { - return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); } /// @@ -85,7 +85,7 @@ public async Task AddAsync(SqlQueryEntity entity, CancellationTo /// The query to remove. /// A allowing the operation to be canceled. /// A task indicating completion. - public async Task RemoveAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default) + public async Task RemoveAsync(QueryEntity entity, CancellationToken cancellationToken = default) { await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -96,7 +96,7 @@ public async Task RemoveAsync(SqlQueryEntity entity, CancellationToken cancellat /// The query to update. /// A allowing the operation to be canceled. /// A task indicating completion. - public async Task UpdateAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default) + public async Task UpdateAsync(QueryEntity entity, CancellationToken cancellationToken = default) { await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } diff --git a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs index 7249714..2762668 100644 --- a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs @@ -36,12 +36,13 @@ internal SignalsResourceGroup(ILoadResourceGroup connection) /// Retrieve the signal with the given id; throws if the entity does not exist. /// /// The id of the signal. + /// If true, include only partial details in the result. /// A allowing the operation to be canceled. /// The signal. - public async Task FindAsync(string id, CancellationToken cancellationToken = default) + public async Task FindAsync(string id, bool partial = false, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id }, { "partial", partial } }, cancellationToken).ConfigureAwait(false); } /// @@ -50,11 +51,12 @@ public async Task FindAsync(string id, CancellationToken cancellat /// If the id of a is provided, only signals owned by them will be included in the result; if /// not specified, personal signals for all owners will be listed. /// If true, shared signals will be included in the result. + /// If true, include only partial details in the result. /// allowing the operation to be canceled. /// A list containing matching signals. - public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) + public async Task> ListAsync(string ownerId = null, bool shared = false, bool partial = false, CancellationToken cancellationToken = default) { - var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; + var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared }, { "partial", partial } }; return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false); } diff --git a/src/Seq.Api/ResourceGroups/ViewsResourceGroup.cs b/src/Seq.Api/ResourceGroups/ViewsResourceGroup.cs new file mode 100644 index 0000000..b6e0e98 --- /dev/null +++ b/src/Seq.Api/ResourceGroups/ViewsResourceGroup.cs @@ -0,0 +1,106 @@ +// 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; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Seq.Api.Model; +using Seq.Api.Model.Metrics; +using Seq.Api.Model.Users; + +namespace Seq.Api.ResourceGroups; + +/// +/// Perform operations on metrics views. A view is a collection of filters, groupings, and pinned charts used to +/// populate the Seq Metrics screen. +/// +public class ViewsResourceGroup : ApiResourceGroup +{ + internal ViewsResourceGroup(ILoadResourceGroup connection) + : base("Views", connection) + { + } + + /// + /// Retrieve the view with the given id; throws if the entity does not exist. + /// + /// The id of the view. + /// If true, include only partial details in the result. + /// A allowing the operation to be canceled. + /// The view. + public async Task FindAsync(string id, bool partial = false, CancellationToken cancellationToken = default) + { + if (id == null) throw new ArgumentNullException(nameof(id)); + return await GroupGetAsync("Item", new Dictionary { { "id", id }, { "partial", partial } }, cancellationToken).ConfigureAwait(false); + } + + /// + /// Retrieve views. + /// + /// If the id of a is provided, only views owned by them will be included in the result; if + /// not specified, personal views for all owners will be listed. + /// If true, shared views will be included in the result. + /// If true, include only partial details in the result. + /// allowing the operation to be canceled. + /// A list containing matching views. + public async Task> ListAsync(string ownerId = null, bool shared = false, bool partial = false, CancellationToken cancellationToken = default) + { + var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared }, { "partial", partial } }; + return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Construct a view with server defaults pre-initialized. + /// + /// allowing the operation to be canceled. + /// The unsaved view. + public async Task TemplateAsync(CancellationToken cancellationToken = default) + { + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Add a new view. + /// + /// The view to add. + /// A allowing the operation to be canceled. + /// The view, with server-allocated properties such as initialized. + public async Task AddAsync(ViewEntity entity, CancellationToken cancellationToken = default) + { + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Remove an existing view. + /// + /// The view to remove. + /// A allowing the operation to be canceled. + /// A task indicating completion. + public async Task RemoveAsync(ViewEntity entity, CancellationToken cancellationToken = default) + { + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Update an existing view. + /// + /// The view to update. + /// A allowing the operation to be canceled. + /// A task indicating completion. + public async Task UpdateAsync(ViewEntity entity, CancellationToken cancellationToken = default) + { + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index 19e11c8..157f69a 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,9 +1,8 @@ Client library for the Seq HTTP API. - 2025.2.2 Datalust;Contributors - netstandard2.0;net6.0;net8.0 + netstandard2.0;net6.0;net8.0;net10.0 true true seq @@ -23,13 +22,21 @@ - + - + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Seq.Api/SeqConnection.cs b/src/Seq.Api/SeqConnection.cs index 9fc90f7..5ac056d 100644 --- a/src/Seq.Api/SeqConnection.cs +++ b/src/Seq.Api/SeqConnection.cs @@ -143,6 +143,11 @@ public void Dispose() /// Perform operations on the Seq license certificate. /// public LicensesResourceGroup Licenses => new(this); + + /// + /// Perform operations on metrics definitions. + /// + public MetricsResourceGroup Metrics => new(this); /// /// Perform operations on permalinked events. @@ -175,9 +180,9 @@ public void Dispose() public SignalsResourceGroup Signals => new(this); /// - /// Perform operations on saved SQL queries. + /// Perform operations on saved queries. /// - public SqlQueriesResourceGroup SqlQueries => new(this); + public QueriesResourceGroup Queries => new(this); /// /// Perform operations on known available Seq versions. @@ -189,6 +194,11 @@ public void Dispose() /// public UsersResourceGroup Users => new(this); + /// + /// Perform operations on metrics views. + /// + public ViewsResourceGroup Views => new(this); + /// /// Perform operations on workspaces. /// diff --git a/test/Seq.Api.Tests/Seq.Api.Tests.csproj b/test/Seq.Api.Tests/Seq.Api.Tests.csproj index 6c00170..603b80b 100644 --- a/test/Seq.Api.Tests/Seq.Api.Tests.csproj +++ b/test/Seq.Api.Tests/Seq.Api.Tests.csproj @@ -1,8 +1,10 @@  - net462;net6.0 + net48 + $(TargetFrameworks);net10.0 true + false