private float GetFormulaPercentile(QuantifierResult quantifierResult)
        {
            var contextFormulaPercentile =
                Context.FormulaPercentile.First(f => f.Item1 == quantifierResult.Formula).Item2;

            return(GetPercentile(quantifierResult.FormulaLinesChanged, contextFormulaPercentile));
        }
        private async Task UpdateCommentOnPullRequest(
            PullRequestEventPayload payload,
            IGitHubClientAdapter gitHubClientAdapter,
            QuantifierResult quantifierClientResult,
            string quantifierContextLink)
        {
            // delete existing comments created by us
            var existingComments =
                await gitHubClientAdapter.GetIssueCommentsAsync(payload.Repository.Id, payload.PullRequest.Number);

            var existingCommentsCreatedByUs = existingComments.Where(
                ec =>
                ec.User.Login.Equals($"{gitHubClientAdapter.GitHubAppSettings.Name}[bot]"));

            foreach (var existingComment in existingCommentsCreatedByUs)
            {
                await gitHubClientAdapter.DeleteIssueCommentAsync(payload.Repository.Id, existingComment.Id);
            }

            // create a new comment on the issue
            var comment = await quantifierClientResult.ToMarkdownCommentAsync(
                payload.Repository.HtmlUrl,
                quantifierContextLink,
                payload.PullRequest.HtmlUrl,
                payload.PullRequest.User.Login,
                ShouldPostAnonymousFeedbackLink(payload),
                new MarkdownCommentOptions { CollapsePullRequestQuantifiedSection = true });

            await gitHubClientAdapter.CreateIssueCommentAsync(
                payload.Repository.Id,
                payload.PullRequest.Number,
                comment);
        }
 private void CountTotalChanges(
     QuantifierInput quantifierInput,
     QuantifierResult quantifierResult)
 {
     quantifierResult.QuantifiedLinesAdded   = quantifierInput.Changes.Sum(c => c.QuantifiedLinesAdded);
     quantifierResult.QuantifiedLinesDeleted = quantifierInput.Changes.Sum(c => c.QuantifiedLinesDeleted);
 }
 private float GetAdditionDeletionPercentile(
     QuantifierResult quantifierResult,
     bool addition)
 {
     return(addition
         ? GetPercentile(quantifierResult.QuantifiedLinesAdded, Context.AdditionPercentile)
         : GetPercentile(quantifierResult.QuantifiedLinesDeleted, Context.DeletionPercentile));
 }
        private async Task ApplyLabelToPullRequest(
            PullRequestEventPayload payload,
            IGitHubClientAdapter gitHubClientAdapter,
            QuantifierResult quantifierClientResult,
            IEnumerable <string> labelOptionsFromContext)
        {
            // create a new label in the repository if doesn't exist
            try
            {
                var existingLabel = await gitHubClientAdapter.GetLabelAsync(
                    payload.Repository.Id,
                    quantifierClientResult.Label);
            }
            catch (NotFoundException)
            {
                // create new label
                var color = Color.FromName(quantifierClientResult.Color);
                await gitHubClientAdapter.CreateLabelAsync(
                    payload.Repository.Id,
                    new NewLabel(quantifierClientResult.Label, ConvertToHex(color)));
            }

            // remove any previous labels applied if it's different
            // labels do not have the property of who applied them
            // so we use string matching against the label options present in the context
            // if label strings have changed in context since last PR update, this will break
            var existingLabels =
                await gitHubClientAdapter.GetIssueLabelsAsync(payload.Repository.Id, payload.PullRequest.Number);

            var existingLabelsByUs = existingLabels.Select(el => el.Name).Intersect(labelOptionsFromContext).ToList();

            foreach (var existingLabel in existingLabelsByUs)
            {
                if (existingLabel == quantifierClientResult.Label)
                {
                    continue;
                }

                await gitHubClientAdapter.RemoveLabelFromIssueAsync(
                    payload.Repository.Id,
                    payload.PullRequest.Number,
                    existingLabel);
            }

            // apply new label to pull request
            await gitHubClientAdapter.ApplyLabelAsync(
                payload.Repository.Id,
                payload.PullRequest.Number,
                new[] { quantifierClientResult.Label });
        }
 private int GetChangeNumber(
     QuantifierResult quantifierResult,
     ThresholdFormula formula)
 {
     return(formula switch
     {
         ThresholdFormula.Sum => quantifierResult.QuantifiedLinesAdded + quantifierResult.QuantifiedLinesDeleted,
         ThresholdFormula.Avg => (quantifierResult.QuantifiedLinesAdded +
                                  quantifierResult.QuantifiedLinesDeleted) / 2,
         ThresholdFormula.Min => Math.Min(
             quantifierResult.QuantifiedLinesAdded,
             quantifierResult.QuantifiedLinesDeleted),
         ThresholdFormula.Max => Math.Max(
             quantifierResult.QuantifiedLinesAdded,
             quantifierResult.QuantifiedLinesDeleted),
         _ => 0
     });
        private void SetLabel(QuantifierResult quantifierResult)
        {
            if (quantifierResult.QuantifiedLinesDeleted == 0 && quantifierResult.QuantifiedLinesAdded == 0)
            {
                quantifierResult.Label = "No Changes";
                quantifierResult.Color = nameof(Color.Green);
                return;
            }

            // for now to set the label use the absolute values addition/deletion and compare them with the thresholds
            // the percentile will be displayed saying that if your change has this number
            // of lines additions then you are at this percentile within this context
            foreach (var contextThreshold in Context.Thresholds.OrderBy(t => t.Value))
            {
                // we set the label from the thresholds and exit when we have first value threshold grater then percentile
                quantifierResult.Label               = contextThreshold.Label;
                quantifierResult.Color               = contextThreshold.Color;
                quantifierResult.Formula             = contextThreshold.Formula;
                quantifierResult.FormulaLinesChanged = GetChangeNumber(quantifierResult, contextThreshold.Formula);

                if (quantifierResult.FormulaLinesChanged <= contextThreshold.Value)
                {
                    break;
                }
            }

            // in case no addition/deletion found then we won't be able to set the change percentile.
            if (Context.AdditionPercentile == null ||
                Context.DeletionPercentile == null ||
                Context.FormulaPercentile == null ||
                Context.AdditionPercentile.Count == 0 ||
                Context.DeletionPercentile.Count == 0 ||
                !Context.FormulaPercentile.Any())
            {
                return;
            }

            quantifierResult.PercentileAddition = MathF.Round(GetAdditionDeletionPercentile(quantifierResult, true), 2);
            quantifierResult.PercentileDeletion = MathF.Round(
                GetAdditionDeletionPercentile(quantifierResult, false),
                2);
            quantifierResult.FormulaPercentile = MathF.Round(GetFormulaPercentile(quantifierResult), 2);
        }
        private float GetFormulaPercentile(QuantifierResult quantifierResult)
        {
            var finalValue = quantifierResult.Formula switch
            {
                ThresholdFormula.Sum => quantifierResult.QuantifiedLinesAdded + quantifierResult.QuantifiedLinesDeleted,
                ThresholdFormula.Avg => (quantifierResult.QuantifiedLinesAdded +
                                         quantifierResult.QuantifiedLinesDeleted) / 2,
                ThresholdFormula.Min => Math.Min(
                    quantifierResult.QuantifiedLinesAdded,
                    quantifierResult.QuantifiedLinesDeleted),
                ThresholdFormula.Max => Math.Max(
                    quantifierResult.QuantifiedLinesAdded,
                    quantifierResult.QuantifiedLinesDeleted),
                _ => throw new ArgumentOutOfRangeException(),
            };

            var contextFormulaPercentile =
                Context.FormulaPercentile.First(f => f.Item1 == quantifierResult.Formula).Item2;

            return(GetPercentile(finalValue, contextFormulaPercentile));
        }
        private async Task <QuantifierResult> Compute(QuantifierInput quantifierInput)
        {
            QuantifierResult quantifierResult = null;

            await Task.Factory.StartNew(
                () =>
            {
                quantifierResult = new QuantifierResult
                {
                    QuantifierInput = quantifierInput
                };

                // involve context and compute
                Parallel.ForEach(quantifierInput.Changes, ApplyContext);

                CountTotalChanges(quantifierInput, quantifierResult);

                // compute the label using the context percentile information and the thresholds
                SetLabel(quantifierResult);
            });

            return(quantifierResult);
        }
Beispiel #10
0
        public static async Task <string> ToConsoleOutput(this QuantifierResult quantifierResult)
        {
            var stubble = new StubbleBuilder()
                          .Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); }).Build();

            using var stream = new StreamReader(
                      Assembly.GetExecutingAssembly().GetManifestResourceStream(
                          $"{typeof(QuantifierResultExtensions).Namespace}.ConsoleOutput.mustache") !);

            var consoleOutput = await stubble.RenderAsync(
                await stream.ReadToEndAsync(),
                new
            {
                quantifierResult.Label,
                quantifierResult.QuantifiedLinesAdded,
                quantifierResult.QuantifiedLinesDeleted,
                quantifierResult.PercentileAddition,
                quantifierResult.PercentileDeletion,
                quantifierResult.FormulaPercentile,
                Formula = quantifierResult.Formula.ToString(),
            });

            return(consoleOutput);
        }
Beispiel #11
0
        public static async Task <string> ToMarkdownCommentAsync(
            this QuantifierResult quantifierResult,
            string repositoryLink,
            string contextFileLink,
            string pullRequestLink,
            string authorName,
            MarkdownCommentOptions markdownCommentOptions = null)
        {
            markdownCommentOptions ??= new MarkdownCommentOptions();

            var stubble = new StubbleBuilder()
                          .Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); }).Build();

            using var stream = new StreamReader(
                      Assembly.GetExecutingAssembly().GetManifestResourceStream(
                          $"{typeof(QuantifierResultExtensions).Namespace}.QuantifierComment.mustache") !);

            var feedbackLinkThumbsUp = CreateFeedbackLink(
                "ThumbsUp",
                authorName,
                repositoryLink,
                pullRequestLink);
            var feedbackLinkNeutral = CreateFeedbackLink(
                "Neutral",
                authorName,
                repositoryLink,
                pullRequestLink);
            var feedbackLinkThumbsDown = CreateFeedbackLink(
                "ThumbsDown",
                authorName,
                repositoryLink,
                pullRequestLink);

            var comment = await stubble.RenderAsync(
                await stream.ReadToEndAsync(),
                new
            {
                quantifierResult.Color,
                quantifierResult.Label,
                quantifierResult.QuantifiedLinesAdded,
                quantifierResult.QuantifiedLinesDeleted,
                quantifierResult.PercentileAddition,
                quantifierResult.PercentileDeletion,
                quantifierResult.FormulaPercentile,
                Formula             = quantifierResult.Formula.ToString(),
                ContextFileLink     = contextFileLink,
                FeedbackLinkUp      = feedbackLinkThumbsUp,
                FeedbackLinkNeutral = feedbackLinkNeutral,
                FeedbackLinkDown    = feedbackLinkThumbsDown,
                TotalFilesChanged   = quantifierResult.QuantifierInput.Changes.Count,
                Details             = string.Join(
                    Environment.NewLine,
                    quantifierResult.QuantifierInput.Changes
                    .GroupBy(c => c.FileExtension)
                    .Select(
                        g =>
                        $"{g.Key} : +{g.Sum(v => v.QuantifiedLinesAdded)} -{g.Sum(v => v.QuantifiedLinesDeleted)}")),
                CollapseChangesSummarySection        = markdownCommentOptions.CollapseChangesSummarySection ? string.Empty : "open",
                CollapsePullRequestQuantifiedSection = markdownCommentOptions.CollapsePullRequestQuantifiedSection ? string.Empty : "open"
            });

            return(comment);
        }
        public static async Task <string> ToMarkdownCommentAsync(
            this QuantifierResult quantifierResult,
            string repositoryLink,
            string contextFileLink,
            string pullRequestLink,
            string authorName,
            bool anonymous = true,
            MarkdownCommentOptions markdownCommentOptions = null)
        {
            markdownCommentOptions ??= new MarkdownCommentOptions();

            var stubble = new StubbleBuilder()
                          .Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); }).Build();

            using var stream = new StreamReader(
                      Assembly.GetExecutingAssembly().GetManifestResourceStream(
                          $"{typeof(QuantifierResultExtensions).Namespace}.Mustache.QuantifierComment.mustache") !);

            var feedbackLinkThumbsUp = CreateFeedbackLink(
                "ThumbsUp",
                authorName,
                repositoryLink,
                pullRequestLink,
                anonymous);
            var feedbackLinkNeutral = CreateFeedbackLink(
                "Neutral",
                authorName,
                repositoryLink,
                pullRequestLink,
                anonymous);
            var feedbackLinkThumbsDown = CreateFeedbackLink(
                "ThumbsDown",
                authorName,
                repositoryLink,
                pullRequestLink,
                anonymous);

            var contextFormulaPercentile =
                quantifierResult.Context.FormulaPercentile.First(f => f.Item1 == quantifierResult.Formula).Item2;

            var(idealSizeLowerBound, idealSizeUpperBound) = GetIdealChangeCountRange(contextFormulaPercentile);
            var detailsByFileExt = quantifierResult.QuantifierInput.Changes
                                   .Where(c => !string.IsNullOrEmpty(c.FileExtension))
                                   .GroupBy(c => c.FileExtension)
                                   .Select(
                g =>
                $"{g.Key} : +{g.Sum(v => v.QuantifiedLinesAdded)} -{g.Sum(v => v.QuantifiedLinesDeleted)}")
                                   .Concat(
                quantifierResult.QuantifierInput.Changes
                .Where(c => string.IsNullOrEmpty(c.FileExtension))
                .Select(c => $"{c.FilePath} : +{c.QuantifiedLinesAdded} -{c.QuantifiedLinesDeleted}"));
            var comment = await stubble.RenderAsync(
                await stream.ReadToEndAsync(),
                new
            {
                // lower th color str because of a bug in shield service
                // https://img.shields.io/static/v1?label=Quantified&message=Extra%20Small&color=Red is not respecting the red color,
                // but https://img.shields.io/static/v1?label=Quantified&message=Extra%20Small&color=red is working
                Color = quantifierResult.Color.ToLower(),
                quantifierResult.Label,

                // encode the label in case contains space
                EncodedLabel = Uri.EscapeUriString(quantifierResult.Label),
                quantifierResult.QuantifiedLinesAdded,
                quantifierResult.QuantifiedLinesDeleted,
                quantifierResult.FormulaLinesChanged,
                quantifierResult.PercentileAddition,
                quantifierResult.PercentileDeletion,
                quantifierResult.FormulaPercentile,
                IdealSizeLowerBound = idealSizeLowerBound,
                IdealSizeUpperBound = idealSizeUpperBound,
                IsIdealSize         = quantifierResult.FormulaLinesChanged >= idealSizeLowerBound && quantifierResult.FormulaLinesChanged <= idealSizeUpperBound,
                Formula             = quantifierResult.Formula.ToString(),
                ContextFileLink     = !string.IsNullOrWhiteSpace(contextFileLink)
                        ? contextFileLink :
                                      "https://github.com/microsoft/PullRequestQuantifier/blob/main/docs/prquantifier-yaml.md",
                FeedbackLinkUp      = feedbackLinkThumbsUp,
                FeedbackLinkNeutral = feedbackLinkNeutral,
                FeedbackLinkDown    = feedbackLinkThumbsDown,
                TotalFilesChanged   = quantifierResult.QuantifierInput.Changes.Count,
                Details             = string.Join(Environment.NewLine, detailsByFileExt),
                CollapsePullRequestQuantifiedSection = markdownCommentOptions.CollapsePullRequestQuantifiedSection ? string.Empty : "open"
            });

            return(comment);
        }
Beispiel #13
0
        private static void PrintResult(
            QuantifierResult quantifierResult,
            ClientOutputType clientOutputType)
        {
            Console.ForegroundColor = QuantifyClientHelper.GetColor(quantifierResult.Color);

            switch (clientOutputType)
            {
            case ClientOutputType.None:
            {
                Console.WriteLine(quantifierResult.ToConsoleOutput().Result);
                break;
            }

            case ClientOutputType.Detailed:
            {
                Console.WriteLine(JsonSerializer.Serialize(
                                      quantifierResult,
                                      new JsonSerializerOptions {
                        WriteIndented = true
                    }));
                break;
            }

            case ClientOutputType.SummaryByFile:
            {
                Console.WriteLine(JsonSerializer.Serialize(
                                      new
                    {
                        quantifierResult.Label,
                        quantifierResult.QuantifiedLinesAdded,
                        quantifierResult.QuantifiedLinesDeleted,
                        Formula = quantifierResult.Formula.ToString(),
                        quantifierResult.PercentileAddition,
                        quantifierResult.PercentileDeletion,
                        Details = quantifierResult.QuantifierInput.Changes.Select(s =>
                                                                                  new
                        {
                            s.FilePath,
                            s.QuantifiedLinesAdded,
                            s.QuantifiedLinesDeleted,
                            s.DiscardFromCounting
                        })
                    },
                                      new JsonSerializerOptions {
                        WriteIndented = true
                    }));
                break;
            }

            case ClientOutputType.SummaryByExt:
            {
                Console.WriteLine(JsonSerializer.Serialize(
                                      new
                    {
                        quantifierResult.Label,
                        quantifierResult.QuantifiedLinesAdded,
                        quantifierResult.QuantifiedLinesDeleted,
                        Formula = quantifierResult.Formula.ToString(),
                        quantifierResult.PercentileAddition,
                        quantifierResult.PercentileDeletion,
                        Details = quantifierResult.QuantifierInput.Changes.GroupBy(c => c.FileExtension).Select(g =>
                                                                                                                new
                        {
                            FileExtension          = g.Key,
                            QuantifiedLinesAdded   = g.Sum(v => v.QuantifiedLinesAdded),
                            QuantifiedLinesDeleted = g.Sum(v => v.QuantifiedLinesDeleted)
                        })
                    },
                                      new JsonSerializerOptions {
                        WriteIndented = true
                    }));
                break;
            }

            default:
                throw new ArgumentOutOfRangeException(nameof(clientOutputType), clientOutputType, null);
            }

            Console.ResetColor();
        }