Repository: microsoft/onefuzz
File: src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs (C#)

Code before:
        return OneFuzzResultVoid.Ok;
    }

    private static WorkItemTrackingHttpClient GetAdoClient(Uri baseUrl, string token) {
        return new WorkItemTrackingHttpClient(baseUrl, new VssBasicCredential("PAT", token));
    }

    private static async Async.Task<Dictionary<string, WorkItemField2>> GetValidFields(WorkItemTrackingHttpClient client, string? project) {
        return (await client.GetWorkItemFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields))
            .ToDictionary(field => field.ReferenceName.ToLowerInvariant());
    }

    private static async Async.Task ProcessNotification(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogger logTracer, IList<(string, string)> notificationInfo, Renderer? renderer = null) {
        if (!config.AdoFields.TryGetValue(TITLE_FIELD, out var issueTitle)) {
            issueTitle = "{{ report.crash_site }} - {{ report.executable }}";
        }
        var instanceUrl = context.Creds.GetInstanceUrl();
        renderer ??= await Renderer.ConstructRenderer(context, container, filename, issueTitle, report, instanceUrl, logTracer);
        var project = renderer.Render(config.Project, instanceUrl);

        var authToken = await context.SecretsOperations.GetSecretValue(config.AuthToken.Secret);
        var client = GetAdoClient(config.BaseUrl, authToken!);

        var renderedConfig = RenderAdoTemplate(logTracer, renderer, config, instanceUrl);
        var ado = new AdoConnector(renderedConfig, project!, client, instanceUrl, logTracer, await GetValidFields(client, project));
        await ado.Process(notificationInfo);
    }

    public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer renderer, AdoTemplate original, Uri instanceUrl) {
        var adoFields = original.AdoFields.ToDictionary(kvp => kvp.Key, kvp => Render(renderer, kvp.Value, instanceUrl, logTracer));
        var onDuplicateAdoFields = original.OnDuplicate.AdoFields.ToDictionary(kvp => kvp.Key, kvp => Render(renderer, kvp.Value, instanceUrl, logTracer));

        var systemTitle = renderer.IssueTitle;
        if (systemTitle.Length > MAX_SYSTEM_TITLE_LENGTH) {
            var systemTitleHashString = Convert.ToHexString(
                System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(systemTitle))
            );
            // try to avoid naming collisions caused by the trim by appending the first 8 characters of the title's hash at the end
            var truncatedTitle = $"{systemTitle[..(MAX_SYSTEM_TITLE_LENGTH - 14)]}... [{systemTitleHashString[..8]}]";

            // TITLE_FIELD is required in adoFields (ADO won't allow you to create a work item without a title)
            adoFields[TITLE_FIELD] = truncatedTitle;

            // It may or may not be present in on_duplicate
            if (onDuplicateAdoFields.ContainsKey(TITLE_FIELD)) {
                onDuplicateAdoFields[TITLE_FIELD] = truncatedTitle;
            }

            logTracer.LogInformation(
                "System.Title \"{Title}\" was too long ({TitleLength} chars); shortend it to \"{NewTitle}\" ({NewTitleLength} chars)",
                systemTitle,

Review comment (suggestion):
The `AdoConnector` already has an instance of the rendered config so we actually don't even need to modify the `Process` function signature.

Code after:
        return OneFuzzResultVoid.Ok;
    }

    private static WorkItemTrackingHttpClient GetAdoClient(Uri baseUrl, string token) {
        return new WorkItemTrackingHttpClient(baseUrl, new VssBasicCredential("PAT", token));
    }

    private static async Async.Task<Dictionary<string, WorkItemField2>> GetValidFields(WorkItemTrackingHttpClient client, string? project) {
        return (await client.GetWorkItemFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields))
            .ToDictionary(field => field.ReferenceName.ToLowerInvariant());
    }

    private static async Async.Task ProcessNotification(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogger logTracer, IList<(string, string)> notificationInfo, Renderer? renderer = null) {
        if (!config.AdoFields.TryGetValue(TITLE_FIELD, out var issueTitle)) {
            issueTitle = "{{ report.crash_site }} - {{ report.executable }}";
        }
        var instanceUrl = context.Creds.GetInstanceUrl();
        renderer ??= await Renderer.ConstructRenderer(context, container, filename, issueTitle, report, instanceUrl, logTracer);
        var project = renderer.Render(config.Project, instanceUrl);

        var authToken = await context.SecretsOperations.GetSecretValue(config.AuthToken.Secret);
        var client = GetAdoClient(config.BaseUrl, authToken!);

        var renderedConfig = RenderAdoTemplate(logTracer, renderer, config, instanceUrl);
        var ado = new AdoConnector(renderedConfig, project!, client, instanceUrl, logTracer, await GetValidFields(client, project));
        await ado.Process(notificationInfo, config.AdoDuplicateFields);
    }

    public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer renderer, AdoTemplate original, Uri instanceUrl) {
        var adoFields = original.AdoFields.ToDictionary(kvp => kvp.Key, kvp => Render(renderer, kvp.Value, instanceUrl, logTracer));
        var onDuplicateAdoFields = original.OnDuplicate.AdoFields.ToDictionary(kvp => kvp.Key, kvp => Render(renderer, kvp.Value, instanceUrl, logTracer));

        var systemTitle = renderer.IssueTitle;
        if (systemTitle.Length > MAX_SYSTEM_TITLE_LENGTH) {
            var systemTitleHashString = Convert.ToHexString(
                System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(systemTitle))
            );
            // try to avoid naming collisions caused by the trim by appending the first 8 characters of the title's hash at the end
            var truncatedTitle = $"{systemTitle[..(MAX_SYSTEM_TITLE_LENGTH - 14)]}... [{systemTitleHashString[..8]}]";

            // TITLE_FIELD is required in adoFields (ADO won't allow you to create a work item without a title)
            adoFields[TITLE_FIELD] = truncatedTitle;

            // It may or may not be present in on_duplicate
            if (onDuplicateAdoFields.ContainsKey(TITLE_FIELD)) {
                onDuplicateAdoFields[TITLE_FIELD] = truncatedTitle;
            }

            logTracer.LogInformation(
                "System.Title \"{Title}\" was too long ({TitleLength} chars); shortend it to \"{NewTitle}\" ({NewTitleLength} chars)",
                systemTitle,