File: GetHelixWorkItems.cs
Web Access
Project: src\src\Microsoft.DotNet.Helix\Sdk\Microsoft.DotNet.Helix.Sdk.csproj (Microsoft.DotNet.Helix.Sdk)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Helix.Client.Models;
using Newtonsoft.Json;
using Task = System.Threading.Tasks.Task;
 
namespace Microsoft.DotNet.Helix.Sdk
{
    public class GetHelixWorkItems : HelixTask
    {
        public const int DelayBetweenHelixApiCallsInMs = 500;
 
        /// <summary>
        /// An array of Helix Jobs for which to get status
        /// </summary>
        [Required]
        public ITaskItem[] Jobs { get; set; }
 
        [Output]
        public ITaskItem[] WorkItems { get; set; }
 
        protected override async Task ExecuteCore(CancellationToken cancellationToken)
        {
            WorkItems = (await Task.WhenAll(Jobs.Select(j => GetWorkItemsAsync(j, cancellationToken))).ConfigureAwait(false)).SelectMany(r => r).ToArray();
        }
 
        private async Task<IEnumerable<ITaskItem>> GetWorkItemsAsync(ITaskItem job, CancellationToken cancellationToken)
        {
            var jobName = job.GetMetadata("Identity");
 
            Log.LogMessage($"Getting status of job {jobName}");
 
            var status = await HelixApi.Job.PassFailAsync(jobName, cancellationToken).ConfigureAwait(false);
 
            if (status.Working > 0)
            {
                Log.LogError(
                    FailureCategory.Build,
                    $"This task can only be used on completed jobs. There are {status.Working} of {status.Total} unfinished work items.");
                return Array.Empty<ITaskItem>();
            }
 
            var workItems = new List<ITaskItem>();
 
            IDictionary<string, string> CreateWorkItemMetadata(string name)
            {
                var metadata = job.CloneCustomMetadata() as IDictionary<string, string>;
                metadata["JobName"] = jobName;
                metadata["WorkItemName"] = name;
                var consoleUri = HelixApi.Options.BaseUri.AbsoluteUri.TrimEnd('/') + $"/api/2019-06-17/jobs/{jobName}/workitems/{Uri.EscapeDataString(name)}/console";
                metadata["ConsoleOutputUri"] = consoleUri;
 
                return metadata;
            }
 
            ITaskItem2 CreateTaskItem(string workItemName, IDictionary<string, string> metadata)
            {
                ITaskItem2 workItem = new TaskItem(workItemName);
 
                foreach(KeyValuePair<string, string> entry in metadata)
                {
                    workItem.SetMetadataValueLiteral(entry.Key, entry.Value);
                }
 
                return workItem;
            }
 
            foreach (string name in status.Passed)
            {
                string wi = Helpers.CleanWorkItemName(name);
 
                var metadata = CreateWorkItemMetadata(wi);
                metadata["Failed"] = "false";
 
                workItems.Add(CreateTaskItem($"{jobName}/{wi}", metadata));
            }
 
            foreach (string name in status.Failed)
            {
                string wi = Helpers.CleanWorkItemName(name);
 
                var metadata = CreateWorkItemMetadata(wi);
                metadata["Failed"] = "true";
 
                try
                {
                    // Do this serially with a delay because total failure can hit throttling
                    // latestOnly parameter is set false here to download all possible files
                    var files = await HelixApi.WorkItem.ListFilesAsync(wi, jobName, false, cancellationToken).ConfigureAwait(false);
 
                    if (!string.IsNullOrEmpty(AccessToken))
                    {
                        // Add AccessToken to all file links because the api requires auth if we submitted the job with auth
                        files = files
                                .Select(file => new UploadedFile(file.Name, file.Link + "?access_token=" + AccessToken))
                                .ToImmutableList();
                    }
 
                    metadata["UploadedFiles"] = JsonConvert.SerializeObject(files);
                }
                catch (Exception ex)
                {
                    Log.LogWarningFromException(ex);
                }
 
                workItems.Add(CreateTaskItem($"{jobName}/{wi}", metadata));
                await Task.Delay(DelayBetweenHelixApiCallsInMs, cancellationToken);
            }
 
            return workItems;
        }
    }
}