IResultFromExtension <WorkItemLink[]> TryConvertWorkItemLinks(string[] workItemIds, string?releaseNotePrefix, string baseUrl)
        {
            systemLog.InfoFormat("Getting work items {0} from Jira", string.Join(", ", workItemIds));
            var response = jira.Value.GetIssues(workItemIds.ToArray()).GetAwaiter().GetResult();

            if (response is IFailureResult failureResult)
            {
                return(FailureWithLog(failureResult.Errors));
            }

            var issues = ((ISuccessResult <JiraIssue[]>)response).Value;

            if (issues.Length == 0)
            {
                systemLog.InfoFormat("No work items returned from Jira");
                return(ResultFromExtension <WorkItemLink[]> .Success(Array.Empty <WorkItemLink>()));
            }

            var issueMap = issues.ToDictionary(x => x.Key, StringComparer.OrdinalIgnoreCase);

            var workItemsNotFound = workItemIds.Where(x => !issueMap.ContainsKey(x)).ToArray();

            if (workItemsNotFound.Length > 0)
            {
                systemLog.Warn($"Parsed work item ids {string.Join(", ", workItemsNotFound)} from commit messages but could not locate them in Jira");
            }

            return(ResultFromExtension <WorkItemLink[]> .Success(
                       workItemIds
                       .Where(workItemId => issueMap.ContainsKey(workItemId))
                       .Select(
                           workItemId =>
            {
                var issue = issueMap[workItemId];
                return new WorkItemLink
                {
                    Id = issue.Key,
                    Description = GetReleaseNote(issueMap[workItemId], releaseNotePrefix),
                    LinkUrl = baseUrl + "/browse/" + workItemId,
                    Source = JiraConfigurationStore.CommentParser
                };
            })
                       .Where(i => i != null)
                       // ReSharper disable once RedundantEnumerableCastCall
                       .Cast <WorkItemLink>() // cast back from `WorkItemLink?` type to keep the compiler happy
                       .ToArray()));
        }
        public async Task <IResultFromExtension <JiraIssue[]> > GetIssues(string[] workItemIds)
        {
            var workItemQuery = $"id in ({string.Join(", ", workItemIds.Select(x => x.ToUpper()))})";

            // WARNING: while the Jira API documentation says that validateQuery values of true/false are deprecated,
            // that is only valid for Jira Cloud. Jira Server only supports true/false
            var content = JsonConvert.SerializeObject(
                new
                { jql = workItemQuery, fields = new[] { "summary", "comment" }, maxResults = 10000, validateQuery = "false" });

            string errorMessage;

            try
            {
                using var response = await httpClient.PostAsync($"{baseUrl}/{baseApiUri}/search", new StringContent (content, Encoding.UTF8, "application/json"));

                if (response.IsSuccessStatusCode)
                {
                    var result = await GetResult <JiraSearchResult>(response);

                    if (result == null)
                    {
                        systemLog.Info("Jira Work Item data not found in response body");
                        return(ResultFromExtension <JiraIssue[]> .Failed("Jira Work Item data not found in response body"));
                    }

                    systemLog.Info($"Retrieved Jira Work Item data for work item ids {string.Join(", ", result.Issues.Select(wi => wi.Key))}");
                    return(ResultFromExtension <JiraIssue[]> .Success(result.Issues));
                }

                if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
                {
                    systemLog.Info("Authentication failure, check the Jira access token is valid and has permissions to read work items");
                    return(ResultFromExtension <JiraIssue[]> .Failed("Authentication failure, check the Jira access token is valid and has permissions to read work items"));
                }

                var errorResult = await GetResult <JiraErrorResult>(response);

                errorMessage = $"Failed to retrieve Jira issues from {baseUrl}. Response Code: {response.StatusCode}{(errorResult?.ErrorMessages.Any() == true ? $" (Errors: {string.Join(", ", errorResult.ErrorMessages)})" : "")}";
            }
            catch (HttpRequestException e)
            {
                errorMessage = $"Failed to retrieve Jira issues '{string.Join(", ", workItemIds)}' from {baseUrl}. (Reason: {e.Message})";
            }
            catch (TaskCanceledException e)
            {
                errorMessage = $"Failed to retrieve Jira issues '{string.Join(", ", workItemIds)}' from {baseUrl}. (Reason: {e.Message})";
            }

            systemLog.Warn(errorMessage);

            return(ResultFromExtension <JiraIssue[]> .Failed(errorMessage));
        }