Exemple #1
0
        static async Task SpecificationApprovals(Launchpad.Project project)
        {
            Console.WriteLine("Specification approvals");
            Console.WriteLine("=======================");
            Console.WriteLine();

            foreach (var specification in await project.GetValidSpecifications())
            {
                var milestone = await specification.GetMilestone();

                if (specification.Direction != Direction.Approved)
                {
                    Console.WriteLine(
                        $"- [{specification.Name} ({milestone?.Name})]({specification.Json.web_link})\n" +
                        $"  - **Status:** {specification.Lifecycle}, {specification.Priority}, {specification.Direction}, {specification.Definition}, {specification.Implementation}"
                        );
                    Console.WriteLine();
                }
            }
        }
Exemple #2
0
        static async Task BugTriage(Launchpad.Project project, IConfigurationSection config, List <Commit> commits)
        {
            Console.WriteLine("Bug triage");
            Console.WriteLine("==========");
            Console.WriteLine();

            var bugsConfig             = config.GetSection("bugs");
            var scanAttachments        = GetConfigPatternMatchers(bugsConfig.GetSection("scanAttachments"));
            var commitReferencesConfig = config.GetSection("commits").GetSection("bugReferences");
            var commitReferencesSource = GetConfigPatternValueMatchers(commitReferencesConfig.GetSection("source"));
            var duplicateMinWords      = int.Parse(bugsConfig["duplicateMinWords"] ?? "1");

            var bugDuplicates = new Dictionary <string, (string Title, string Link)>();

            foreach (var bugTask in await project.GetRecentBugTasks(DateTimeOffset.Parse(bugsConfig["startDate"])))
            {
                var bug = await bugTask.GetBug();

                var milestone = await bugTask.GetMilestone();

                var attachments = await bug.GetAttachments();

                var attachmentLogs = await Task.WhenAll(attachments
                                                        .Where(attachment => attachment.Type != Launchpad.Type.Patch &&
                                                               scanAttachments.Any(pattern => pattern(attachment.Name)))
                                                        .Select(async attachment => await attachment.GetData()));

                var issues = new List <string>();

                var idealTitles = new List <string>();
                if (bugsConfig["scanDescriptions"] == "True")
                {
                    foreach (var message in await bug.GetMessages())
                    {
                        var idealTitle = GetBugIdealTitle(bugsConfig.GetSection("idealTitle"), message.Description);
                        if (idealTitle.Length > 0)
                        {
                            idealTitles.Add(idealTitle);
                        }
                    }
                }
                foreach (var attachmentLog in attachmentLogs)
                {
                    var idealTitle = GetBugIdealTitle(bugsConfig.GetSection("idealTitle"), attachmentLog);
                    if (idealTitle.Length > 0)
                    {
                        idealTitles.Add(idealTitle);
                    }
                }
                var idealTagsTitle = bug.Name;
                if (idealTitles.Count >= 1)
                {
                    if (!bug.Name.Contains(idealTitles[0]))
                    {
                        issues.Add($"Ideal title: {idealTitles[0]}");
                        idealTagsTitle = idealTitles[0];
                    }
                }

                var allIdealTags = new SortedSet <string>();
                foreach (var idealTagConfig in bugsConfig.GetSection("idealTags").GetChildren())
                {
                    if (Regex.IsMatch(idealTagsTitle, idealTagConfig.Key))
                    {
                        var knownTags = new SortedSet <string>();
                        foreach (var knownTag in idealTagConfig["knownTags"].Split(' '))
                        {
                            knownTags.Add(knownTag);
                        }

                        var idealTags = GetBugIdealTags(idealTagConfig, idealTagsTitle);
                        allIdealTags.UnionWith(idealTags);

                        foreach (var tag in idealTags.Where(tag => !bug.Tags.Contains(tag)))
                        {
                            issues.Add($"Missing known tag {tag}");
                        }
                        foreach (var tag in knownTags.Where(tag => bug.Tags.Contains(tag) && !idealTags.Contains(tag)))
                        {
                            issues.Add($"Extra known tag {tag}");
                        }
                    }
                }

                var duplicates          = new List <(double Match, string Link)>();
                var duplicateTitleWords = idealTagsTitle.Split(" ");
                for (var i = duplicateTitleWords.Length; i >= duplicateMinWords; i--)
                {
                    var duplicateTitle = String.Join(" ", duplicateTitleWords.Take(i));
                    if (bugDuplicates.ContainsKey(duplicateTitle))
                    {
                        if (!duplicates.Any(d => d.Link == bugDuplicates[duplicateTitle].Link))
                        {
                            duplicates.Add((
                                               50d * duplicateTitle.Length / idealTagsTitle.Length
                                               + 50d * duplicateTitle.Length / bugDuplicates[duplicateTitle].Title.Length,
                                               bugDuplicates[duplicateTitle].Link
                                               ));
                        }
                    }
                    else
                    {
                        bugDuplicates[duplicateTitle] = (bug.Name, GetBugLink(bugTask, bug));
                    }
                }
                foreach (var duplicate in duplicates.OrderBy(d => - d.Match))
                {
                    issues.Add($"Possible duplicate {duplicate.Match:F0}% - {duplicate.Link}");
                }

                foreach (var idealStatusConfig in bugsConfig.GetSection("idealStatus").GetChildren())
                {
                    var statusMatch = IsValuePresentMissing(idealStatusConfig.GetSection("status"), bugTask.Status.ToString());
                    var tagsMatch   = IsValuePresentMissing(idealStatusConfig.GetSection("tags"), allIdealTags.ToArray());
                    if (statusMatch && tagsMatch && bugTask.Status.ToString() != idealStatusConfig.Key)
                    {
                        issues.Add($"Status should be {idealStatusConfig.Key}");
                    }
                }

                var commitMentions = commits.Where(commit => commit.Message.Contains(bugTask.Json.web_link));
                foreach (var message in await bug.GetMessages())
                {
                    foreach (var referenceSource in commitReferencesSource)
                    {
                        var match = referenceSource(message.Description);
                        if (match != "")
                        {
                            var target = commitReferencesConfig["target"].Replace("%1", match);
                            commitMentions = commitMentions.Union(commits.Where(commit => commit.Message.Contains(target)));
                        }
                    }
                }
                if (commitMentions.Any())
                {
                    if (bugTask.Status < Status.InProgress)
                    {
                        issues.Add("Code was committed but bug is not in progress or fixed");
                    }
                    var latestCommit = commitMentions.OrderBy(commit => commit.AuthorDate).Last();
                    if ((DateTimeOffset.Now - latestCommit.AuthorDate).TotalDays > 28 &&
                        bugTask.Status < Status.FixCommitted)
                    {
                        issues.Add("Code was committed exclusively more than 28 days ago but bug is not fixed");
                    }
                }
                else
                {
                    if (bugTask.Status >= Status.FixCommitted)
                    {
                        issues.Add("No code was committed but bug is fixed");
                    }
                }

                WriteBugIssues(bugTask, bug, milestone, issues);
            }

            foreach (var bugTask in await project.GetUnreleasedBugTasks())
            {
                var bug = await bugTask.GetBug();

                var milestone = await bugTask.GetMilestone();

                var issues = new List <string>();

                if (bugTask.Status == Status.InProgress && !bugTask.HasAssignee)
                {
                    issues.Add("No assignee set but bug is in progress");
                }
                else if (bugTask.Status >= Status.FixCommitted && !bugTask.HasAssignee)
                {
                    issues.Add("No assignee set but bug is fixed");
                }

                if (bugTask.Status >= Status.FixCommitted && milestone == null)
                {
                    issues.Add("No milestone set but bug is fixed");
                }

                WriteBugIssues(bugTask, bug, milestone, issues);
            }

            foreach (var bugTask in await project.GetIncompleteBugTasks())
            {
                var bug = await bugTask.GetBug();

                var milestone = await bugTask.GetMilestone();

                var messages = await bug.GetMessages();

                var issues = new List <string>();

                var incompleteMessages = messages.Where(m => m.Created >= bugTask.Incomplete).ToList();
                if (incompleteMessages.Count > 0)
                {
                    var lastMessage = incompleteMessages.Last();
                    var diff        = lastMessage.Created - bugTask.Incomplete;
                    if (diff.TotalMinutes >= 1)
                    {
                        var lastMessageUser = await lastMessage.GetOwner();

                        var lastMessageAge = DateTimeOffset.Now - lastMessage.Created;
                        issues.Add($"{incompleteMessages.Count} messages added since incomplete status was set; last message was by {lastMessageUser.Name}, {lastMessageAge.TotalDays:N1} days ago");
                    }
                }

                WriteBugIssues(bugTask, bug, milestone, issues);
            }
        }
Exemple #3
0
        static async Task SpecificationTriage(Launchpad.Project project, IConfigurationSection config, List <Commit> commits)
        {
            Console.WriteLine("Specification triage");
            Console.WriteLine("====================");
            Console.WriteLine();

            var commitsConfig          = config.GetSection("commits");
            var commitReferencesConfig = commitsConfig.GetSection("specificationReferences");
            var commitReferencesSource = commitReferencesConfig.GetSection("source").GetChildren();

            foreach (var specification in await project.GetSpecifications())
            {
                var milestone = await specification.GetMilestone();

                var issues = new List <string>();
                if (specification.Direction == Direction.Approved &&
                    specification.Priority <= Priority.Undefined)
                {
                    issues.Add("Direction is approved but priority is missing");
                }
                if (specification.Definition == Definition.Approved &&
                    specification.Direction != Direction.Approved)
                {
                    issues.Add("Definition is approved but direction is not approved");
                }
                foreach (var link in config.GetSection("links").GetChildren())
                {
                    var hasStartDate   = DateTimeOffset.TryParse(link["startDate"] ?? "", out var startDate);
                    var startMilestone = link["startMilestone"];
                    var forms          = link.GetSection("expectedForms").GetChildren();
                    if ((!hasStartDate || specification.Created >= startDate) &&
                        (milestone == null || startMilestone == null || string.Compare(milestone.Id, startMilestone) >= 0) &&
                        specification.Definition == Definition.Approved &&
                        specification.Implementation != Implementation.Informational)
                    {
                        if (!specification.Summary.Contains(link["baseUrl"]))
                        {
                            issues.Add($"Definition is approved but no {link.Key} link is found");
                        }
                        else if (!forms.Any(form => specification.Summary.Contains(form.Value)))
                        {
                            issues.Add($"Definition is approved but no normal {link.Key} link is found");
                        }
                    }
                }
                if (specification.Definition == Definition.Approved &&
                    !specification.HasApprover)
                {
                    issues.Add("Definition is approved but approver is missing");
                }
                if (specification.Definition <= Definition.Drafting &&
                    !specification.HasDrafter)
                {
                    issues.Add("Definition is drafting (or later) but drafter is missing");
                }
                if (specification.Implementation >= Implementation.Started &&
                    specification.Definition != Definition.Approved &&
                    specification.Definition <= Definition.New)
                {
                    issues.Add("Implementation is started (or later) but definition is not approved");
                }
                if (specification.Implementation >= Implementation.Started &&
                    !specification.HasAssignee)
                {
                    issues.Add("Implementation is started (or later) but assignee is missing");
                }
                if (specification.Implementation == Implementation.Implemented &&
                    !specification.HasMilestone)
                {
                    issues.Add("Implementation is completed but milestone is missing");
                }
                var commitMentions = commits.Where(commit => commit.Message.Contains(specification.Json.web_link));
                if (specification.Whiteboard != null)
                {
                    foreach (var referenceSource in commitReferencesSource)
                    {
                        var match = Regex.Match(specification.Whiteboard, referenceSource.Value, RegexOptions.IgnoreCase);
                        while (match.Success)
                        {
                            var target = commitReferencesConfig["target"].Replace("%1", match.Groups[1].Value);
                            commitMentions = commitMentions.Union(commits.Where(commit => commit.Message.Contains(target)));
                            match          = match.NextMatch();
                        }
                    }
                }
                if (commitMentions.Any())
                {
                    if (milestone != null &&
                        milestone.Id != commitsConfig["currentMilestone"])
                    {
                        issues.Add($"Code was committed but milestone is {milestone.Id} (expected missing/{commitsConfig["currentMilestone"]})");
                    }
                    if (specification.Definition != Definition.Approved &&
                        specification.Definition <= Definition.New)
                    {
                        issues.Add("Code was committed but definition is not approved");
                    }
                    var latestCommit = commitMentions.OrderBy(commit => commit.AuthorDate).Last();
                    if ((DateTimeOffset.Now - latestCommit.AuthorDate).TotalDays > 28 &&
                        specification.Implementation != Implementation.Implemented)
                    {
                        issues.Add("Code was committed exclusively more than 28 days ago but implementation is not complete");
                    }
                }
                else
                {
                    if (specification.Implementation == Implementation.Implemented &&
                        milestone != null &&
                        milestone.Id == commitsConfig["currentMilestone"])
                    {
                        issues.Add("No code was committed but implementation for current milestone is complete");
                    }
                }
                if (issues.Count > 0)
                {
                    Console.WriteLine(
                        $"- [{specification.Name} ({milestone?.Name})]({specification.Json.web_link})\n" +
                        $"  - **Status:** {specification.Lifecycle}, {specification.Priority}, {specification.Direction}, {specification.Definition}, {specification.Implementation}\n" +
                        String.Join("\n", issues.Select(issue => $"  - **Issue:** {issue}"))
                        );
                    Console.WriteLine();
                }
            }
        }