Files
git.stella-ops.org/src/Notify/__Libraries/StellaOps.Notify.Models/NotifyQuietHours.cs
StellaOps Bot 564df71bfb
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
up
2025-12-13 00:20:26 +02:00

402 lines
12 KiB
C#

using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Notify.Models;
/// <summary>
/// Quiet hours schedule configuration for suppressing notifications during specified periods.
/// </summary>
public sealed record NotifyQuietHoursSchedule
{
[JsonConstructor]
public NotifyQuietHoursSchedule(
string scheduleId,
string tenantId,
string name,
string cronExpression,
TimeSpan duration,
string timeZone,
string? channelId = null,
bool enabled = true,
string? description = null,
ImmutableDictionary<string, string>? metadata = null,
string? createdBy = null,
DateTimeOffset? createdAt = null,
string? updatedBy = null,
DateTimeOffset? updatedAt = null)
{
ScheduleId = NotifyValidation.EnsureNotNullOrWhiteSpace(scheduleId, nameof(scheduleId));
TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId));
Name = NotifyValidation.EnsureNotNullOrWhiteSpace(name, nameof(name));
CronExpression = NotifyValidation.EnsureNotNullOrWhiteSpace(cronExpression, nameof(cronExpression));
Duration = duration > TimeSpan.Zero ? duration : TimeSpan.FromHours(8);
TimeZone = NotifyValidation.EnsureNotNullOrWhiteSpace(timeZone, nameof(timeZone));
ChannelId = NotifyValidation.TrimToNull(channelId);
Enabled = enabled;
Description = NotifyValidation.TrimToNull(description);
Metadata = NotifyValidation.NormalizeStringDictionary(metadata);
CreatedBy = NotifyValidation.TrimToNull(createdBy);
CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow);
UpdatedBy = NotifyValidation.TrimToNull(updatedBy);
UpdatedAt = NotifyValidation.EnsureUtc(updatedAt ?? CreatedAt);
}
public static NotifyQuietHoursSchedule Create(
string scheduleId,
string tenantId,
string name,
string cronExpression,
TimeSpan duration,
string timeZone,
string? channelId = null,
bool enabled = true,
string? description = null,
IEnumerable<KeyValuePair<string, string>>? metadata = null,
string? createdBy = null,
DateTimeOffset? createdAt = null,
string? updatedBy = null,
DateTimeOffset? updatedAt = null)
{
return new NotifyQuietHoursSchedule(
scheduleId,
tenantId,
name,
cronExpression,
duration,
timeZone,
channelId,
enabled,
description,
ToImmutableDictionary(metadata),
createdBy,
createdAt,
updatedBy,
updatedAt);
}
public string ScheduleId { get; }
public string TenantId { get; }
public string Name { get; }
/// <summary>
/// Cron expression defining when quiet hours start.
/// </summary>
public string CronExpression { get; }
/// <summary>
/// Duration of the quiet hours window.
/// </summary>
public TimeSpan Duration { get; }
/// <summary>
/// IANA time zone for evaluating the cron expression (e.g., "America/New_York").
/// </summary>
public string TimeZone { get; }
/// <summary>
/// Optional channel ID to scope quiet hours to a specific channel.
/// If null, applies to all channels.
/// </summary>
public string? ChannelId { get; }
public bool Enabled { get; }
public string? Description { get; }
public ImmutableDictionary<string, string> Metadata { get; }
public string? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
public string? UpdatedBy { get; }
public DateTimeOffset UpdatedAt { get; }
private static ImmutableDictionary<string, string>? ToImmutableDictionary(IEnumerable<KeyValuePair<string, string>>? pairs)
{
if (pairs is null)
{
return null;
}
var builder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
foreach (var (key, value) in pairs)
{
builder[key] = value;
}
return builder.ToImmutable();
}
}
/// <summary>
/// Maintenance window for planned suppression of notifications.
/// </summary>
public sealed record NotifyMaintenanceWindow
{
[JsonConstructor]
public NotifyMaintenanceWindow(
string windowId,
string tenantId,
string name,
DateTimeOffset startsAt,
DateTimeOffset endsAt,
bool suppressNotifications = true,
string? reason = null,
ImmutableArray<string> channelIds = default,
ImmutableArray<string> ruleIds = default,
ImmutableDictionary<string, string>? metadata = null,
string? createdBy = null,
DateTimeOffset? createdAt = null,
string? updatedBy = null,
DateTimeOffset? updatedAt = null)
{
WindowId = NotifyValidation.EnsureNotNullOrWhiteSpace(windowId, nameof(windowId));
TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId));
Name = NotifyValidation.EnsureNotNullOrWhiteSpace(name, nameof(name));
StartsAt = NotifyValidation.EnsureUtc(startsAt);
EndsAt = NotifyValidation.EnsureUtc(endsAt);
SuppressNotifications = suppressNotifications;
Reason = NotifyValidation.TrimToNull(reason);
ChannelIds = NormalizeStringArray(channelIds);
RuleIds = NormalizeStringArray(ruleIds);
Metadata = NotifyValidation.NormalizeStringDictionary(metadata);
CreatedBy = NotifyValidation.TrimToNull(createdBy);
CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow);
UpdatedBy = NotifyValidation.TrimToNull(updatedBy);
UpdatedAt = NotifyValidation.EnsureUtc(updatedAt ?? CreatedAt);
if (EndsAt <= StartsAt)
{
throw new ArgumentException("EndsAt must be after StartsAt.", nameof(endsAt));
}
}
public static NotifyMaintenanceWindow Create(
string windowId,
string tenantId,
string name,
DateTimeOffset startsAt,
DateTimeOffset endsAt,
bool suppressNotifications = true,
string? reason = null,
IEnumerable<string>? channelIds = null,
IEnumerable<string>? ruleIds = null,
IEnumerable<KeyValuePair<string, string>>? metadata = null,
string? createdBy = null,
DateTimeOffset? createdAt = null,
string? updatedBy = null,
DateTimeOffset? updatedAt = null)
{
return new NotifyMaintenanceWindow(
windowId,
tenantId,
name,
startsAt,
endsAt,
suppressNotifications,
reason,
ToImmutableArray(channelIds),
ToImmutableArray(ruleIds),
ToImmutableDictionary(metadata),
createdBy,
createdAt,
updatedBy,
updatedAt);
}
public string WindowId { get; }
public string TenantId { get; }
public string Name { get; }
public DateTimeOffset StartsAt { get; }
public DateTimeOffset EndsAt { get; }
/// <summary>
/// Whether to suppress notifications during the maintenance window.
/// </summary>
public bool SuppressNotifications { get; }
/// <summary>
/// Reason for the maintenance window.
/// </summary>
public string? Reason { get; }
/// <summary>
/// Optional list of channel IDs to scope the maintenance window.
/// If empty, applies to all channels.
/// </summary>
public ImmutableArray<string> ChannelIds { get; }
/// <summary>
/// Optional list of rule IDs to scope the maintenance window.
/// If empty, applies to all rules.
/// </summary>
public ImmutableArray<string> RuleIds { get; }
public ImmutableDictionary<string, string> Metadata { get; }
public string? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
public string? UpdatedBy { get; }
public DateTimeOffset UpdatedAt { get; }
/// <summary>
/// Checks if the maintenance window is active at the specified time.
/// </summary>
public bool IsActiveAt(DateTimeOffset timestamp)
=> SuppressNotifications && timestamp >= StartsAt && timestamp < EndsAt;
private static ImmutableArray<string> NormalizeStringArray(ImmutableArray<string> values)
{
if (values.IsDefaultOrEmpty)
{
return ImmutableArray<string>.Empty;
}
return values
.Where(static v => !string.IsNullOrWhiteSpace(v))
.Select(static v => v.Trim())
.Distinct(StringComparer.Ordinal)
.ToImmutableArray();
}
private static ImmutableArray<string> ToImmutableArray(IEnumerable<string>? values)
=> values is null ? ImmutableArray<string>.Empty : values.ToImmutableArray();
private static ImmutableDictionary<string, string>? ToImmutableDictionary(IEnumerable<KeyValuePair<string, string>>? pairs)
{
if (pairs is null)
{
return null;
}
var builder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
foreach (var (key, value) in pairs)
{
builder[key] = value;
}
return builder.ToImmutable();
}
}
/// <summary>
/// Operator override for quiet hours or throttle configuration.
/// Allows an operator to temporarily bypass quiet hours or throttling.
/// </summary>
public sealed record NotifyOperatorOverride
{
[JsonConstructor]
public NotifyOperatorOverride(
string overrideId,
string tenantId,
NotifyOverrideType overrideType,
DateTimeOffset expiresAt,
string? channelId = null,
string? ruleId = null,
string? reason = null,
string? createdBy = null,
DateTimeOffset? createdAt = null)
{
OverrideId = NotifyValidation.EnsureNotNullOrWhiteSpace(overrideId, nameof(overrideId));
TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId));
OverrideType = overrideType;
ExpiresAt = NotifyValidation.EnsureUtc(expiresAt);
ChannelId = NotifyValidation.TrimToNull(channelId);
RuleId = NotifyValidation.TrimToNull(ruleId);
Reason = NotifyValidation.TrimToNull(reason);
CreatedBy = NotifyValidation.TrimToNull(createdBy);
CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow);
}
public static NotifyOperatorOverride Create(
string overrideId,
string tenantId,
NotifyOverrideType overrideType,
DateTimeOffset expiresAt,
string? channelId = null,
string? ruleId = null,
string? reason = null,
string? createdBy = null,
DateTimeOffset? createdAt = null)
{
return new NotifyOperatorOverride(
overrideId,
tenantId,
overrideType,
expiresAt,
channelId,
ruleId,
reason,
createdBy,
createdAt);
}
public string OverrideId { get; }
public string TenantId { get; }
public NotifyOverrideType OverrideType { get; }
public DateTimeOffset ExpiresAt { get; }
/// <summary>
/// Optional channel ID to scope the override.
/// </summary>
public string? ChannelId { get; }
/// <summary>
/// Optional rule ID to scope the override.
/// </summary>
public string? RuleId { get; }
public string? Reason { get; }
public string? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
/// <summary>
/// Checks if the override is active at the specified time.
/// </summary>
public bool IsActiveAt(DateTimeOffset timestamp)
=> timestamp < ExpiresAt;
}
/// <summary>
/// Type of operator override.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum NotifyOverrideType
{
/// <summary>
/// Bypass quiet hours.
/// </summary>
BypassQuietHours,
/// <summary>
/// Bypass throttling.
/// </summary>
BypassThrottle,
/// <summary>
/// Bypass maintenance window.
/// </summary>
BypassMaintenance,
/// <summary>
/// Force suppress notifications.
/// </summary>
ForceSuppression
}