Skip to content

Commit 33b787a

Browse files
committed
more work
1 parent 8ea107c commit 33b787a

29 files changed

+4375
-93
lines changed

src/ServiceControl.Persistence.Sql.Core/DbContexts/ServiceControlDbContextBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ protected ServiceControlDbContextBase(DbContextOptions options) : base(options)
2828
public DbSet<FailedMessageRetryEntity> FailedMessageRetries { get; set; }
2929
public DbSet<GroupCommentEntity> GroupComments { get; set; }
3030
public DbSet<RetryBatchNowForwardingEntity> RetryBatchNowForwarding { get; set; }
31+
public DbSet<NotificationsSettingsEntity> NotificationsSettings { get; set; }
3132

3233
protected override void OnModelCreating(ModelBuilder modelBuilder)
3334
{
@@ -51,6 +52,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
5152
modelBuilder.ApplyConfiguration(new FailedMessageRetryConfiguration());
5253
modelBuilder.ApplyConfiguration(new GroupCommentConfiguration());
5354
modelBuilder.ApplyConfiguration(new RetryBatchNowForwardingConfiguration());
55+
modelBuilder.ApplyConfiguration(new NotificationsSettingsConfiguration());
5456

5557
OnModelCreatingProvider(modelBuilder);
5658
}

src/ServiceControl.Persistence.Sql.Core/Entities/ExternalIntegrationDispatchRequestEntity.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace ServiceControl.Persistence.Sql.Core.Entities;
44

55
public class ExternalIntegrationDispatchRequestEntity
66
{
7-
public string Id { get; set; } = null!;
8-
public string DispatchContextJson { get; set; } = null!; // Serialize the DispatchContext object as JSON
7+
public long Id { get; set; }
8+
public string DispatchContextJson { get; set; } = null!;
99
public DateTime CreatedAt { get; set; }
1010
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ServiceControl.Persistence.Sql.Core.Entities;
2+
3+
public class NotificationsSettingsEntity
4+
{
5+
public string Id { get; set; } = string.Empty;
6+
public string EmailSettingsJson { get; set; } = string.Empty;
7+
}

src/ServiceControl.Persistence.Sql.Core/EntityConfigurations/ExternalIntegrationDispatchRequestConfiguration.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ public void Configure(EntityTypeBuilder<ExternalIntegrationDispatchRequestEntity
1010
{
1111
builder.ToTable("ExternalIntegrationDispatchRequests");
1212
builder.HasKey(e => e.Id);
13-
builder.Property(e => e.Id).HasMaxLength(200).IsRequired();
13+
builder.Property(e => e.Id)
14+
.ValueGeneratedOnAdd()
15+
.HasColumnType("bigint")
16+
.IsRequired();
17+
1418
builder.Property(e => e.DispatchContextJson).IsRequired();
1519
builder.Property(e => e.CreatedAt).IsRequired();
1620

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace ServiceControl.Persistence.Sql.Core.EntityConfigurations;
2+
3+
using Entities;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
6+
7+
class NotificationsSettingsConfiguration : IEntityTypeConfiguration<NotificationsSettingsEntity>
8+
{
9+
public void Configure(EntityTypeBuilder<NotificationsSettingsEntity> builder)
10+
{
11+
builder.ToTable("NotificationsSettings");
12+
builder.HasKey(e => e.Id);
13+
builder.Property(e => e.Id).HasMaxLength(200).IsRequired();
14+
builder.Property(e => e.EmailSettingsJson).IsRequired();
15+
}
16+
}

src/ServiceControl.Persistence.Sql.Core/Implementation/ArchiveMessages.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,8 @@ public void DismissArchiveOperation(string groupId, ArchiveType archiveType)
7373
var dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContextBase>();
7474

7575
var operationId = MakeOperationId(groupId, archiveType);
76-
var operation = dbContext.ArchiveOperations.FirstOrDefault(a => a.Id == operationId);
7776

78-
if (operation != null)
79-
{
80-
dbContext.ArchiveOperations.Remove(operation);
81-
dbContext.SaveChanges();
82-
}
77+
dbContext.ArchiveOperations.Where(a => a.Id == operationId).ExecuteDelete();
8378
}
8479

8580
public async Task StartArchiving(string groupId, ArchiveType archiveType)
@@ -139,20 +134,23 @@ public IEnumerable<InMemoryArchive> GetArchivalOperations()
139134

140135
var operations = dbContext.ArchiveOperations
141136
.AsNoTracking()
142-
.ToList();
137+
.AsEnumerable();
143138

144-
return operations.Select(op => new InMemoryArchive(op.RequestId, (ArchiveType)op.ArchiveType, domainEvents)
139+
foreach (var op in operations)
145140
{
146-
GroupName = op.GroupName,
147-
ArchiveState = (ArchiveState)op.ArchiveState,
148-
TotalNumberOfMessages = op.TotalNumberOfMessages,
149-
NumberOfMessagesArchived = op.NumberOfMessagesArchived,
150-
NumberOfBatches = op.NumberOfBatches,
151-
CurrentBatch = op.CurrentBatch,
152-
Started = op.Started,
153-
Last = op.Last,
154-
CompletionTime = op.CompletionTime
155-
}).ToList();
141+
yield return new InMemoryArchive(op.RequestId, (ArchiveType)op.ArchiveType, domainEvents)
142+
{
143+
GroupName = op.GroupName,
144+
ArchiveState = (ArchiveState)op.ArchiveState,
145+
TotalNumberOfMessages = op.TotalNumberOfMessages,
146+
NumberOfMessagesArchived = op.NumberOfMessagesArchived,
147+
NumberOfBatches = op.NumberOfBatches,
148+
CurrentBatch = op.CurrentBatch,
149+
Started = op.Started,
150+
Last = op.Last,
151+
CompletionTime = op.CompletionTime
152+
};
153+
}
156154
}
157155

158156
static string MakeOperationId(string groupId, ArchiveType archiveType)

src/ServiceControl.Persistence.Sql.Core/Implementation/CustomChecksDataStore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task<QueryResult<IList<CustomCheck>>> GetStats(PagingInfo paging, s
8686

8787
var customChecks = results.Select(e => new CustomCheck
8888
{
89-
Id = $"CustomChecks/{e.Id}",
89+
Id = $"{e.Id}",
9090
CustomCheckId = e.CustomCheckId,
9191
Category = e.Category,
9292
Status = (Status)e.Status,
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
namespace ServiceControl.Persistence.Sql.Core.Implementation;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text.Json;
7+
using System.Threading.Tasks;
8+
using DbContexts;
9+
using Entities;
10+
using Microsoft.EntityFrameworkCore;
11+
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Logging;
13+
using ServiceControl.MessageFailures;
14+
using ServiceControl.Persistence;
15+
16+
class EditFailedMessagesManager(
17+
IServiceScope scope) : IEditFailedMessagesManager
18+
{
19+
readonly ServiceControlDbContextBase dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContextBase>();
20+
string? currentEditingRequestId;
21+
FailedMessage? currentMessage;
22+
23+
static readonly JsonSerializerOptions JsonOptions = new()
24+
{
25+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
26+
WriteIndented = false
27+
};
28+
29+
public async Task<FailedMessage?> GetFailedMessage(string uniqueMessageId)
30+
{
31+
var entity = await dbContext.FailedMessages
32+
.FirstOrDefaultAsync(m => m.UniqueMessageId == uniqueMessageId);
33+
34+
if (entity == null)
35+
{
36+
return null;
37+
}
38+
39+
var processingAttempts = JsonSerializer.Deserialize<List<FailedMessage.ProcessingAttempt>>(entity.ProcessingAttemptsJson, JsonOptions) ?? [];
40+
var failureGroups = JsonSerializer.Deserialize<List<FailedMessage.FailureGroup>>(entity.FailureGroupsJson, JsonOptions) ?? [];
41+
42+
currentMessage = new FailedMessage
43+
{
44+
Id = entity.Id,
45+
UniqueMessageId = entity.UniqueMessageId,
46+
Status = entity.Status,
47+
ProcessingAttempts = processingAttempts,
48+
FailureGroups = failureGroups
49+
};
50+
51+
return currentMessage;
52+
}
53+
54+
public async Task UpdateFailedMessage(FailedMessage failedMessage)
55+
{
56+
var entity = await dbContext.FailedMessages
57+
.FirstOrDefaultAsync(m => m.Id == failedMessage.Id);
58+
59+
if (entity != null)
60+
{
61+
entity.Status = failedMessage.Status;
62+
entity.ProcessingAttemptsJson = JsonSerializer.Serialize(failedMessage.ProcessingAttempts, JsonOptions);
63+
entity.FailureGroupsJson = JsonSerializer.Serialize(failedMessage.FailureGroups, JsonOptions);
64+
65+
// Update denormalized fields from last attempt
66+
var lastAttempt = failedMessage.ProcessingAttempts.LastOrDefault();
67+
if (lastAttempt != null)
68+
{
69+
entity.MessageId = lastAttempt.MessageId;
70+
entity.MessageType = lastAttempt.Headers?.GetValueOrDefault("NServiceBus.EnclosedMessageTypes");
71+
entity.TimeSent = lastAttempt.AttemptedAt;
72+
entity.SendingEndpoint = lastAttempt.Headers?.GetValueOrDefault("NServiceBus.OriginatingEndpoint");
73+
entity.ReceivingEndpoint = lastAttempt.Headers?.GetValueOrDefault("NServiceBus.ProcessingEndpoint");
74+
entity.ExceptionType = lastAttempt.FailureDetails?.Exception?.ExceptionType;
75+
entity.ExceptionMessage = lastAttempt.FailureDetails?.Exception?.Message;
76+
entity.QueueAddress = lastAttempt.Headers?.GetValueOrDefault("NServiceBus.FailedQ");
77+
entity.LastProcessedAt = lastAttempt.AttemptedAt;
78+
}
79+
80+
entity.NumberOfProcessingAttempts = failedMessage.ProcessingAttempts.Count;
81+
}
82+
}
83+
84+
public Task<string?> GetCurrentEditingRequestId(string failedMessageId)
85+
{
86+
// Simple in-memory tracking for the editing request
87+
return Task.FromResult(currentMessage?.Id == failedMessageId ? currentEditingRequestId : null);
88+
}
89+
90+
public Task SetCurrentEditingRequestId(string editingMessageId)
91+
{
92+
currentEditingRequestId = editingMessageId;
93+
return Task.CompletedTask;
94+
}
95+
96+
public async Task SetFailedMessageAsResolved()
97+
{
98+
if (currentMessage != null)
99+
{
100+
currentMessage.Status = FailedMessageStatus.Resolved;
101+
await UpdateFailedMessage(currentMessage);
102+
}
103+
}
104+
105+
public async Task SaveChanges()
106+
{
107+
await dbContext.SaveChangesAsync();
108+
}
109+
110+
public void Dispose()
111+
{
112+
scope.Dispose();
113+
}
114+
}

src/ServiceControl.Persistence.Sql.Core/Implementation/EventLogDataStore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public async Task Add(EventLogItem logItem)
6969
RelatedTo = entity.RelatedTo != null ? JsonSerializer.Deserialize<List<string>>(entity.RelatedTo) : null
7070
}).ToList();
7171

72-
// Version could be based on the latest RaisedAt timestampn but the paging can affect this result, given that the latest may not be retrieved
72+
// Version could be based on the latest RaisedAt timestamp but the paging can affect this result, given that the latest may not be retrieved
7373
var version = entities.Any() ? entities.Max(e => e.RaisedAt).Ticks.ToString() : "0";
7474

7575
return (items, total, version);

src/ServiceControl.Persistence.Sql.Core/Implementation/ExternalIntegrationRequestsDataStore.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ namespace ServiceControl.Persistence.Sql.Core.Implementation;
1010
using Entities;
1111
using Microsoft.EntityFrameworkCore;
1212
using Microsoft.Extensions.DependencyInjection;
13-
using Microsoft.Extensions.Hosting;
1413
using Microsoft.Extensions.Logging;
1514
using ServiceControl.ExternalIntegrations;
1615
using ServiceControl.Persistence;
1716

18-
public class ExternalIntegrationRequestsDataStore : IExternalIntegrationRequestsDataStore, IHostedService
17+
public class ExternalIntegrationRequestsDataStore : IExternalIntegrationRequestsDataStore, IAsyncDisposable
1918
{
2019
readonly IServiceProvider serviceProvider;
2120
readonly ILogger<ExternalIntegrationRequestsDataStore> logger;
@@ -53,7 +52,6 @@ public async Task StoreDispatchRequest(IEnumerable<ExternalIntegrationDispatchRe
5352

5453
var entity = new ExternalIntegrationDispatchRequestEntity
5554
{
56-
Id = "ExternalIntegrationDispatchRequests/" + Guid.NewGuid(),
5755
DispatchContextJson = JsonSerializer.Serialize(dispatchRequest.DispatchContext, JsonOptions),
5856
CreatedAt = DateTime.UtcNow
5957
};
@@ -139,13 +137,9 @@ async Task DispatchBatch(CancellationToken cancellationToken)
139137
await dbContext.SaveChangesAsync(cancellationToken);
140138
}
141139

142-
public Task StartAsync(CancellationToken cancellationToken)
143-
{
144-
// The dispatcher will start when Subscribe is called
145-
return Task.CompletedTask;
146-
}
140+
public async Task StopAsync(CancellationToken cancellationToken) => await DisposeAsync();
147141

148-
public async Task StopAsync(CancellationToken cancellationToken)
142+
public async ValueTask DisposeAsync()
149143
{
150144
if (isDisposed)
151145
{

0 commit comments

Comments
 (0)