// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Horde.Build.Agents; using Horde.Build.Agents.Pools; using Horde.Build.Logs; using Horde.Build.Server; using Horde.Build.Streams; using Horde.Build.Utilities; using HordeCommon; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; namespace Horde.Build.Jobs { using LogId = ObjectId; using PoolId = StringId; using StreamId = StringId; using TemplateId = StringId; /// /// Collection of JobStepRef documents /// public class JobStepRefCollection : IJobStepRefCollection { class JobStepRef : IJobStepRef { [BsonId] public JobStepRefId Id { get; set; } public string JobName { get; set; } = "Unknown"; public string Name { get; set; } public StreamId StreamId { get; set; } public TemplateId TemplateId { get; set; } public int Change { get; set; } public LogId? LogId { get; set; } public PoolId? PoolId { get; set; } public AgentId? AgentId { get; set; } public JobStepOutcome? Outcome { get; set; } [BsonIgnoreIfNull] public int? LastSuccess { get; } [BsonIgnoreIfNull] public int? LastWarning { get; } public float BatchWaitTime { get; set; } public float BatchInitTime { get; set; } [BsonIgnoreIfNull] public DateTimeOffset? StartTime { get; set; } [BsonIgnoreIfNull] public DateTime? StartTimeUtc { get; set; } [BsonIgnoreIfNull] public DateTimeOffset? FinishTime { get; set; } [BsonIgnoreIfNull] public DateTime? FinishTimeUtc { get; set; } [BsonIgnoreIfNull] public bool? UpdateIssues { get; set; } DateTime IJobStepRef.StartTimeUtc => StartTimeUtc ?? StartTime?.UtcDateTime ?? default; DateTime? IJobStepRef.FinishTimeUtc => FinishTimeUtc ?? FinishTime?.UtcDateTime; string IJobStepRef.NodeName => Name; bool IJobStepRef.UpdateIssues => UpdateIssues ?? false; public JobStepRef(JobStepRefId id, string jobName, string nodeName, StreamId streamId, TemplateId templateId, int change, LogId? logId, PoolId? poolId, AgentId? agentId, JobStepOutcome? outcome, bool updateIssues, int? lastSuccess, int? lastWarning, float batchWaitTime, float batchInitTime, DateTime startTimeUtc, DateTime? finishTimeUtc) { Id = id; JobName = jobName; Name = nodeName; StreamId = streamId; TemplateId = templateId; Change = change; LogId = logId; PoolId = poolId; AgentId = agentId; Outcome = outcome; UpdateIssues = updateIssues; LastSuccess = lastSuccess; LastWarning = lastWarning; BatchWaitTime = batchWaitTime; BatchInitTime = batchInitTime; StartTime = startTimeUtc; StartTimeUtc = startTimeUtc; FinishTime = finishTimeUtc; FinishTimeUtc = finishTimeUtc; } } readonly IMongoCollection _jobStepRefs; /// /// Constructor /// /// The database service instance public JobStepRefCollection(MongoService mongoService) { _jobStepRefs = mongoService.GetCollection("JobStepRefs", keys => keys.Ascending(x => x.StreamId).Ascending(x => x.TemplateId).Ascending(x => x.Name).Descending(x => x.Change)); } /// public async Task InsertOrReplaceAsync(JobStepRefId id, string jobName, string stepName, StreamId streamId, TemplateId templateId, int change, LogId? logId, PoolId? poolId, AgentId? agentId, JobStepOutcome? outcome, bool updateIssues, int? lastSuccess, int? lastWarning, float waitTime, float initTime, DateTime startTimeUtc, DateTime? finishTimeUtc) { JobStepRef newJobStepRef = new JobStepRef(id, jobName, stepName, streamId, templateId, change, logId, poolId, agentId, outcome, updateIssues, lastSuccess, lastWarning, waitTime, initTime, startTimeUtc, finishTimeUtc); await _jobStepRefs.ReplaceOneAsync(Builders.Filter.Eq(x => x.Id, newJobStepRef.Id), newJobStepRef, new ReplaceOptions { IsUpsert = true }); return newJobStepRef; } /// public async Task> GetStepsForNodeAsync(StreamId streamId, TemplateId templateId, string nodeName, int? change, bool includeFailed, int count) { // Find all the steps matching the given criteria FilterDefinitionBuilder filterBuilder = Builders.Filter; FilterDefinition filter = FilterDefinition.Empty; filter &= filterBuilder.Eq(x => x.StreamId, streamId); filter &= filterBuilder.Eq(x => x.TemplateId, templateId); filter &= filterBuilder.Eq(x => x.Name, nodeName); if (change != null) { filter &= filterBuilder.Lte(x => x.Change, change.Value); } if (!includeFailed) { filter &= filterBuilder.Ne(x => x.Outcome, JobStepOutcome.Failure); } List steps = await _jobStepRefs.Find(filter).SortByDescending(x => x.Change).ThenByDescending(x => x.StartTime).Limit(count).ToListAsync(); return steps.ConvertAll(x => x); } /// public async Task GetPrevStepForNodeAsync(StreamId streamId, TemplateId templateId, string nodeName, int change, JobStepOutcome? outcome = null, bool? updateIssues = null) { FilterDefinitionBuilder filterBuilder = Builders.Filter; FilterDefinition filter = FilterDefinition.Empty; filter &= filterBuilder.Eq(x => x.StreamId, streamId); filter &= filterBuilder.Eq(x => x.TemplateId, templateId); filter &= filterBuilder.Eq(x => x.Name, nodeName); filter &= filterBuilder.Lt(x => x.Change, change); if (outcome != null) { filter &= filterBuilder.Eq(x => x.Outcome, outcome); } else { filter &= filterBuilder.Ne(x => x.Outcome, null); } if (updateIssues != null) { filter &= filterBuilder.Ne(x => x.UpdateIssues, false); } return await _jobStepRefs.Find(filter).SortByDescending(x => x.Change).FirstOrDefaultAsync(); } /// public async Task GetNextStepForNodeAsync(StreamId streamId, TemplateId templateId, string nodeName, int change, JobStepOutcome? outcome = null, bool? updateIssues = null) { FilterDefinitionBuilder filterBuilder = Builders.Filter; FilterDefinition filter = FilterDefinition.Empty; filter &= filterBuilder.Eq(x => x.StreamId, streamId); filter &= filterBuilder.Eq(x => x.TemplateId, templateId); filter &= filterBuilder.Eq(x => x.Name, nodeName); filter &= filterBuilder.Gt(x => x.Change, change); if (outcome != null) { filter &= filterBuilder.Eq(x => x.Outcome, outcome); } else { filter &= filterBuilder.Ne(x => x.Outcome, null); } if (updateIssues != null) { filter &= filterBuilder.Ne(x => x.UpdateIssues, false); } return await _jobStepRefs.Find(filter).SortBy(x => x.Change).FirstOrDefaultAsync(); } } }