public async Task CollectStages(ScorecardBuildBreakdown buildBreakdown) { string timelineLinkWithAttempts = $"{buildBreakdown.BuildSummary.TimelineLink}?changeId=1"; Utilities.WriteDebug($"Querying AzDO API: {timelineLinkWithAttempts}", Log, LogLevel); JObject jsonTimelineResponse = await GetAzdoApiResponseAsync(timelineLinkWithAttempts); BuildTimeline timeline = jsonTimelineResponse.ToObject <BuildTimeline>(); if (timeline.Records != null) { // We're going to use this to store previous attempts as we find them Dictionary <string, BuildTimeline> previousAttemptTimelines = new Dictionary <string, BuildTimeline>(); foreach (BuildTimelineEntry record in timeline.Records) { // We measure times at the stage level because this is the simplest thing to do // By taking the min start time and max end time of all the stages except for the ones we exclude, // we can determine a pretty accurate measurement of how long it took to rollout if ((record.Type == "Checkpoint.Approval" || record.Type == "Stage") && !RepoConfig.ExcludeStages.Any(s => s == record.Name)) { buildBreakdown.BuildSummary.Stages.Add(record); if (record.PreviousAttempts.Count > 0) { // we're going to just add these attempts as additional stages foreach (PreviousAttempt attempt in record.PreviousAttempts) { if (!previousAttemptTimelines.ContainsKey(attempt.TimelineId)) { previousAttemptTimelines.Add(attempt.TimelineId, (await GetAzdoApiResponseAsync($"{buildBreakdown.BuildSummary.TimelineLink}/{attempt.TimelineId}")).ToObject <BuildTimeline>()); } if (previousAttemptTimelines[attempt.TimelineId] != null) { buildBreakdown.BuildSummary.Stages.Add(previousAttemptTimelines[attempt.TimelineId].Records .Where(t => (t.Type == "Checkpoint.Approval" || t.Type == "Stage") && t.Name == record.Name).First()); } } } } } if (buildBreakdown.BuildSummary.Stages.Any(s => s.Name.StartsWith("deploy", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(s.EndTime))) { buildBreakdown.BuildSummary.DeploymentReached = true; Utilities.WriteDebug($"Build {buildBreakdown.BuildSummary.BuildNumber} determined to have reached deployment.", Log, LogLevel); } else { Utilities.WriteDebug($"Build {buildBreakdown.BuildSummary.BuildNumber} determined to NOT have reached deployment.", Log, LogLevel); } } }
/// <summary> /// Calculates the "Time to Rollout" portion of the scorecard /// </summary> /// <returns>A timespan representing the total time to rollout and a list of scorecard build breakdowns for each rollout</returns> public async Task <TimeSpan> CalculateTimeToRolloutAsync() { List <TimeSpan> rolloutBuildTimes = new List <TimeSpan>(); // Loop over all the builds in the returned content and calculate start and end times foreach (ScorecardBuildBreakdown build in BuildBreakdowns) { TimeSpan duration = TimeSpan.Zero; string timelineLinkWithAttempts = $"{build.BuildSummary.TimelineLink}?changeId=1"; JObject jsonTimelineResponse = await GetAzdoApiResponseAsync(timelineLinkWithAttempts); BuildTimeline timeline = jsonTimelineResponse.ToObject <BuildTimeline>(); // We're going to use this to store previous attempts as we find them Dictionary <string, BuildTimeline> previousAttemptTimelines = new Dictionary <string, BuildTimeline>(); if (timeline.Records != null) { List <BuildTimelineEntry> stages = new List <BuildTimelineEntry>(); List <BuildTimelineEntry> approvalCheckpoints = new List <BuildTimelineEntry>(); foreach (BuildTimelineEntry record in timeline.Records) { // We measure times at the stage level because this is the simplest thing to do // By taking the min start time and max end time of all the stages except for the ones we exclude, // we can determine a pretty accurate measurement of how long it took to rollout if ((record.Type == "Checkpoint.Approval" || record.Type == "Stage") && !RepoConfig.ExcludeStages.Any(s => s == record.Name)) { stages.Add(record); if (record.PreviousAttempts.Count > 0) { // we're going to just add these attempts as additional stages foreach (PreviousAttempt attempt in record.PreviousAttempts) { if (!previousAttemptTimelines.ContainsKey(attempt.TimelineId)) { previousAttemptTimelines.Add(attempt.TimelineId, (await GetAzdoApiResponseAsync($"{build.BuildSummary.TimelineLink}/{attempt.TimelineId}")).ToObject <BuildTimeline>()); } if (previousAttemptTimelines[attempt.TimelineId] != null) { stages.Add(previousAttemptTimelines[attempt.TimelineId].Records .Where(t => (t.Type == "Checkpoint.Approval" || t.Type == "Stage") && t.Name == record.Name).First()); } } } } } duration = GetPipelineDurationFromStages(stages); if (stages.Any(s => s.Name.StartsWith("deploy", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(s.EndTime))) { build.BuildSummary.DeploymentReached = true; } } rolloutBuildTimes.Add(duration); build.Score.TimeToRollout = duration; } return(new TimeSpan(rolloutBuildTimes.Sum(t => t.Ticks))); }