// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using HordeServer.Server; using HordeServer.Utilities; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; namespace HordeServer.Dashboard { /// /// /// public interface IDashboardPreview { /// /// The unique ID of the preview item /// public int Id { get; } /// /// When the preview item was created /// public DateTime CreatedAt { get; } /// /// A summary of what the preview item changes /// public string Summary { get; } /// /// The CL the preview was deployed in /// public int? DeployedCL { get; } /// /// Whather the preview is under consideration, if false the preview item didn't pass muster /// public bool Open { get; } /// /// An example of the preview site users can view the changes /// public string? ExampleLink { get; } /// /// Optional Link for discussion the preview item /// public string? DiscussionLink { get; } /// /// Optional Link for discussing the preview item /// public string? TrackingLink { get; } } /// /// Collection of preview documents /// public interface IDashboardPreviewCollection { /// /// Add a preview item /// /// /// /// Task AddPreviewAsync(string summary, CancellationToken cancellationToken); /// /// Update a dashboard preview item /// /// /// /// /// /// /// /// /// /// Task UpdatePreviewAsync(int previewId, string? summary = null, int? deployedCL = null, bool? open = null, string? exampleLink = null, string? discussionLink = null, string? trackingLink = null, CancellationToken cancellationToken = default); /// /// Finds preview items with the given criterial /// /// /// /// Task> FindPreviewsAsync(bool? open = null, CancellationToken cancellationToken = default); } internal class DashboardPreviewCollection : IDashboardPreviewCollection { [SingletonDocument("dashboard-preview-ledger", "63f8e2154a2e859581b5bb24")] internal class PreviewLedger : SingletonBase { public int NextId { get; set; } } internal class PreviewDocument : IDashboardPreview { [BsonId] public int Id { get; set; } public DateTime CreatedAt { get; set; } public string Summary { get; set; } public bool Open { get; set; } [BsonIgnoreIfNull] public int? DeployedCL { get; set; } [BsonIgnoreIfNull] public string? ExampleLink { get; set; } [BsonIgnoreIfNull] public string? DiscussionLink { get; set; } [BsonIgnoreIfNull] public string? TrackingLink { get; set; } [BsonConstructor] private PreviewDocument() { Summary = String.Empty; } public PreviewDocument(int id, string summary) { Id = id; Summary = summary; CreatedAt = DateTime.UtcNow; Open = true; } } readonly IMongoCollection _previews; readonly ISingletonDocument _ledgerSingleton; public DashboardPreviewCollection(IMongoService mongoService) { _ledgerSingleton = new SingletonDocument(mongoService); _previews = mongoService.GetCollection("DashboardPreviews"); } public async Task AddPreviewAsync(string summary, CancellationToken cancellationToken) { PreviewLedger ledger = await _ledgerSingleton.UpdateAsync(x => x.NextId++, cancellationToken); PreviewDocument newPreview = new PreviewDocument(ledger.NextId, summary); await _previews.InsertOneAsync(newPreview, (InsertOneOptions?)null, cancellationToken); return newPreview; } public async Task UpdatePreviewAsync(string summary, CancellationToken cancellationToken) { PreviewLedger ledger = await _ledgerSingleton.UpdateAsync(x => x.NextId++, cancellationToken); PreviewDocument newPreview = new PreviewDocument(ledger.NextId, summary); await _previews.InsertOneAsync(newPreview, (InsertOneOptions?)null, cancellationToken); return newPreview; } public async Task UpdatePreviewAsync(int previewId, string? summary = null, int? deployedCL = null, bool? open = null, string? exampleLink = null, string? discussionLink = null, string? trackingLink = null, CancellationToken cancellationToken = default) { UpdateDefinitionBuilder updateBuilder = Builders.Update; List> updates = new List>(); if (summary != null) { updates.Add(updateBuilder.Set(x => x.Summary, summary)); } if (deployedCL != null) { updates.Add(updateBuilder.Set(x => x.DeployedCL, deployedCL)); } if (open != null) { updates.Add(updateBuilder.Set(x => x.Open, open)); } if (exampleLink != null) { updates.Add(updateBuilder.Set(x => x.ExampleLink, exampleLink)); } if (discussionLink != null) { updates.Add(updateBuilder.Set(x => x.DiscussionLink, discussionLink)); } if (trackingLink != null) { updates.Add(updateBuilder.Set(x => x.TrackingLink, trackingLink)); } if (updates.Count == 0) { await _previews.Find(x => x.Id == previewId).FirstOrDefaultAsync(cancellationToken); } if (await _previews.FindOneAndUpdateAsync(x => x.Id == previewId, updateBuilder.Combine(updates), null, cancellationToken) == null) { return null; } return await _previews.Find(x => x.Id == previewId).FirstOrDefaultAsync(cancellationToken); } public async Task> FindPreviewsAsync(bool? open = null, CancellationToken cancellationToken = default) { FilterDefinition filter = Builders.Filter.Empty; if (open != null) { filter &= Builders.Filter.Eq(x => x.Open, open); } List results = await _previews.Find(filter).ToListAsync(cancellationToken); return results.Select(x => x).ToList(); } } }