public void DuplicatesGetIgnored()
        {
            var store          = Substitute.For <IJiraConfigurationStore>();
            var jiraClient     = Substitute.For <IJiraRestClient>();
            var jiraClientLazy = new Lazy <IJiraRestClient>(() => jiraClient);

            store.GetBaseUrl().Returns("https://github.com");
            store.GetIsEnabled().Returns(true);
            store.GetJiraUsername().Returns("user");
            store.GetJiraPassword().Returns("password".ToSensitiveString());
            var jiraIssue = new JiraIssue
            {
                Key    = "JRE-1234",
                Fields = new JiraIssueFields
                {
                    Comments = new JiraIssueComments()
                }
            };

            jiraClient.GetIssues(Arg.Any <string[]>()).Returns(ResultFromExtension <JiraIssue[]> .Success(new[] { jiraIssue }));

            var mapper = new WorkItemLinkMapper(store, new CommentParser(), jiraClientLazy, Substitute.For <ISystemLog>());

            var workItems = mapper.Map(
                new OctopusBuildInformation
            {
                Commits = new[]
        public IResultFromExtension <WorkItemLink[]> Map(OctopusBuildInformation buildInformation)
        {
            if (!IsEnabled)
            {
                return(ResultFromExtension <WorkItemLink[]> .ExtensionDisabled());
            }

            if (string.IsNullOrEmpty(store.GetJiraUsername()) ||
                string.IsNullOrEmpty(store.GetJiraPassword()?.Value))
            {
                return(FailureWithLog("Username/password not configured"));
            }

            var baseUrl = store.GetBaseUrl();

            if (string.IsNullOrWhiteSpace(baseUrl))
            {
                return(FailureWithLog("No BaseUrl configured"));
            }

            var releaseNotePrefix = store.GetReleaseNotePrefix();
            var workItemIds       = commentParser.ParseWorkItemIds(buildInformation).Distinct().ToArray();

            if (workItemIds.Length == 0)
            {
                return(ResultFromExtension <WorkItemLink[]> .Success(Array.Empty <WorkItemLink>()));
            }

            return(TryConvertWorkItemLinks(workItemIds, releaseNotePrefix, baseUrl));
        }
        public string GetWorkItemDescription(string linkData, string releaseNotePrefix, string releaseNote)
        {
            var store          = Substitute.For <IJiraConfigurationStore>();
            var jiraClient     = Substitute.For <IJiraRestClient>();
            var jiraClientLazy = new Lazy <IJiraRestClient>(() => jiraClient);
            var jiraIssue      = new JiraIssue
            {
                Key    = linkData,
                Fields = new JiraIssueFields
                {
                    Summary  = "Test title",
                    Comments = new JiraIssueComments
                    {
                        Total    = 1,
                        Comments = new[]
                        {
                            new JiraIssueComment
                            {
                                Body = releaseNote
                            }
                        }
                    }
                }
            };

            jiraClient.GetIssues(Arg.Any <string[]>()).Returns(ResultFromExtension <JiraIssue[]> .Success(new[] { jiraIssue }));

            return(new WorkItemLinkMapper(store, new CommentParser(), jiraClientLazy, Substitute.For <ISystemLog>()).GetReleaseNote(jiraIssue, releaseNotePrefix));
        }
        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));
        }
 public IResultFromExtension DoSomethingWithNoObjectToReturn(Behaviour behaviour)
 {
     if (behaviour == Behaviour.Failure)
     {
         return(ResultFromExtension.Failed("Some failure reason"));
     }
     if (behaviour == Behaviour.Disabled)
     {
         return(ResultFromExtension.ExtensionDisabled());
     }
     return(ResultFromExtension.Success());
 }
 public IResultFromExtension <TestObjectBeingReturned> DoSomething(Behaviour behaviour)
 {
     if (behaviour == Behaviour.Failure)
     {
         return(ResultFromExtension <TestObjectBeingReturned> .Failed("Some failure reason"));
     }
     if (behaviour == Behaviour.Disabled)
     {
         return(ResultFromExtension <TestObjectBeingReturned> .ExtensionDisabled());
     }
     return(ResultFromExtension <TestObjectBeingReturned> .Success(new TestObjectBeingReturned("Some Name")));
 }
        public IResultFromExtension <WorkItemLink[]> Map(OctopusBuildInformation buildInformation)
        {
            // For ADO, we should ignore anything that wasn't built by ADO because we get work items from the build
            if (!IsEnabled)
            {
                return(ResultFromExtension <WorkItemLink[]> .ExtensionDisabled());
            }
            if (buildInformation?.BuildEnvironment != "Azure DevOps" ||
                string.IsNullOrWhiteSpace(buildInformation?.BuildUrl))
            {
                return(ResultFromExtension <WorkItemLink[]> .Success(Array.Empty <WorkItemLink>()));
            }

            return(client.GetBuildWorkItemLinks(AdoBuildUrls.ParseBrowserUrl(buildInformation.BuildUrl)));
        }
        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()));
        }
Beispiel #9
0
        public IResultFromExtension <WorkItemLink[]> Map(OctopusBuildInformation buildInformation)
        {
            if (!IsEnabled)
            {
                return(ResultFromExtension <WorkItemLink[]> .ExtensionDisabled());
            }

            var baseUrl = store.GetBaseUrl();

            if (string.IsNullOrWhiteSpace(baseUrl))
            {
                return(ResultFromExtension <WorkItemLink[]> .Failed("Base Url is not configured"));
            }
            if (buildInformation.VcsRoot == null)
            {
                return(ResultFromExtension <WorkItemLink[]> .Failed("No VCS root configured"));
            }

            const string pathComponentIndicatingAzureDevOpsVcs = @"/_git/";

            if (buildInformation.VcsRoot.Contains(pathComponentIndicatingAzureDevOpsVcs))
            {
                systemLog.WarnFormat("The VCS Root '{0}' indicates this build information is Azure DevOps related so GitHub comment references will be ignored", buildInformation.VcsRoot);
                return(ResultFromExtension <WorkItemLink[]> .Success(new WorkItemLink[0]));
            }

            var releaseNotePrefix  = store.GetReleaseNotePrefix();
            var workItemReferences = commentParser.ParseWorkItemReferences(buildInformation);

            return(ResultFromExtension <WorkItemLink[]> .Success(workItemReferences.Select(wir => new WorkItemLink
            {
                Id = wir.IssueNumber,
                Description = GetReleaseNote(buildInformation.VcsRoot, wir.IssueNumber, wir.LinkData, releaseNotePrefix),
                LinkUrl = NormalizeLinkData(baseUrl, buildInformation.VcsRoot, wir.LinkData),
                Source = GitHubConfigurationStore.CommentParser
            })
                                                                 .Distinct()
                                                                 .ToArray()));
        }
 IResultFromExtension <WorkItemLink[]> FailureWithLog(params string[] errors)
 {
     systemLog.Warn(string.Join(Environment.NewLine, errors));
     return(ResultFromExtension <WorkItemLink[]> .Failed(errors));
 }