public async Task <ActionResult <CommentMoveResult> > MoveComment( string toOwnerName, string toRepoName, [FromBody] CommentMoveRequest commentMoveRequest) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); try { await gitHub.Issue.Comment.Create(toOwnerName, toRepoName, commentMoveRequest.IssueNumber, commentMoveRequest.Text); return(Ok( new CommentMoveResult { })); } catch (Exception ex) { return(BadRequest( new CommentMoveResult { Exception = ex, })); } }
public async Task <IActionResult> GetMoveData(string fromOwnerName, string fromRepoName, string fromIssueNumber) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); if (!int.TryParse(fromIssueNumber, out var fromIssueNumberInt)) { return(BadRequest( new IssueMoveData { ErrorMessage = $"Issue number is invalid: {fromIssueNumber}", })); } var fromIssue = await gitHub.Issue.Get(fromOwnerName, fromRepoName, fromIssueNumberInt); var comments = (await gitHub.Issue.Comment.GetAllForIssue(fromOwnerName, fromRepoName, fromIssueNumberInt)) .Select(issueComment => new CommentData { Author = issueComment.User.Login, Text = issueComment.Body, Date = issueComment.CreatedAt, }) .ToList(); try { return(Ok( new IssueMoveData { RepoOwner = fromOwnerName, RepoName = fromRepoName, State = GetIssueState(fromIssue.State.Value), HtmlUrl = fromIssue.HtmlUrl, IsPullRequest = fromIssue.PullRequest != null, Title = fromIssue.Title, Number = fromIssue.Number, Author = fromIssue.User.Login, Body = fromIssue.Body, Assignees = fromIssue.Assignees.Select(a => a.Login).ToArray(), CreatedDate = fromIssue.CreatedAt, Milestone = fromIssue.Milestone?.Title, Labels = fromIssue.Labels.Select(l => new LabelData { Text = l.Name, Color = l.Color, }).ToList(), Comments = comments, })); } catch (Exception ex) { return(BadRequest( new IssueMoveData { Exception = ex, })); } }
public async Task <ActionResult <IssueCloseResult> > CloseIssue( string fromOwnerName, string fromRepoName, [FromBody] IssueCloseRequest issueCloseRequest) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); try { await gitHub.Issue.Update(fromOwnerName, fromRepoName, issueCloseRequest.IssueNumber, new IssueUpdate { State = ItemState.Closed, }); return(Ok( new IssueCloseResult { })); } catch (Exception ex) { return(BadRequest( new IssueCloseResult { Exception = ex, })); } }
public async Task <IActionResult> GetRepoData(string toOwnerName, string toRepoName) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var repo = await gitHub.Repository.Get(toOwnerName, toRepoName); try { return(Ok( new RepoMoveData { Owner = repo.Owner?.Login, Repo = repo.Name, OpenIssueCount = repo.OpenIssuesCount, })); } catch (Exception ex) { return(BadRequest( new RepoMoveData { Exception = ex, })); } }
public async Task <IActionResult> ApplyLabel(string owner, string repo, int issueNumber, string prediction) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var issue = await gitHub.Issue.Get(owner, repo, issueNumber); var issueUpdate = new IssueUpdate { Milestone = issue.Milestone?.Number // Have to re-set milestone because otherwise it gets cleared out. See https://github.com/octokit/octokit.net/issues/1927 }; issueUpdate.AddLabel(prediction); // Add all existing labels to the update so that they don't get removed foreach (var label in issue.Labels) { issueUpdate.AddLabel(label.Name); } await gitHub.Issue.Update(owner, repo, issueNumber, issueUpdate); // Because GitHub search queries can show stale data, add a cache entry to // indicate this issue should be hidden for a while because it was just labeled. _memoryCache.Set( GetIssueHiderCacheKey(owner, repo, issueNumber), 0, // no data is needed; the existence of the cache key is what counts new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30), }); return(RedirectToPage("/MikLabel")); }
public async Task <ActionResult <IssueMoveResult> > MoveIssue( string toOwnerName, string toRepoName, [FromBody] IssueMoveRequest issueMoveRequest) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var destinationMilestones = await gitHub.Issue.Milestone.GetAllForRepository(toOwnerName, toRepoName); try { // Create new issue var newIssueDetails = new NewIssue(issueMoveRequest.Title) { Body = issueMoveRequest.Body, }; if (issueMoveRequest.Milestone != null) { // Set the milestone to the ID that matches the one in the destination repo, if it exists var destinationMilestone = destinationMilestones.SingleOrDefault(m => string.Equals(m.Title, issueMoveRequest.Milestone, StringComparison.OrdinalIgnoreCase)); newIssueDetails.Milestone = destinationMilestone?.Number; } if (issueMoveRequest.Assignees != null) { foreach (var assignee in issueMoveRequest.Assignees) { newIssueDetails.Assignees.Add(assignee); } } if (issueMoveRequest.Labels != null) { foreach (var label in issueMoveRequest.Labels) { newIssueDetails.Labels.Add(label); } } var newIssueCreated = await gitHub.Issue.Create(toOwnerName, toRepoName, newIssueDetails); return(Ok( new IssueMoveResult { IssueNumber = newIssueCreated.Number, HtmlUrl = newIssueCreated.HtmlUrl, })); } catch (Exception ex) { return(BadRequest( new IssueMoveResult { Exception = ex, })); } }
public async Task <IActionResult> ApplyLabel(string owner, string repo, int issueNumber, string prediction, string repoSetName) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); await ApplyLabel(gitHub, owner, repo, issueNumber, prediction); return(RedirectToPage("/MikLabel", routeValues: new { repoSetName = repoSetName })); }
public async Task <IActionResult> MissingRepos() { var gitHubName = HttpContext.User.Identity.Name; var gitHubAccessToken = await HttpContext.Authentication.GetTokenAsync("access_token"); var gitHubClient = GitHubUtils.GetGitHubClient(gitHubAccessToken); var repoDataSet = await RepoSetProvider.GetRepoDataSet(); var repoSetLists = repoDataSet.GetRepoSetLists(); var distinctOrgs = repoSetLists .SelectMany( repoSet => repoSet.Value.Repos.Select(repoDefinition => repoDefinition.Owner)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(org => org).ToList(); var allOrgRepos = new ConcurrentDictionary <string, string[]>(StringComparer.OrdinalIgnoreCase); var result = AsyncParallelUtils.ForEachAsync(distinctOrgs, 5, async org => { var reposInOrg = await gitHubClient.Repository.GetAllForOrg(org); allOrgRepos[org] = reposInOrg.Where(repo => !repo.Fork).Select(repo => repo.Name).ToArray(); }); await result; var missingOrgRepos = allOrgRepos.Select(org => new MissingRepoSet { Org = org.Key, MissingRepos = org.Value .Except( repoSetLists .SelectMany(repoSetList => repoSetList.Value.Repos) .Select(repoDefinition => repoDefinition.Name), StringComparer.OrdinalIgnoreCase) .OrderBy(repo => repo, StringComparer.OrdinalIgnoreCase) .ToList(), }) .OrderBy(missingRepoSet => missingRepoSet.Org, StringComparer.OrdinalIgnoreCase) .ToList(); return(View(new MissingReposViewModel { GitHubUserName = gitHubName, RepoSetNames = repoDataSet.GetRepoSetLists().Select(repoSetList => repoSetList.Key).ToArray(), MissingRepos = missingOrgRepos, })); }
public async Task <IActionResult> GetIssuesToDispatch(string ownerName, string repoName) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); // Issue the three queries simultaneously and wait for results var allRepoIssuesTask = gitHub.Issue.GetAllForRepository(ownerName, repoName); var allLabelsTask = gitHub.Issue.Labels.GetAllForRepository(ownerName, repoName); await Task.WhenAll(allRepoIssuesTask, allLabelsTask); var allRepoIssues = await allRepoIssuesTask; var allLabels = await allLabelsTask; var sortedRepoLabelNames = allLabels .Where(label => label.Name.StartsWith("repo:", StringComparison.OrdinalIgnoreCase)) .Select(label => label.Name) .OrderBy(labelName => labelName, StringComparer.OrdinalIgnoreCase) .ToList(); // TODO: Ignore Backlog/Discussion(s) milestones? // TODO: Project the issues to a simpler item that also includes action links for dispatching, etc. var repoRef = new RepositoryReference { Owner = new UserReference { Login = ownerName, }, Name = repoName, }; var allIssuesWithoutRepoLabels = allRepoIssues .Where(issue => issue.Labels.All(label => !sortedRepoLabelNames.Contains(label.Name, StringComparer.OrdinalIgnoreCase)) && !IsExcludedMilestone(issue.Milestone?.Title)) .Select(issue => GetIssueDataFromIssue(issue, repoRef)) .ToList(); return(Json(new { issuesWithoutRepoLabels = allIssuesWithoutRepoLabels, repoLabels = sortedRepoLabelNames, })); }
public async Task <IActionResult> ApplyLabels([FromForm] List <string> applyDefault, string repoSetName) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var tasks = new Task[applyDefault.Count]; for (var i = 0; i < applyDefault.Count; i++) { var(owner, repo, number, prediction) = ParsePrediction(applyDefault[i]); tasks[i] = ApplyLabel(gitHub, owner, repo, number, prediction); } await Task.WhenAll(tasks); return(RedirectToPage("/MikLabel", routeValues: new { repoSetName = repoSetName })); }
public async Task <ActionResult <LabelCreateResult> > CreateLabels( string toOwnerName, string toRepoName, [FromBody] LabelCreateRequest labelCreateRequest) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var destinationLabels = await gitHub.Issue.Labels.GetAllForRepository(toOwnerName, toRepoName); var listOfLabelsToCreate = labelCreateRequest.Labels .Where(labelNeeded => !destinationLabels .Any(destinationLabel => string.Equals( labelNeeded.Text, destinationLabel.Name, StringComparison.OrdinalIgnoreCase))) .ToList(); try { foreach (var labelToCreate in listOfLabelsToCreate) { await gitHub.Issue.Labels.Create(toOwnerName, toRepoName, new NewLabel(labelToCreate.Text, labelToCreate.Color)); } return(Ok( new LabelCreateResult { LabelsCreated = listOfLabelsToCreate, })); } catch (Exception ex) { return(BadRequest( new LabelCreateResult { Exception = ex, })); } }
public async Task <IActionResult> DispatchIssueTo(string ownerName, string repoName, int issueNumber, string destinationLabel) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var issueUpdate = new IssueUpdate(); issueUpdate.AddLabel(destinationLabel); try { await gitHub.Issue.Update(ownerName, repoName, issueNumber, issueUpdate); return(Ok()); } catch (Exception ex) { return(BadRequest(ex.Message)); } }
public async Task <ActionResult <LabelCreateResult> > CreateMilestone( string toOwnerName, string toRepoName, [FromBody] MilestoneCreateRequest milestoneCreateRequest) { var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); var destinationMilestones = await gitHub.Issue.Milestone.GetAllForRepository(toOwnerName, toRepoName); if (destinationMilestones.Any(m => string.Equals(m.Title, milestoneCreateRequest.Milestone, StringComparison.OrdinalIgnoreCase))) { // Milestone already exists, so do nothing return(Ok(new MilestoneCreateResult { MilestoneCreated = null, })); } try { await gitHub.Issue.Milestone.Create(toOwnerName, toRepoName, new NewMilestone(milestoneCreateRequest.Milestone)); return(Ok( new MilestoneCreateResult { MilestoneCreated = milestoneCreateRequest.Milestone, })); } catch (Exception ex) { return(BadRequest( new MilestoneCreateResult { Exception = ex, })); } }
public IActionResult Index(string repoSet, string gitHubAccessToken, string gitHubName) { // Authenticated and all claims have been read if (!RepoSetProvider.RepoSetExists(repoSet)) { return(HttpNotFound()); } var requestStopwatch = new Stopwatch(); requestStopwatch.Start(); var repos = RepoSetProvider.GetRepoSet(repoSet); var distinctRepos = repos.Repos .Distinct() .Where(repo => repo.RepoInclusionLevel != RepoInclusionLevel.None) .ToArray(); var personSetName = repos.AssociatedPersonSetName; var personSet = PersonSetProvider.GetPersonSet(personSetName); var peopleInPersonSet = personSet?.People ?? new string[0]; var workingLabels = repos.WorkingLabels ?? new string[0]; var allIssuesByRepo = new ConcurrentDictionary <RepoDefinition, RepoTask <IReadOnlyList <Issue> > >(); var allPullRequestsByRepo = new ConcurrentDictionary <RepoDefinition, RepoTask <IReadOnlyList <PullRequest> > >(); var gitHubClient = GitHubUtils.GetGitHubClient(gitHubAccessToken); Parallel.ForEach(distinctRepos, repo => allIssuesByRepo[repo] = GetIssuesForRepo(repo, gitHubClient)); Parallel.ForEach(distinctRepos, repo => allPullRequestsByRepo[repo] = GetPullRequestsForRepo(repo, gitHubClient)); // while waiting for queries to run, do some other work... var labelQuery = GetLabelQuery(repos.LabelFilter); var openIssuesQuery = GetOpenIssuesQuery(GetExcludedMilestonesQuery(), labelQuery, distinctRepos); var workingIssuesQuery = GetWorkingIssuesQuery(labelQuery, workingLabels, distinctRepos); var unassignedIssuesQuery = GetUnassignedIssuesQuery(GetExcludedMilestonesQuery(), labelQuery, distinctRepos); var untriagedIssuesQuery = GetUntriagedIssuesQuery(labelQuery, distinctRepos); var openPRsQuery = GetOpenPRsQuery(distinctRepos); var stalePRsQuery = GetStalePRsQuery(distinctRepos); // now wait for queries to finish executing try { Task.WaitAll(allIssuesByRepo.Select(x => x.Value.Task).ToArray()); } catch (AggregateException) { // Just hide the exceptions here - faulted tasks will be aggregated later } try { Task.WaitAll(allPullRequestsByRepo.Select(x => x.Value.Task).ToArray()); } catch (AggregateException) { // Just hide the exceptions here - faulted tasks will be aggregated later } var repoFailures = new List <RepoFailure>(); repoFailures.AddRange( allIssuesByRepo .Where(repoTask => repoTask.Value.Task.IsFaulted || repoTask.Value.Task.IsCanceled) .Select(repoTask => new RepoFailure { Repo = repoTask.Key, FailureMessage = string.Format("Issues couldn't be retrieved for the {0}/{1} repo", repoTask.Key.Owner, repoTask.Key.Name), })); repoFailures.AddRange( allPullRequestsByRepo .Where(repoTask => repoTask.Value.Task.IsFaulted || repoTask.Value.Task.IsCanceled) .Select(repoTask => new RepoFailure { Repo = repoTask.Key, FailureMessage = string.Format("Pull requests couldn't be retrieved for the {0}/{1} repo", repoTask.Key.Owner, repoTask.Key.Name), })); var allIssues = allIssuesByRepo .Where(repoTask => !repoTask.Value.Task.IsFaulted && !repoTask.Value.Task.IsCanceled) .SelectMany(issueList => issueList.Value.Task.Result .Where( issue => !IsExcludedMilestone(issue.Milestone?.Title) && issue.PullRequest == null && IsFilteredIssue(issue, repos) && ItemIncludedByInclusionLevel(issue.Assignee?.Login, issueList.Key, peopleInPersonSet)) .Select( issue => new IssueWithRepo { Issue = issue, Repo = issueList.Key, WorkingStartTime = GetWorkingStartTime(issueList.Key, issue, workingLabels, gitHubClient).Result, IsInAssociatedPersonSet = IsInAssociatedPersonSet(issue.Assignee?.Login, personSet), })) .OrderBy(issueWithRepo => issueWithRepo.WorkingStartTime) .ToList(); var workingIssues = allIssues .Where(issue => issue.Issue.Labels .Any(label => workingLabels.Contains(label.Name, StringComparer.OrdinalIgnoreCase))) .ToList(); var untriagedIssues = allIssues .Where(issue => issue.Issue.Milestone == null).ToList(); var unassignedIssues = allIssues .Where(issue => issue.Issue.Assignee == null).ToList(); var allPullRequests = allPullRequestsByRepo .Where(repoTask => !repoTask.Value.Task.IsFaulted && !repoTask.Value.Task.IsCanceled) .SelectMany(pullRequestList => pullRequestList.Value.Task.Result .Where( pullRequest => !IsExcludedMilestone(pullRequest.Milestone?.Title) && (ItemIncludedByInclusionLevel(pullRequest.Assignee?.Login, pullRequestList.Key, peopleInPersonSet) || ItemIncludedByInclusionLevel(pullRequest.User.Login, pullRequestList.Key, peopleInPersonSet))) .Select(pullRequest => new PullRequestWithRepo { PullRequest = pullRequest, Repo = pullRequestList.Key, IsInAssociatedPersonSet = IsInAssociatedPersonSet(pullRequest.User?.Login, personSet), })) .OrderBy(pullRequestWithRepo => pullRequestWithRepo.PullRequest.CreatedAt) .ToList(); var milestoneData = distinctRepos .OrderBy(repo => repo.Owner + "/" + repo.Name, StringComparer.OrdinalIgnoreCase) .Select(repo => new MilestoneSummary() { Repo = repo, MilestoneData = allIssues .Where(issue => issue.Repo == repo) .GroupBy(issue => issue.Issue.Milestone?.Title) .Select(issueMilestoneGroup => new MilestoneData { Milestone = issueMilestoneGroup.Key, OpenIssues = issueMilestoneGroup.Count(), }) .ToList(), }); var fullSortedMilestoneList = milestoneData .SelectMany(milestone => milestone.MilestoneData) .Select(milestone => milestone.Milestone) .Distinct() .OrderBy(milestone => new PossibleSemanticVersion(milestone)); var issueListViewModel = new IssueListViewModel { RepoFailures = repoFailures, GitHubUserName = gitHubName, LastUpdated = DateTimeOffset.Now.ToPacificTime().ToString(), ExtraLinks = repos.RepoExtraLinks, RepoSetName = repoSet, RepoSetNames = RepoSetProvider.GetRepoSetLists().Select(repoSetList => repoSetList.Key).ToArray(), TotalIssues = allIssues.Count, WorkingIssues = workingIssues.Count, UntriagedIssues = untriagedIssues.Count, UnassignedIssues = unassignedIssues.Count, OpenPullRequests = allPullRequests.Count, StalePullRequests = allPullRequests.Where(pr => pr.PullRequest.CreatedAt < DateTimeOffset.Now.AddDays(-14)).Count(), ReposIncluded = distinctRepos .OrderBy(repo => repo.Owner.ToLowerInvariant()) .ThenBy(repo => repo.Name.ToLowerInvariant()) .Select(repo => new RepoSummary { Repo = repo, OpenIssues = allIssues.Where(issue => issue.Repo == repo).Count(), OpenIssuesQueryUrl = GetOpenIssuesQuery(GetExcludedMilestonesQuery(), labelQuery, repo), UnassignedIssues = allIssues.Where(issue => issue.Repo == repo && issue.Issue.Assignee == null).Count(), UnassignedIssuesQueryUrl = GetUnassignedIssuesQuery(GetExcludedMilestonesQuery(), labelQuery, repo), UntriagedIssues = allIssues.Where(issue => issue.Repo == repo && issue.Issue.Milestone == null).Count(), UntriagedIssuesQueryUrl = GetUntriagedIssuesQuery(labelQuery, repo), WorkingIssues = allIssues.Where(issue => issue.Repo == repo && workingIssues.Contains(issue)).Count(), WorkingIssuesQueryUrl = GetWorkingIssuesQuery(labelQuery, workingLabels, repo), OpenPRs = allPullRequests.Where(pullRequest => pullRequest.Repo == repo).Count(), OpenPRsQueryUrl = GetOpenPRsQuery(repo), StalePRs = allPullRequests.Where(pullRequest => pullRequest.Repo == repo && pullRequest.PullRequest.CreatedAt < DateTimeOffset.Now.AddDays(-14)).Count(), StalePRsQueryUrl = GetStalePRsQuery(repo), }) .ToList(), MilestoneSummary = milestoneData.ToList(), MilestonesAvailable = fullSortedMilestoneList.ToList(), OpenIssuesQuery = openIssuesQuery, WorkingIssuesQuery = workingIssuesQuery, UntriagedIssuesQuery = untriagedIssuesQuery, UnassignedIssuesQuery = unassignedIssuesQuery, OpenPRsQuery = openPRsQuery, StalePRsQuery = stalePRsQuery, GroupByAssignee = new GroupByAssigneeViewModel { Assignees = peopleInPersonSet .OrderBy(person => person, StringComparer.OrdinalIgnoreCase) .Select(person => new GroupByAssigneeAssignee { Assignee = person, IsInAssociatedPersonSet = IsInAssociatedPersonSet(person, personSet), Issues = workingIssues .Where(workingIssue => workingIssue.Issue.Assignee?.Login == person) .ToList(), PullRequests = allPullRequests .Where( pr => pr.PullRequest.User.Login == person || pr.PullRequest.Assignee?.Login == person) .OrderBy(pr => pr.PullRequest.CreatedAt) .ToList(), OtherIssues = allIssues .Where(issue => issue.Issue.Assignee?.Login == person) .Except(workingIssues) .OrderBy(issueWithRepo => new PossibleSemanticVersion(issueWithRepo.Issue.Milestone?.Title)) .ThenBy(issueWithRepo => issueWithRepo.Repo.Name, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => issueWithRepo.Issue.Number) .ToList(), }) .Concat(new[] { new GroupByAssigneeAssignee { Assignee = "<other assignees>", IsMetaAssignee = true, IsInAssociatedPersonSet = false, Issues = workingIssues .Where(workingIssue => workingIssue.Issue.Assignee != null && !peopleInPersonSet.Contains(workingIssue.Issue.Assignee.Login, StringComparer.OrdinalIgnoreCase)) .ToList(), PullRequests = allPullRequests .Where( pr => pr.PullRequest.Assignee != null && !peopleInPersonSet.Contains(pr.PullRequest.User.Login, StringComparer.OrdinalIgnoreCase) && !peopleInPersonSet.Contains(pr.PullRequest.Assignee.Login, StringComparer.OrdinalIgnoreCase)) .OrderBy(pr => pr.PullRequest.CreatedAt) .ToList(), OtherIssues = allIssues .Where(issue => issue.Issue.Assignee != null && !peopleInPersonSet.Contains(issue.Issue.Assignee?.Login, StringComparer.OrdinalIgnoreCase)) .Except(workingIssues) .OrderBy(issueWithRepo => issueWithRepo.Issue.Assignee.Login, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => new PossibleSemanticVersion(issueWithRepo.Issue.Milestone?.Title)) .ThenBy(issueWithRepo => issueWithRepo.Repo.Name, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => issueWithRepo.Issue.Number) .ToList(), }, new GroupByAssigneeAssignee { Assignee = "<unassigned>", IsMetaAssignee = true, IsInAssociatedPersonSet = false, Issues = workingIssues .Where(workingIssue => workingIssue.Issue.Assignee == null) .ToList(), PullRequests = allPullRequests .Where( pr => pr.PullRequest.Assignee == null && !peopleInPersonSet.Contains(pr.PullRequest.User.Login, StringComparer.OrdinalIgnoreCase)) .OrderBy(pr => pr.PullRequest.CreatedAt) .ToList(), OtherIssues = allIssues .Where(issue => issue.Issue.Assignee == null) .Except(workingIssues) .OrderBy(issueWithRepo => new PossibleSemanticVersion(issueWithRepo.Issue.Milestone?.Title)) .ThenBy(issueWithRepo => issueWithRepo.Repo.Name, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => issueWithRepo.Issue.Number) .ToList(), }, }) .ToList() .AsReadOnly(), }, GroupByMilestone = new GroupByMilestoneViewModel { Milestones = workingIssues .Select(issue => issue.Issue.Milestone?.Title) .Concat(new string[] { null }) .Distinct() .OrderBy(milestone => new PossibleSemanticVersion(milestone)) .Select(milestone => new GroupByMilestoneMilestone { Milestone = milestone, Issues = workingIssues .Where(issue => issue.Issue.Milestone?.Title == milestone) .OrderBy(issue => issue.WorkingStartTime) .ToList(), PullRequests = allPullRequests .Where(pullRequest => pullRequest.PullRequest.Milestone?.Title == milestone) .OrderBy(pullRequest => pullRequest.PullRequest.CreatedAt) .ToList(), }) .OrderBy(group => new PossibleSemanticVersion(group.Milestone)) .ToList() }, GroupByRepo = new GroupByRepoViewModel { Repos = workingIssues .Select(issue => issue.Repo) .Concat(allPullRequests.Select(pullRequest => pullRequest.Repo)) .Distinct() .OrderBy(repo => repo) .Select(repo => new GroupByRepoRepo { Repo = repo, Issues = workingIssues .Where(issue => issue.Repo == repo) .OrderBy(issue => issue.WorkingStartTime) .ToList(), PullRequests = allPullRequests .Where(pullRequest => pullRequest.Repo == repo) .OrderBy(pullRequest => pullRequest.PullRequest.Assignee?.Login) .ThenBy(pullRequest => pullRequest.PullRequest.Number) .ToList(), }) .ToList() }, }; requestStopwatch.Stop(); issueListViewModel.PageRequestTime = requestStopwatch.Elapsed; return(View(issueListViewModel)); }
public async Task <IActionResult> GetIssuesByUserAsync(string repoSetName, string userName) { var repoSet = _dataSource.GetRepoDataSet().GetRepoSet(repoSetName); var accessToken = await HttpContext.GetTokenAsync("access_token"); var gitHub = GitHubUtils.GetGitHubClient(accessToken); // Issue the three queries simultaneously and wait for results var assignedIssuesQuery = repoSet.GenerateQuery("is:open", "is:issue", $"assignee:{userName}"); var assignedPrsQuery = repoSet.GenerateQuery("is:open", "is:pr", $"assignee:{userName}"); var createdPrsQuery = repoSet.GenerateQuery("is:open", "is:pr", $"author:{userName}"); var assignedIssuesTask = _github.SearchIssuesAsync(assignedIssuesQuery, accessToken); var assignedPrsTask = _github.SearchIssuesAsync(assignedPrsQuery, accessToken); var createdPrsTask = _github.SearchIssuesAsync(createdPrsQuery, accessToken); await Task.WhenAll(assignedIssuesTask, assignedPrsTask, createdPrsTask); var assignedIssues = await assignedIssuesTask; var assignedPrs = await assignedPrsTask; var createdPrs = await createdPrsTask; // Identify issues being worked on var workingIssues = new List <IssueData>(); var otherIssues = new List <IssueData>(); foreach (var result in assignedIssues.Search) { if (result.Labels.Any(l => repoSet.WorkingLabels.Contains(l.Name))) { // We need to grab additional data about Working issues result.Working = true; result.WorkingStartedAt = await GetWorkingStartTime(result, repoSet.WorkingLabels, gitHub); workingIssues.Add(result); } else { otherIssues.Add(result); } } // Update rate limit information var rateLimitCost = RateLimitInfo.Add(RateLimitInfo.Add(assignedIssues.RateLimit, assignedPrs.RateLimit), createdPrs.RateLimit); _logger.LogDebug("Fetched issues for {User} in repo group {Group}. Total Rate Limit Cost: {Cost}", userName, repoSetName, rateLimitCost.Cost); return(Json(new { working = SortWorkingIssues(workingIssues), other = SortOtherAssignedIssues(otherIssues), prs = SortPRs(Enumerable.Concat(assignedPrs.Search, createdPrs.Search)), graphQlRateLimit = rateLimitCost, restRateLimit = gitHub.GetLastApiInfo()?.RateLimit, pages = assignedIssues.Pages + assignedPrs.Pages + createdPrs.Pages, queries = new string[] { assignedIssuesQuery, assignedPrsQuery, createdPrsQuery } })); }
public async Task <IActionResult> Index(string repoSet) { using (_logger.BeginScope("Requesting Triage Data for {RepoSet}", repoSet)) { HttpContext.AddTelemetryProperty("RepoSet", repoSet); HttpContext.AddTelemetryProperty("RepoSetView", "Triage"); var metricsPrefix = $"TriageController:RepoSet({repoSet})"; var gitHubName = HttpContext.User.Identity.Name; HttpContext.AddTelemetryProperty("GitHubUser", gitHubName); var gitHubAccessToken = await HttpContext.GetTokenAsync("access_token"); // Authenticated and all claims have been read var repoDataSet = _dataSource.GetRepoDataSet(); if (!repoDataSet.RepoSetExists(repoSet)) { var invalidRepoSetPageViewTelemetry = new PageViewTelemetry("RepoSet") { Url = new Uri(Request.GetDisplayUrl()), }; HttpContext.AddTelemetryProperty("RepoSetValid", false); return(NotFound()); } var requestStopwatch = new Stopwatch(); requestStopwatch.Start(); var repos = repoDataSet.GetRepoSet(repoSet); var distinctRepos = repos.Repos .Distinct() .Where(repo => repo.RepoInclusionLevel != RepoInclusionLevel.NotInRepoSet && repo.RepoInclusionLevel != RepoInclusionLevel.Ignored) .ToArray(); var personSetName = repos.AssociatedPersonSetName; var personSet = _dataSource.GetPersonSet(personSetName); var peopleInPersonSet = personSet?.People ?? new string[0]; var workingLabels = repos.WorkingLabels ?? new HashSet <string>(); var allIssuesByRepo = new ConcurrentDictionary <RepoDefinition, RepoTask <IReadOnlyList <Issue> > >(); var allPullRequestsByRepo = new ConcurrentDictionary <RepoDefinition, RepoTask <IReadOnlyList <PullRequest> > >(); var gitHubClient = GitHubUtils.GetGitHubClient(gitHubAccessToken); // Get missing repos var distinctOrgs = distinctRepos .Select( repoDefinition => repoDefinition.Owner) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(org => org) .ToList(); var allOrgRepos = new ConcurrentDictionary <string, string[]>(StringComparer.OrdinalIgnoreCase); using (_metricsService.Time($"{metricsPrefix}:GetAllRepositories")) { var getAllOrgReposTask = AsyncParallelUtils.ForEachAsync(distinctOrgs, 5, async org => { IReadOnlyList <Repository> reposInOrg; using (_metricsService.Time($"{metricsPrefix}:Org({org}):GetRepositories")) { reposInOrg = await gitHubClient.Repository.GetAllForOrg(org); } allOrgRepos[org] = reposInOrg.Where(repo => !repo.Fork).Select(repo => repo.Name).ToArray(); }); await getAllOrgReposTask; } var missingOrgRepos = allOrgRepos.Select(org => new MissingRepoSet { Org = org.Key, MissingRepos = org.Value .Except( repos.Repos .Distinct() .Where(repo => repo.RepoInclusionLevel != RepoInclusionLevel.NotInRepoSet) .Select(repoDefinition => repoDefinition.Name), StringComparer.OrdinalIgnoreCase) .OrderBy(repo => repo, StringComparer.OrdinalIgnoreCase) .ToList(), }) .OrderBy(missingRepoSet => missingRepoSet.Org, StringComparer.OrdinalIgnoreCase) .ToList(); // Get bugs/PR data Parallel.ForEach(distinctRepos, repo => allIssuesByRepo[repo] = GetIssuesForRepo(repo, gitHubClient, metricsPrefix)); Parallel.ForEach(distinctRepos, repo => allPullRequestsByRepo[repo] = GetPullRequestsForRepo(repo, gitHubClient, metricsPrefix)); // while waiting for queries to run, do some other work... var distinctMainRepos = distinctRepos.Where(repo => repo.RepoInclusionLevel == RepoInclusionLevel.AllItems).ToArray(); var distinctExtraRepos = distinctRepos.Where(repo => repo.RepoInclusionLevel == RepoInclusionLevel.ItemsAssignedToPersonSet).ToArray(); var labelQuery = GetLabelQuery(repos.LabelFilter); var openIssuesQuery = GetOpenIssuesQuery(GetExcludedMilestonesQuery(), labelQuery, distinctMainRepos); var workingIssuesQuery = GetWorkingIssuesQuery(labelQuery, workingLabels, distinctMainRepos); var unassignedIssuesQuery = GetUnassignedIssuesQuery(GetExcludedMilestonesQuery(), labelQuery, distinctMainRepos); var untriagedIssuesQuery = GetUntriagedIssuesQuery(labelQuery, distinctMainRepos); var openPRsQuery = GetOpenPRsQuery(distinctMainRepos); var stalePRsQuery = GetStalePRsQuery(distinctMainRepos); // now wait for queries to finish executing var failuresOccurred = false; try { Task.WaitAll(allIssuesByRepo.Select(x => x.Value.Task).ToArray()); } catch (AggregateException) { // Just hide the exceptions here - faulted tasks will be aggregated later failuresOccurred = true; } try { Task.WaitAll(allPullRequestsByRepo.Select(x => x.Value.Task).ToArray()); } catch (AggregateException) { // Just hide the exceptions here - faulted tasks will be aggregated later failuresOccurred = true; } using (_metricsService.Time($"{metricsPrefix}:PostQueryProcessingTime")) { // Log failures var repoFailures = new List <RepoFailure>(); if (failuresOccurred) { repoFailures.AddRange( allIssuesByRepo .Where(repoTask => repoTask.Value.Task.IsFaulted || repoTask.Value.Task.IsCanceled) .Select(repoTask => new RepoFailure { Repo = repoTask.Key, IssueType = IssueType.Issue, FailureMessage = string.Format("Issues couldn't be retrieved for the {0}/{1} repo", repoTask.Key.Owner, repoTask.Key.Name), Exception = repoTask.Value.Task.Exception, })); repoFailures.AddRange( allPullRequestsByRepo .Where(repoTask => repoTask.Value.Task.IsFaulted || repoTask.Value.Task.IsCanceled) .Select(repoTask => new RepoFailure { Repo = repoTask.Key, IssueType = IssueType.PullRequest, FailureMessage = string.Format("Pull requests couldn't be retrieved for the {0}/{1} repo", repoTask.Key.Owner, repoTask.Key.Name), Exception = repoTask.Value.Task.Exception, })); foreach (var failure in repoFailures) { _logger.LogError( failure.Exception, "Error retrieving {IssueType} data for {RepositoryOwner}/{RepositoryName}", failure.IssueType, failure.Repo.Owner, failure.Repo.Name); } } var allIssues = allIssuesByRepo .Where(repoTask => !repoTask.Value.Task.IsFaulted && !repoTask.Value.Task.IsCanceled) .SelectMany(issueList => issueList.Value.Task.Result .Where( issue => !IsExcludedMilestone(issue.Milestone?.Title) && issue.PullRequest == null && IsFilteredIssue(issue, repos) && ItemIncludedByInclusionLevel(issue.Assignee?.Login, issueList.Key, peopleInPersonSet)) .Select( issue => new IssueWithRepo { Issue = issue, Repo = issueList.Key, IsInAssociatedPersonSet = IsInAssociatedPersonSet(issue.Assignee?.Login, personSet), })) .OrderBy(issueWithRepo => issueWithRepo.WorkingStartTime) .ToList(); var workingIssues = allIssues .Where(issue => issue.Issue.Labels .Any(label => workingLabels.Contains(label.Name, StringComparer.OrdinalIgnoreCase))) .ToList(); var untriagedIssues = allIssues .Where(issue => issue.Issue.Milestone == null).ToList(); var unassignedIssues = allIssues .Where(issue => issue.Issue.Assignee == null).ToList(); var allPullRequests = allPullRequestsByRepo .Where(repoTask => !repoTask.Value.Task.IsFaulted && !repoTask.Value.Task.IsCanceled) .SelectMany(pullRequestList => pullRequestList.Value.Task.Result .Where( pullRequest => !IsExcludedMilestone(pullRequest.Milestone?.Title) && (ItemIncludedByInclusionLevel(pullRequest.Assignee?.Login, pullRequestList.Key, peopleInPersonSet) || ItemIncludedByInclusionLevel(pullRequest.User.Login, pullRequestList.Key, peopleInPersonSet))) .Select(pullRequest => new PullRequestWithRepo { PullRequest = pullRequest, Repo = pullRequestList.Key, IsInAssociatedPersonSet = IsInAssociatedPersonSet(pullRequest.User?.Login, personSet), })) .OrderBy(pullRequestWithRepo => pullRequestWithRepo.PullRequest.CreatedAt) .ToList(); var allIssuesInMainRepos = allIssues.Where(issue => distinctMainRepos.Contains(issue.Repo)).ToList(); var allIssuesInExtraRepos = allIssues.Where(issue => distinctExtraRepos.Contains(issue.Repo)).ToList(); var mainMilestoneData = distinctMainRepos .OrderBy(repo => repo.Owner + "/" + repo.Name, StringComparer.OrdinalIgnoreCase) .Select(repo => new MilestoneSummary() { Repo = repo, MilestoneData = allIssuesInMainRepos .Where(issue => issue.Repo == repo) .GroupBy(issue => issue.Issue.Milestone?.Title) .Select(issueMilestoneGroup => new MilestoneData { Milestone = issueMilestoneGroup.Key, OpenIssues = issueMilestoneGroup.Count(), }) .ToList(), }); var fullSortedMainMilestoneList = mainMilestoneData .SelectMany(milestone => milestone.MilestoneData) .Select(milestone => milestone.Milestone) .Distinct() .OrderBy(milestone => new PossibleSemanticVersion(milestone)); var extraMilestoneData = distinctExtraRepos .OrderBy(repo => repo.Owner + "/" + repo.Name, StringComparer.OrdinalIgnoreCase) .Select(repo => new MilestoneSummary() { Repo = repo, MilestoneData = allIssuesInExtraRepos .Where(issue => issue.Repo == repo) .GroupBy(issue => issue.Issue.Milestone?.Title) .Select(issueMilestoneGroup => new MilestoneData { Milestone = issueMilestoneGroup.Key, OpenIssues = issueMilestoneGroup.Count(), }) .ToList(), }); var fullSortedExtraMilestoneList = extraMilestoneData .SelectMany(milestone => milestone.MilestoneData) .Select(milestone => milestone.Milestone) .Distinct() .OrderBy(milestone => new PossibleSemanticVersion(milestone)); var lastApiInfo = gitHubClient.GetLastApiInfo(); _metricsService.Record("TriageController:RateLimitRemaining", lastApiInfo.RateLimit.Remaining); var issueListViewModel = new IssueListViewModel { LastApiInfo = lastApiInfo, RepoFailures = repoFailures, GitHubUserName = gitHubName, LastUpdated = DateTimeOffset.Now.ToPacificTime().ToString(), ExtraLinks = repos.RepoExtraLinks, RepoSetName = repoSet, RepoSetNames = repoDataSet.GetRepoSetLists().Select(repoSetList => repoSetList.Key).ToArray(), TotalIssues = allIssues.Where(issue => issue.Repo.RepoInclusionLevel == RepoInclusionLevel.AllItems).Count(), WorkingIssues = workingIssues.Count, UntriagedIssues = untriagedIssues.Where(issue => issue.Repo.RepoInclusionLevel == RepoInclusionLevel.AllItems).Count(), UnassignedIssues = unassignedIssues.Count, OpenPullRequests = allPullRequests.Where(pr => pr.Repo.RepoInclusionLevel == RepoInclusionLevel.AllItems).Count(), StalePullRequests = allPullRequests.Where(pr => pr.Repo.RepoInclusionLevel == RepoInclusionLevel.AllItems && pr.PullRequest.CreatedAt < DateTimeOffset.Now.AddDays(-14)).Count(), MainReposIncluded = distinctMainRepos.GetRepoSummary(allIssues, workingIssues, allPullRequests, labelQuery, workingLabels, this), ExtraReposIncluded = distinctExtraRepos.GetRepoSummary(allIssues, workingIssues, allPullRequests, labelQuery, workingLabels, this), MissingRepos = missingOrgRepos, IgnoredRepos = repos.Repos .Where(repo => repo.RepoInclusionLevel == RepoInclusionLevel.Ignored) .GroupBy(repo => repo.Owner, StringComparer.OrdinalIgnoreCase) .Select(ignoredRepoGroup => new MissingRepoSet { Org = ignoredRepoGroup.Key, MissingRepos = ignoredRepoGroup .Select(repo => repo.Name) .OrderBy(repoName => repoName, StringComparer.OrdinalIgnoreCase) .ToArray() }) .OrderBy(missingRepoSet => missingRepoSet.Org, StringComparer.OrdinalIgnoreCase) .ToArray(), MainMilestoneSummary = new MilestoneSummaryData { MilestoneData = mainMilestoneData.ToList(), MilestonesAvailable = fullSortedMainMilestoneList.ToList(), }, ExtraMilestoneSummary = new MilestoneSummaryData { MilestoneData = extraMilestoneData.ToList(), MilestonesAvailable = fullSortedExtraMilestoneList.ToList(), }, OpenIssuesQuery = openIssuesQuery, WorkingIssuesQuery = workingIssuesQuery, UntriagedIssuesQuery = untriagedIssuesQuery, UnassignedIssuesQuery = unassignedIssuesQuery, OpenPRsQuery = openPRsQuery, StalePRsQuery = stalePRsQuery, GroupByAssignee = new GroupByAssigneeViewModel { Assignees = new[] { new GroupByAssigneeAssignee { Assignee = "<assigned outside this person set>", IsMetaAssignee = true, IsInAssociatedPersonSet = false, Issues = workingIssues .Where(workingIssue => workingIssue.Issue.Assignee != null && !peopleInPersonSet.Contains(workingIssue.Issue.Assignee.Login, StringComparer.OrdinalIgnoreCase)) .ToList(), PullRequests = allPullRequests .Where( pr => pr.PullRequest.Assignee != null && !peopleInPersonSet.Contains(pr.PullRequest.User.Login, StringComparer.OrdinalIgnoreCase) && !peopleInPersonSet.Contains(pr.PullRequest.Assignee.Login, StringComparer.OrdinalIgnoreCase)) .OrderBy(pr => pr.PullRequest.CreatedAt) .ToList(), OtherIssues = allIssues .Where(issue => issue.Issue.Assignee != null && !peopleInPersonSet.Contains(issue.Issue.Assignee?.Login, StringComparer.OrdinalIgnoreCase)) .Except(workingIssues) .OrderBy(issueWithRepo => issueWithRepo.Issue.Assignee.Login, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => new PossibleSemanticVersion(issueWithRepo.Issue.Milestone?.Title)) .ThenBy(issueWithRepo => issueWithRepo.Repo.Name, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => issueWithRepo.Issue.Number) .ToList(), }, new GroupByAssigneeAssignee { Assignee = "<unassigned>", IsMetaAssignee = true, IsInAssociatedPersonSet = false, Issues = workingIssues .Where(workingIssue => workingIssue.Issue.Assignee == null) .ToList(), PullRequests = allPullRequests .Where( pr => pr.PullRequest.Assignee == null && !peopleInPersonSet.Contains(pr.PullRequest.User.Login, StringComparer.OrdinalIgnoreCase)) .OrderBy(pr => pr.PullRequest.CreatedAt) .ToList(), OtherIssues = allIssues .Where(issue => issue.Issue.Assignee == null) .Except(workingIssues) .OrderBy(issueWithRepo => new PossibleSemanticVersion(issueWithRepo.Issue.Milestone?.Title)) .ThenBy(issueWithRepo => issueWithRepo.Repo.Name, StringComparer.OrdinalIgnoreCase) .ThenBy(issueWithRepo => issueWithRepo.Issue.Number) .ToList(), }, } .ToList() .AsReadOnly(), }, }; requestStopwatch.Stop(); issueListViewModel.PageRequestTime = requestStopwatch.Elapsed; HttpContext.AddTelemetryProperty("RepoSetValid", true); return(View(issueListViewModel)); } } }
public async Task <IGitHubClient> GetGitHubClient() { var accessToken = await _jsRuntime.InvokeAsync <string>("GetGitHubAccessToken"); return(GitHubUtils.GetGitHubClient(accessToken)); }