public static async Task <List <Model.Issue> > GetIssues(DataAccess.Repository selectedrepo, ReportType reportype) { List <Model.Issue> issues = new List <Model.Issue>(); var recently = new RepositoryIssueRequest { Filter = IssueFilter.All, Since = DateTimeOffset.Now.Subtract(TimeSpan.FromDays((int)reportype)) }; if (selectedrepo.Name.CompareTo("ALL REPOSITORIES") == 0) { foreach (DataAccess.Repository repo in _repolist) { var issuesForAnet = await GithubClient.Issue.GetAllForRepository("AuthorizeNet", repo.Name, recently); //var issuesForAnet = await GithubClient.PullRequest.GetAllForRepository("AuthorizeNet", repo.Name, recently); foreach (Octokit.Issue issue in issuesForAnet) { Model.Issue iss = new Model.Issue(); iss.Assignee = issue.Assignee != null ? issue.Assignee.Name : ""; iss.Comments = issue.Comments; iss.IsPullRequest = (issue.PullRequest != null); if (iss.Comments > 0) { var commentsforissue = await GithubClient.Issue.Comment.GetAllForIssue(repo.owner, repo.Name, issue.Number); DateTime lasttime = DateTime.Now; foreach (IssueComment cmnt in commentsforissue) { iss.CommentedBy += cmnt.User.Login + ","; lasttime = cmnt.UpdatedAt.Value.DateTime; iss.AllComments += cmnt.User.Login + "\t\t\t\t" + lasttime.ToString() + "\n\n" + cmnt.Body + "\n\n"; } iss.LastCommentedAt = lasttime; iss.LastCommentedAge = (int)(DateTime.Now - iss.LastCommentedAt).TotalDays; } iss.Number = issue.Number; iss.IDLink = issue.HtmlUrl; iss.Repository = repo.Name;// issue.Repository!=null? issue.Repository.Name:""; iss.Title = issue.Title; iss.Description = issue.Body; iss.CreatedAt = issue.CreatedAt.DateTime; iss.User = issue.User.Login; iss.CreationAge = (int)(DateTime.Now - issue.CreatedAt).TotalDays; issues.Add(iss); } } } else { var issuesForAnet = await GithubClient.Issue.GetAllForRepository("AuthorizeNet", selectedrepo.Name, recently); foreach (Octokit.Issue issue in issuesForAnet) { Model.Issue iss = new Model.Issue(); iss.Assignee = issue.Assignee != null ? issue.Assignee.Name : ""; iss.Comments = issue.Comments; iss.IsPullRequest = (issue.PullRequest != null); if (iss.Comments > 0) { var commentsforissue = await GithubClient.Issue.Comment.GetAllForIssue(selectedrepo.owner, selectedrepo.Name, issue.Number); DateTime lasttime = DateTime.Now; foreach (IssueComment cmnt in commentsforissue) { iss.CommentedBy += cmnt.User.Login + ","; lasttime = cmnt.UpdatedAt.Value.DateTime; iss.AllComments += cmnt.User.Login + "\t\t\t\t" + lasttime.ToString() + "\n\n" + cmnt.Body + "\n\n"; } iss.LastCommentedAt = lasttime; iss.LastCommentedAge = (int)(DateTime.Now - iss.LastCommentedAt).TotalDays; } iss.Number = issue.Number; iss.IDLink = issue.HtmlUrl; iss.Repository = selectedrepo.Name;// issue.Repository!=null? issue.Repository.Name:""; iss.Title = issue.Title; iss.Description = issue.Body; iss.CreatedAt = issue.CreatedAt.DateTime; iss.User = issue.User.Login; iss.CreationAge = (int)(DateTime.Now - issue.CreatedAt).TotalDays; issues.Add(iss); } } return(issues); }
private static async void GetChangelog() { RepositoryIssueRequest shouldPrioritize; try { if (options.IncludeOpen == "Y") { shouldPrioritize = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, }; } else { shouldPrioritize = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.Closed, }; }; List <Issue> problemIssues = new List <Issue>(); var issues = await client.Issue.GetAllForRepository(options.Organization, options.Repo, shouldPrioritize); Dictionary <IssueType, List <Issue> > labelSet = new Dictionary <IssueType, List <Issue> >(); foreach (var issue in issues) { if (issue.Milestone != null && issue.Milestone.Title == options.Milestone) { issuesList.Add(issue); bool issueFixed = true; bool hidden = false; IssueType issueType = IssueType.None; bool epicLabel = false; bool regressionDuringThisVersion = false; bool engineeringImprovement = false; foreach (var label in issue.Labels) { if (label.Name.Contains("ClosedAs:")) { issueFixed = false; } if (label.Name == "RegressionDuringThisVersion") { regressionDuringThisVersion = true; hidden = true; } if (label.Name == "Area: Engineering Improvements") { engineeringImprovement = true; hidden = true; } switch (label.Name) { case "Epic": epicLabel = true; break; case "Type:Feature": issueType = IssueType.Feature; break; case "Type:DCR": issueType = IssueType.DCR; break; case "Type:Bug": issueType = IssueType.Bug; break; case "Type:Spec": issueType = IssueType.Spec; break; default: break; } } // if an issue is an epicLabel and has a real IssueType (feature/bug/dcr), // then hide it... we want to show the primary epic issue only. if (epicLabel) { if (issueType == IssueType.None) { issueType = IssueType.Feature; } else { hidden = true; } } else if (issueType == IssueType.None) { if (issueFixed && !regressionDuringThisVersion && !engineeringImprovement) { // PROBLEM : if this is fixed...was it a feature/bug or dcr??? problemIssues.Add(issue); } else { hidden = true; } } if (!hidden && issueFixed) { List <Issue> issueCollection = null; if (!labelSet.ContainsKey(issueType)) { issueCollection = new List <Issue>(); labelSet.Add(issueType, issueCollection); } else { issueCollection = labelSet[issueType]; } issueCollection.Add(issue); } } } GenerateMarkdown(labelSet, problemIssues); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } }
/// <summary> /// Gets issues for a repository. /// </summary> /// <remarks> /// http://developer.github.com/v3/issues/#list-issues-for-a-repository /// </remarks> /// <param name="repositoryId">The ID of the repository</param> /// <param name="request">Used to filter and sort the list of issues returned</param> public IObservable<Issue> GetAllForRepository(int repositoryId, RepositoryIssueRequest request) { Ensure.ArgumentNotNull(request, "request"); return GetAllForRepository(repositoryId, request, ApiOptions.None); }
/// <summary> /// Gets issues for a repository. /// </summary> /// <remarks> /// http://developer.github.com/v3/issues/#list-issues-for-a-repository /// </remarks> /// <param name="repositoryId">The ID of the repository</param> /// <param name="request">Used to filter and sort the list of issues returned</param> /// <param name="options">Options for changing the API response</param> public IObservable<Issue> GetAllForRepository(int repositoryId, RepositoryIssueRequest request, ApiOptions options) { Ensure.ArgumentNotNull(request, "request"); Ensure.ArgumentNotNull(options, "options"); return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.Issues(repositoryId), request.ToParametersDictionary(), options); }
private static void GetRandomIssue(Project project, bool open) { Spinner.Start($"Getting random issue for {project.name}", spinner => { if (project.site.EndsWith("/")) { project.site = project.site.TrimEnd(Convert.ToChar("/")); } var parts = project.site.Split(Convert.ToChar("/")); var owner = parts[parts.Length - 2]; var repoName = parts[parts.Length - 1]; var client = new GitHubClient(new ProductHeaderValue("dotnet-upforgrabs")); var requestIssues = new RepositoryIssueRequest { State = ItemStateFilter.Open, }; requestIssues.Labels.Add(project.upforgrabs.name); try { var issues = client.Issue.GetAllForRepository(owner, repoName, requestIssues).GetAwaiter().GetResult(); if (issues.Count == 0) { spinner.Fail($"{project.name} currently has 0 open issues for {project.upforgrabs.name}"); } else { spinner.Succeed(); var rand = new Random(issues.Count); var item = issues.OrderBy(s => rand.NextDouble()).First(); Console.WriteLine(); Console.Write("UpForGrabs Issue for "); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"{project.name}"); Console.ResetColor(); Console.WriteLine(); Console.Write(" - Title: "); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"{item.Title}"); Console.Write(""); Console.ForegroundColor = ConsoleColor.Yellow; Console.ResetColor(); Console.Write(" - Repository: "); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"{owner}/{repoName}"); Console.ResetColor(); Console.Write(" - Issue: #"); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"{item.Number.ToString()}"); Console.ResetColor(); Console.Write(" - Status: "); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"{item.State.StringValue}"); Console.ResetColor(); Console.WriteLine(); var url = $"https://github.com/{owner}/{repoName}/issues/{item.Number.ToString()}"; Console.Write("Start now: "); Console.ForegroundColor = ConsoleColor.DarkCyan; Console.WriteLine(url); Console.WriteLine(); Console.ResetColor(); if (open) { OpenResult(url); } else { string test = string.Empty; Console.WriteLine($"{Environment.NewLine}Open issue to get started? (Y/N)"); ConsoleKeyInfo answer = new ConsoleKeyInfo(); while (string.IsNullOrEmpty(test)) { answer = Console.ReadKey(); if (answer.Key == ConsoleKey.Y) { OpenResult(url); test = "yes"; } if (answer.Key == ConsoleKey.N) { test = "no"; } } } } } catch (Exception) { spinner.Fail($"Something went wrong trying to get issues for repo:{project.site}. {Environment.NewLine}[DebugInfo: site:{project.site} {Environment.NewLine}owner:{owner} {Environment.NewLine}repoName:{repoName}]"); } }); }
private void Normal() { ReceiveAsync <Messages.Messages.RetrieveRepos>(async msg => { _logger.Info("Retrieving repos for {OrgName} org", msg.OrgName); var repos = await _apiClient.Repository.GetAllForOrg(msg.OrgName); _logger.Info("Retrieved {TotalRepoCount} total repos for {OrgName} org on page", repos.Count, msg.OrgName); var eligibleRepos = repos.Where(x => !x.Private && !x.Archived && x.HasIssues && RepositoryIsUpdatedWithin90Days(x) ).Select(x => new BasicRepoInfo(x.FullName, x.Name, x.Id, x.StargazersCount, x.ForksCount, x.OpenIssuesCount, x.UpdatedAt)).ToList(); _logger.Info("After filtering, there are {EligibleRepoCount} eligible repos for {OrgName} on page", eligibleRepos.Count, msg.OrgName); CheckApiLimits(_apiClient.GetLastApiInfo()); Sender.Tell(new Messages.Messages.ReposForOrganization(eligibleRepos)); }); ReceiveAsync <Messages.Messages.RetrieveLabels>(async msg => { var labels = await _apiClient.Issue.Labels.GetAllForRepository(msg.RepoId); var selectedLabelInfo = labels.Select(x => x.Name).ToList(); CheckApiLimits(_apiClient.GetLastApiInfo()); Sender.Tell(new Messages.Messages.LabelsForRepo(selectedLabelInfo)); }); ReceiveAsync <Messages.Messages.GetIssueCountPerLabel>(async msg => { var req = new RepositoryIssueRequest(); msg.LabelsToCheck.ForEach(label => req.Labels.Add(label)); req.Filter = IssueFilter.All; req.State = ItemStateFilter.All; var issuesForLabels = await _apiClient.Issue.GetAllForRepository(msg.RepoId, req); var result = new Dictionary <string, List <IssueInfo> >(); foreach (var label in msg.LabelsToCheck) { var issueInfos = issuesForLabels .Where(issue => issue.Labels.Any(labelItem => labelItem.Name.Equals(label, StringComparison.InvariantCultureIgnoreCase))) .Select(issue => new IssueInfo(issue.Id, issue.UpdatedAt, issue.Url, issue.ClosedAt, issue.CreatedAt)).ToList(); if (issueInfos.Any()) { result.Add(label, issueInfos); } } var totalCount = result.Sum(x => x.Value.Count); CheckApiLimits(_apiClient.GetLastApiInfo()); if (result.Any()) { Sender.Tell(new Messages.Messages.LabelsAndIssuesResponse(result)); } else { _logger.Info("No relevant issues found for {RepoId} / {RepoFullName}. Ends here.", msg.RepoId, msg.RepoFullName); } }); }
private async Task <string> Build() { var(repoOwner, repoName, milestone, previousMilestone, lastCommit) = config; var client = new GitHubClient(new ProductHeaderValue(config.ProductHeader)); var tokenAuth = new Credentials(config.Token); client.Credentials = tokenAuth; if (milestone == "_") { var allContributors = await client.Repository.GetAllContributors(repoOwner, repoName); builder.AppendLine("# All contributors"); builder.AppendLine(); foreach (var contributor in allContributors) { var user = await client.User.Get(contributor.Login); var name = user?.Name; builder.AppendLine("* " + (string.IsNullOrEmpty(name) ? contributor.ToLink() : contributor.ToLinkWithName(name))); } return(builder.ToString()); } var issueRequest = new RepositoryIssueRequest { State = ItemStateFilter.Closed }; var pullRequestRequest = new PullRequestRequest { State = ItemStateFilter.Closed }; var issues = (await client.Issue.GetAllForRepository(repoOwner, repoName, issueRequest)) .Where(issue => issue.Milestone != null && issue.Milestone.Title == milestone) .Where(issue => issue.PullRequest == null) .OrderBy(issue => issue.Number) .ToList(); var pullRequests = (await client.PullRequest.GetAllForRepository(repoOwner, repoName, pullRequestRequest)) .Where(issue => issue.Milestone != null && issue.Milestone.Title == milestone) .OrderBy(issue => issue.Number) .ToList(); var compare = await client.Repository.Commit.Compare(repoOwner, repoName, previousMilestone, lastCommit); var commits = compare.Commits; var authorNames = new Dictionary <string, string>(); foreach (var contributor in commits.Select(commit => commit.Author)) { if (contributor != null && !authorNames.ContainsKey(contributor.Login)) { var user = await client.User.Get(contributor.Login); var name = user?.Name; authorNames[contributor.Login] = string.IsNullOrWhiteSpace(name) ? contributor.Login : name; } } var contributors = compare.Commits .Select(commit => commit.Author) .Where(author => author != null) .Distinct(AuthorEqualityComparer.Default) .OrderBy(author => authorNames[author.Login]) .ToImmutableList(); var milestoneHtmlUlr = $"https://github.com/{repoOwner}/{repoName}/issues?q=milestone:{milestone}"; builder.AppendLine("## Milestone details"); builder.AppendLine(); builder.AppendLine($"In the [{milestone}]({milestoneHtmlUlr}) scope, "); builder.Append(issues.Count + " issues were resolved and "); builder.AppendLine(pullRequests.Count + " pull requests where merged."); builder.AppendLine($"This release includes {commits.Count} commits by {contributors.Count} contributors."); builder.AppendLine(); AppendList("Resolved issues", issues, issue => $"[#{issue.Number}]({issue.HtmlUrl}) {issue.Title.Trim()}{issue.Assignee.ToStr("assignee:")}"); AppendList("Merged pull requests", pullRequests, pr => $"[#{pr.Number}]({pr.HtmlUrl}) {pr.Title.Trim()}{pr.User.ToStr("by")}"); AppendList("Commits", commits, commit => $"{commit.ToLink()} {commit.Commit.ToCommitMessage()}{commit.ToByStr()}"); AppendList("Contributors", contributors, contributor => $"{authorNames[contributor.Login]} ({contributor.ToLink()})".Trim(), "Thank you very much!"); return(builder.ToString()); }
/// <summary> /// issue検索 /// </summary> /// <returns></returns> public async Task IssueSearch(IDialogContext context, IAwaitable <IMessageActivity> argument) { //入力メッセージを格納 var message = await argument; var text = message.Text; //検索issue格納用リスト List <Issue> issueListTemp = new List <Issue>(); //issue一覧表示の引数用リスト IReadOnlyList <Issue> issueList = null; //操作キャンセル確認メソッド #region 操作一覧選択 switch (search) { case "番号": { //現在のリポジトリからopen済みissueを全取得 IReadOnlyList <Issue> issues = await GitHubDialog.github.Issue.GetAllForRepository(repository.Split('/')[0], repository.Split('/')[1]); //入力されたメッセージを数値化 int num = Convert.ToInt32(text); //入力されたメッセージの数値と同じissueを取得 issueListTemp = issues.ToList().Where(x => x.Number == num) as List <Issue>; //issue一覧表示をするためにリストの形式をreadonlyに変化 issueList = issueListTemp; break; } case "作成ユーザ": { //issue検索用変数を用意し、メンバ変数「作成者」に入力メッセージを格納する var recently = new RepositoryIssueRequest { Creator = text }; //現在のリポジトリからissue検索用変数に添ったissueを全取得 issueList = await GitHubDialog.github.Issue.GetAllForRepository(repository.Split('/')[0], repository.Split('/')[1], recently); break; } case "タイトル": { //現在のリポジトリからopen済みissueを全取得 IReadOnlyList <Issue> issues = await GitHubDialog.github.Issue.GetAllForRepository(repository.Split('/')[0], repository.Split('/')[1]); //入力メッセージがタイトルと部分一致するissueを取得 issueListTemp = issues.ToList().Where(x => x.Title.Contains(text)) as List <Issue>; //issue一覧表示をするためにリストの形式をreadonlyに変化 issueList = issueListTemp; break; } case "ラベル": { List <string> labels = new List <string> { text }; issueList = await GitHubDialog.convinient.GetIssuesForLabel(labels); break; } } #endregion //issueの一覧を表示する旨をメッセージでbot送信 await context.PostAsync("issueの一覧を表示します"); //issue一覧表示処理待ち await IssueDisplay(context, issueList); //issue検索操作を終了する context.Done <object>(context); }
/// <summary> /// Creates the report in markdown and returns it as a string /// </summary> /// <returns>The Markdown string</returns> public async Task <string> CreateReport() { Console.WriteLine($"{Resources.AppTitle}"); Console.WriteLine($"{Resources.Copyright}"); Console.WriteLine($"{Resources.PlsWait}"); Console.WriteLine($"{Resources.Step} 1 {Resources.of} 3 - {Resources.Headers}"); StringBuilder sb = new StringBuilder(); sb.AppendLine($"# {Resources.ReportTitle}"); sb.AppendLine(""); sb.AppendLine($"## {Resources.StatusReportFor} {_repository} {Resources.CreatedOn} {DateTime.Now}"); sb.AppendLine(""); sb.AppendLine($"## {Resources.OverallInfo}"); sb.AppendLine(""); var client = new GitHubClient(new ProductHeaderValue("project-documentor")); var tokenAuth = new Credentials(_token); client.Credentials = tokenAuth; var user = await client.User.Current().ConfigureAwait(true); var repo = await client.Repository.Get(_owner, _repository).ConfigureAwait(true); sb.AppendLine($"| {Resources.Item} | {Resources.Value} |"); sb.AppendLine($"| -- | -- |"); sb.AppendLine($"| {Resources.DateCreated} | {repo.CreatedAt} |"); sb.AppendLine($"| {Resources.DefaultBranch} | {repo.DefaultBranch} |"); sb.AppendLine($"| {Resources.Description} | {repo.Description} |"); sb.AppendLine($"| {Resources.FullName} | {repo.FullName} |"); sb.AppendLine($"| {Resources.URL} | {repo.GitUrl} |"); sb.AppendLine($"| {Resources.Name} | {repo.Name} |"); sb.AppendLine($"| {Resources.CurrentOpenIssues} | {repo.OpenIssuesCount} |"); sb.AppendLine($"| {Resources.LastCodeUpdate} | {repo.PushedAt} |"); sb.AppendLine($"| {Resources.Subscribers} | {repo.WatchersCount} |"); sb.AppendLine($"| {Resources.Last_Update} | {repo.UpdatedAt} |"); sb.AppendLine(""); sb.AppendLine($"## {Resources.Issues}"); Console.WriteLine($"{Resources.Step} 2 {Resources.of} 3 - {Resources.IssuesModified30}"); sb.AppendLine(""); sb.AppendLine($"### {Resources.IssuesModified30}"); var issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, Since = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(30)), SortProperty = IssueSort.Updated, SortDirection = SortDirection.Descending }; var issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); sb.AppendLine(""); sb.AppendLine($"| {Resources.IssueId} | {Resources.Modified} | {Resources.Status} | {Resources.Title} |"); sb.AppendLine("| -------- | -------- | ------ | ----- |"); foreach (var issue in issues) { sb.AppendLine($"| [{issue.Number}]({issue.Url}) | {issue.UpdatedAt.Value.ToLocalTime().ToString("dd-MM-yyyy HH:mm", CultureInfo.CurrentCulture)} | {issue.State} | {issue.Title} |"); } sb.AppendLine(""); sb.AppendLine($"### {Resources.Open_Issues}"); sb.AppendLine(""); sb.AppendLine($"| {Resources.IssueId} | {Resources.Modified} | {Resources.Status} | {Resources.Title} |"); sb.AppendLine("| -------- | -------- | ------ | ----- |"); issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.Open, Since = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(30)), SortProperty = IssueSort.Updated, SortDirection = SortDirection.Descending }; issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var issue in issues) { sb.AppendLine($"| [{issue.Number}]({issue.Url}) | {issue.UpdatedAt.Value.ToLocalTime().ToString("dd-MM-yyyy HH:mm", CultureInfo.CurrentCulture)} | {issue.State} | {issue.Title} |"); } Console.WriteLine($"{Resources.Step} 3 {Resources.of} 3, {Resources.Projects}"); sb.AppendLine(""); sb.AppendLine($"## {Resources.Projects}"); //Projects var projects = await client.Repository.Project.GetAllForRepository(_owner, _repository).ConfigureAwait(true); int projectid = 1; foreach (var project in projects) { Console.WriteLine($" Working on Project {projectid} of {projects.Count}: {project.Name}"); projectid++; sb.AppendLine(""); sb.AppendLine($"### {project.Name}"); sb.AppendLine(""); sb.AppendLine($"| {Resources.Item} | {Resources.Value} |"); sb.AppendLine("| -----| ----- |"); sb.AppendLine($"| {Resources.DateCreated} | {project.CreatedAt.ToLocalTime().ToString("dd-MM-yyyy HH:mm", CultureInfo.CurrentCulture)} |"); sb.AppendLine($"| {Resources.Project_Number} | {project.Number}"); sb.AppendLine($"| {Resources.Status} | {project.State}"); sb.AppendLine($"| {Resources.Modified} | {project.UpdatedAt.ToLocalTime().ToString("dd-MM-yyyy HH:mm", CultureInfo.CurrentCulture)} |"); sb.AppendLine(""); sb.AppendLine($"#### {Resources.Project_Status}"); sb.AppendLine(""); var columns = await client.Repository.Project.Column.GetAll(project.Id).ConfigureAwait(true); List <string> lines = new List <string>(); int colcount = 0; foreach (var column in columns) { colcount++; sb.AppendLine($"##### {column.Name}"); sb.AppendLine(""); Console.WriteLine($" Column {colcount} of {columns.Count} - {column.Name}"); var cards = await client.Repository.Project.Card.GetAll(column.Id).ConfigureAwait(true); int cardcount = 0; //Handle no cards, produce nice message if (cards.Count == 0) { sb.AppendLine($" *{Resources.ThereAreNoIssuesinThisStatus}*"); sb.AppendLine(); } foreach (var card in cards) { cardcount++; Console.WriteLine($" {Resources.Working_on_card}: {cardcount} {Resources.of} {cards.Count}"); if (column.Name != "Done" && column.Name != "Closed") { if (string.IsNullOrEmpty(card.Note)) { string issueId = card.ContentUrl.Split("/")[card.ContentUrl.Split("/").Length - 1]; var cardissue = await client.Issue.Get(_owner, _repository, Convert.ToInt32(issueId, CultureInfo.CurrentCulture)).ConfigureAwait(true); sb.AppendLine($"**[{issueId}]({cardissue.Url})** - *{cardissue.Title}*"); sb.AppendLine(""); sb.AppendLine($"{cardissue.Body}"); sb.AppendLine(""); } else { sb.AppendLine(card.Note); sb.AppendLine(""); } } else { if (string.IsNullOrEmpty(card.Note)) { string issueId = card.ContentUrl.Split("/")[card.ContentUrl.Split("/").Length - 1]; var cardissue = await client.Issue.Get(_owner, _repository, Convert.ToInt32(issueId, CultureInfo.CurrentCulture)).ConfigureAwait(true); sb.AppendLine($"- [{issueId}]({cardissue.Url}) - {cardissue.Title}"); } else { } } } } } return(sb.ToString()); }
public async Task EnsuresArgumentsNotNull() { var client = new IssuesClient(Substitute.For<IApiConnection>()); var options = new ApiOptions(); var request = new RepositoryIssueRequest(); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository(null, "name")); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository(null, "name", options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository(null, "name", request)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository(null, "name", request, options)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("", "name")); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("", "name", options)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("", "name", request)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("", "name", request, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", null)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", null, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", null, request)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", null, request, options)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("owner", "")); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("owner", "", options)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("owner", "", request)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForRepository("owner", "", request, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", "name", (ApiOptions)null)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", "name", (RepositoryIssueRequest)null)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", "name", null, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForRepository("owner", "name", request, null)); }
public async Task EnsuresNonNullArguments() { var client = new IssuesClient(Substitute.For<IApiConnection>()); var options = new ApiOptions(); var request = new RepositoryIssueRequest(); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization(null)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization(null, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization(null, request)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization(null, request, options)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForOrganization("")); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForOrganization("", options)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForOrganization("", request)); await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForOrganization("", request, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization("org", (ApiOptions)null)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization("org", (IssueRequest)null)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization("org", null, options)); await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForOrganization("org", request, null)); }
private async Task ProcessBuildNotificationsAsync(Build build) { const string fullBranchPrefix = "refs/heads/"; foreach (var monitor in _options.Value.Monitor.Builds) { if (!string.Equals(build.Project.Name, monitor.Project, StringComparison.OrdinalIgnoreCase)) { continue; } if (!string.Equals(monitor.DefinitionPath, $"{build.Definition.Path}\\{build.Definition.Name}", StringComparison.OrdinalIgnoreCase)) { continue; } if (monitor.Branches.All(mb => !string.Equals($"{fullBranchPrefix}{mb}", build.SourceBranch, StringComparison.OrdinalIgnoreCase))) { continue; } if (monitor.Tags != null && monitor.Tags.Any() && !(monitor.Tags.Intersect(build.Tags).Any())) { // We should only skip processing if tags were specified in the monitor, and none of those tags were found in the build continue; } string prettyBranch = build.SourceBranch; if (prettyBranch.StartsWith(fullBranchPrefix)) { prettyBranch = prettyBranch.Substring(fullBranchPrefix.Length); } string prettyTags = (monitor.Tags != null && monitor.Tags.Any()) ? $"{string.Join(", ", build.Tags)}" : ""; _logger.LogInformation( "Build '{buildNumber}' in project '{projectName}' with definition '{definitionPath}', tags '{prettyTags}', and branch '{branch}' matches monitoring criteria, sending notification", build.BuildNumber, build.Project.Name, build.Definition.Path, prettyTags, build.SourceBranch); _logger.LogInformation("Fetching timeline messages..."); string timelineMessage = await BuildTimelineMessage(build); _logger.LogInformation("Fetching changes messages..."); string changesMessage = await BuildChangesMessage(build); BuildMonitorOptions.IssuesOptions repo = _options.Value.Issues.SingleOrDefault(i => string.Equals(monitor.IssuesId, i.Id, StringComparison.OrdinalIgnoreCase)); if (repo != null) { IGitHubClient github = await _gitHubApplicationClientFactory.CreateGitHubClientAsync(repo.Owner, repo.Name); DateTimeOffset?finishTime = DateTimeOffset.TryParse(build.FinishTime, out var parsedFinishTime) ?parsedFinishTime: (DateTimeOffset?)null; DateTimeOffset?startTime = DateTimeOffset.TryParse(build.StartTime, out var parsedStartTime) ? parsedStartTime:(DateTimeOffset?)null; string timeString = ""; string durationString = ""; if (finishTime.HasValue) { timeString = finishTime.Value.ToString("R"); if (startTime.HasValue) { durationString = ((int)(finishTime.Value - startTime.Value).TotalMinutes) + " minutes"; } } string icon = build.Result == "failed" ? ":x:" : ":warning:"; string body = @$ "Build [#{build.BuildNumber}]({build.Links.Web.Href}) {build.Result} ## {icon} : {build.Project.Name} / {build.Definition.Name} {build.Result} ### Summary **Finished** - {timeString} **Duration** - {durationString} **Requested for** - {build.RequestedFor.DisplayName} **Reason** - {build.Reason} ### Details {timelineMessage} ### Changes {changesMessage} "; string issueTitlePrefix = $"Build failed: {build.Definition.Name}/{prettyBranch} {prettyTags}"; if (repo.UpdateExisting) { // There is no way to get the username of our bot directly from the GithubApp with the C# api. // Issue opened in Octokit: https://github.com/octokit/octokit.net/issues/2335 // We do, however, have access to the HtmlUrl, which ends with the name of the bot. // Additionally, when the bot opens issues, the username used ends with [bot], which isn't strictly // part of the name anywhere else. So, to get the correct creator name, get the HtmlUrl, grab // the bot's name from it, and append [bot] to that string. var githubAppClient = _gitHubApplicationClientFactory.CreateGitHubAppClient(); string creator = (await githubAppClient.GitHubApps.GetCurrent()).HtmlUrl.Split("/").Last(); RepositoryIssueRequest issueRequest = new RepositoryIssueRequest { Creator = $"{creator}[bot]", State = ItemStateFilter.Open, SortProperty = IssueSort.Created, SortDirection = SortDirection.Descending }; foreach (string label in repo.Labels.OrEmpty()) { issueRequest.Labels.Add(label); } foreach (string label in monitor.Labels.OrEmpty()) { issueRequest.Labels.Add(label); } List <Issue> matchingIssues = (await github.Issue.GetAllForRepository(repo.Owner, repo.Name, issueRequest)).ToList(); Issue matchingIssue = matchingIssues.FirstOrDefault(i => i.Title.StartsWith(issueTitlePrefix)); if (matchingIssue != null) { _logger.LogInformation("Found matching issue {issueNumber} in {owner}/{repo}. Will attempt to add a new comment.", matchingIssue.Number, repo.Owner, repo.Name); // Add a new comment to the issue with the body IssueComment newComment = await github.Issue.Comment.Create(repo.Owner, repo.Name, matchingIssue.Number, body); _logger.LogInformation("Logged comment in {owner}/{repo}#{issueNumber} for build failure", repo.Owner, repo.Name, matchingIssue.Number); return; } else { _logger.LogInformation("Matching issues for {issueTitlePrefix} not found. Creating a new issue.", issueTitlePrefix); } } // Create new issue if repo.UpdateExisting is false or there were no matching issues var newIssue = new NewIssue($"{issueTitlePrefix} #{build.BuildNumber}") { Body = body, }; if (!string.IsNullOrEmpty(monitor.Assignee)) { newIssue.Assignees.Add(monitor.Assignee); } foreach (string label in repo.Labels.OrEmpty()) { newIssue.Labels.Add(label); } foreach (string label in monitor.Labels.OrEmpty()) { newIssue.Labels.Add(label); } Issue issue = await github.Issue.Create(repo.Owner, repo.Name, newIssue); _logger.LogInformation("Logged issue {owner}/{repo}#{issueNumber} for build failure", repo.Owner, repo.Name, issue.Number); }
public async Task Handle(EventContext eventContext) { if (!eventContext.WebHookEvent.IsMessageAuthenticated) { // message is not issued by GitHub. Possibly from a malucious attacker. // log it and return; Debug.WriteLine("ERROR: IssueEvent could not be authenticated!"); return; } var action = (string)eventContext.WebHookEvent.GetPayload().action; var title = (string)eventContext.WebHookEvent.GetPayload().issue.title; var repository = (string)eventContext.WebHookEvent.GetPayload().repository.name; Debug.WriteLine($"Issue with title '{title}' on repository '{repository}' was '{action}'"); if (action.Contains("opened")) { try { var client = eventContext.InstallationContext.Client; var payload = eventContext.WebHookEvent.GetPayload(); var creatorName = (string)payload.issue.user.login; var respositoryName = (string)payload.repository.name; var ownerName = (string)payload.repository.owner.login; var allIssuesForUser = new RepositoryIssueRequest { Creator = creatorName, State = ItemStateFilter.All, Filter = IssueFilter.All }; var issues = await client.Issue.GetAllForRepository(ownerName, respositoryName, allIssuesForUser); var issueCountForCreator = issues.Where(i => i.PullRequest == null).Count(); if (issueCountForCreator == 1) { var welcomeFileResponse = await client.Repository.Content.GetRawContent(ownerName, repository, ".github/welcome-first-issue.md"); var welcomeFileContent = Encoding.Default.GetString(welcomeFileResponse); var issueNumber = (int)payload.issue.number; var repositoryId = (long)payload.repository.id; _ = await client .Issue.Comment .Create(repositoryId, issueNumber, welcomeFileContent); Debug.WriteLine("Habe einen Willkommens-Kommentar gepostet!"); } else { Debug.WriteLine("Der Ersteller ist kein First-Time-Contributor!"); } } catch (Exception ex) { Debug.WriteLine($"Exception gefangen: {ex}"); throw; } } }
public IWithIssueOptions GetIssues() { repositoryIssueRequest = new RepositoryIssueRequest(); return(this); }
private static async void GetChangelog() { try { RepositoryIssueRequest issueQuery; if (options.IncludeOpen == "Y") { issueQuery = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, }; } else { issueQuery = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.Closed, }; } var issues = await client.Issue.GetAllForRepository(options.Organization, options.Repo, issueQuery); Dictionary <IssueType, List <Issue> > IssuesByIssueType = new Dictionary <IssueType, List <Issue> >(); foreach (var issue in issues) { if (issue.Milestone != null && issue.Milestone.Title == options.Milestone) { bool issueFixed = true; bool hidden = false; IssueType issueType = IssueType.None; bool epicLabel = false; bool regressionDuringThisVersion = false; bool engineeringImprovement = false; string requiredLabel = options.RequiredLabel?.ToLower(); bool foundRequiredLabel = string.IsNullOrEmpty(requiredLabel); foreach (var label in issue.Labels) { if (label.Name.Contains("ClosedAs:")) { issueFixed = false; } if (label.Name == "RegressionDuringThisVersion") { regressionDuringThisVersion = true; hidden = true; } if (label.Name == "Area: Engineering Improvements") { engineeringImprovement = true; hidden = true; } if (!foundRequiredLabel && label.Name.ToLower() == requiredLabel) { foundRequiredLabel = true; } switch (label.Name) { case "Epic": epicLabel = true; break; case "Type:Feature": issueType = IssueType.Feature; break; case "Type:DCR": issueType = IssueType.DCR; break; case "Type:Bug": issueType = IssueType.Bug; break; case "Type:Spec": issueType = IssueType.Spec; break; default: break; } } if (!foundRequiredLabel) { hidden = true; } // if an issue is an epicLabel and has a real IssueType (feature/bug/dcr), // then hide it... we want to show the primary epic issue only. if (epicLabel) { if (issueType == IssueType.None) { issueType = IssueType.Feature; } else { hidden = true; } } else if (issueType == IssueType.None) { if (!(issueFixed && !regressionDuringThisVersion && !engineeringImprovement)) { hidden = true; } } if (!hidden && issueFixed) { if (!IssuesByIssueType.ContainsKey(issueType)) { IssuesByIssueType.Add(issueType, new List <Issue>()); } IssuesByIssueType[issueType].Add(issue); } } } GenerateMarkdown(IssuesByIssueType); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } }
/// <summary> /// Gets issues for a repository. /// </summary> /// <remarks> /// http://developer.github.com/v3/issues/#list-issues-for-a-repository /// </remarks> /// <param name="repositoryId">The Id of the repository</param> /// <param name="request">Used to filter and sort the list of issues returned</param> public IObservable <Issue> GetAllForRepository(int repositoryId, RepositoryIssueRequest request) { Ensure.ArgumentNotNull(request, "request"); return(GetAllForRepository(repositoryId, request, ApiOptions.None)); }
/// <summary> /// Retrieves issue and security issue counts (subset) to minimize calls out. /// Note: Octokit Search API was found earlier to be unreliable /// </summary> /// <returns> </returns> public async Task GetIssueHealth(HealthMetrics metrics) { Logger.Trace("GetIssueHealth({0}, {1})", purl.Namespace, purl.Name); var securityFlags = new string[] { "security", "insecure", "vulnerability", "cve", "valgrind", "xss", "sqli ", "vulnerable", "exploit", "fuzz", "injection", "buffer overflow", "valgrind", "sql injection", "csrf", "xsrf", "pwned", "akamai legacy ssl", "bad cipher ordering", "untrusted", "backdoor", "command injection" }; var filter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, Since = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3 * 365)) }; int openIssues = 0; int closedIssues = 0; int openSecurityIssues = 0; int closedSecurityIssues = 0; var issues = await Client.Issue.GetAllForRepository(purl.Namespace, purl.Name, filter); foreach (var issue in issues) { // filter out pull requests if (issue.Url.Contains("/pull") || issue.HtmlUrl.Contains("/pull")) { continue; } //general issue status if (issue.State == ItemState.Open) { openIssues++; } else if (issue.State == ItemState.Closed) { closedIssues++; } // security status check within applicable fields var labels = string.Join(",", issue.Labels.Select(l => l.Name)); var content = issue.Title + issue.Body + labels; if (securityFlags.Any(s => content.Contains(s, StringComparison.InvariantCultureIgnoreCase))) { if (issue.State == ItemState.Open) { openSecurityIssues++; } else if (issue.State == ItemState.Closed) { closedSecurityIssues++; } } } double issueHealth = 0.0; if (openIssues + closedIssues > 0) { issueHealth = 30.0 * openIssues / (openIssues + closedIssues); } else { issueHealth = 50.0; } metrics.IssueHealth = issueHealth; double securityIssueHealth = 0.0; if (openSecurityIssues + closedSecurityIssues > 0) { securityIssueHealth = (double)openSecurityIssues / (double)(openSecurityIssues + closedSecurityIssues); } else { securityIssueHealth = 60.0; // Lose a little credit if project never had a security issue } metrics.SecurityIssueHealth = securityIssueHealth; return; }
public async Task ProcessIssueEvent(IssuesHookData issuePayload) { if (issuePayload.Action != "opened" && issuePayload.Action != "reopened" && issuePayload.Action != "closed") { _logger.LogInformation($"Received github action '{issuePayload.Action}', nothing to do"); return; } // Determine identifiable information for triage items var triageItems = GetTriageItems(issuePayload.Issue.Body); if (!triageItems.Any()) { /* Item is not a triage item (does not contain identifiable information), do nothing */ _logger.LogInformation($"{issuePayload.Issue.Url} is not a triage type issue."); return; } IGitHubClient gitHubClient = await _gitHubApplicationClientFactory.CreateGitHubClientAsync(issuePayload.Repository.Owner.Login, issuePayload.Repository.Name); if (issuePayload.Action == "opened" || issuePayload.Action == "reopened") { // First, look for duplicate issues that are open var openIssues = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.Open, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending, }; openIssues.Labels.Add(_markingLabelName); _logger.LogInformation("Getting open issues"); var existingTriageIssues = await gitHubClient.Issue.GetAllForRepository(issuePayload.Repository.Id, openIssues); _logger.LogInformation($"There are {existingTriageIssues.Count} open issues with the '{_markingLabelName}' label"); foreach (var existingIssue in existingTriageIssues) { if (existingIssue.Number != issuePayload.Issue.Number) { var existingIssueItems = GetTriageItems(existingIssue.Body); if (IsDuplicate(triageItems, existingIssueItems)) { await gitHubClient.Issue.Comment.Create(issuePayload.Repository.Id, issuePayload.Issue.Number, $"Duplicate issue was detected.\n\nClosing as duplicate of {existingIssue.HtmlUrl}\n\nFor more information see {_docLink}"); var issueUpdate = new IssueUpdate { State = ItemState.Closed, }; await gitHubClient.Issue.Update(issuePayload.Repository.Id, issuePayload.Issue.Number, issueUpdate); return; } } } // No duplicates, add label and move issue to triage var issue = await gitHubClient.Issue.Get(issuePayload.Repository.Id, issuePayload.Issue.Number); if (!issue.Labels.Any(l => l.Name == _markingLabelName)) { var update = issue.ToUpdate(); update.AddLabel(_markingLabelName); foreach (var label in _issueLabels) { if (issue.Labels.All(l => l.Name != label)) { update.AddLabel(label); } } await gitHubClient.Issue.Update(issuePayload.Repository.Id, issuePayload.Issue.Number, update); await AddToZenHubTopic(issuePayload, gitHubClient, issue); } } if (issuePayload.Action == "closed") { IReadOnlyList <IssueComment> comments = gitHubClient.Issue.Comment.GetAllForIssue(issuePayload.Repository.Id, issuePayload.Issue.Number).Result; // find the latest comment with category command string updatedCategory = null; foreach (var comment in comments) { string category = GetTriageIssueProperty("category", comment.Body); if (!string.IsNullOrEmpty(category)) { updatedCategory = category; } } if (updatedCategory != null) { foreach (var triageItem in triageItems) { triageItem.UpdatedCategory = updatedCategory; } } } foreach (var triageItem in triageItems) { triageItem.Url = issuePayload.Issue.HtmlUrl; _logger.LogInformation($"buildId: {triageItem.BuildId}, recordId: {triageItem.RecordId}, index: {triageItem.Index}, category: {triageItem.UpdatedCategory}, url: {triageItem.Url}"); } await IngestTriageItemsIntoKusto(triageItems); await gitHubClient.Issue.Comment.Create(issuePayload.Repository.Id, issuePayload.Issue.Number, $"Bot has updated the 'TimelineIssuesTriage' database.\n**PowerBI reports may take up to 24 hours to refresh**\n\nSee {_docLink} for more information."); return; }
/// <summary> /// Gets all issues for a given repository /// </summary> /// <param name="repoId"></param> /// <param name="filter"></param> /// <returns></returns> public static async Task <ObservableCollection <Issue> > GetAllIssuesForRepo(long repoId, RepositoryIssueRequest filter) { try { var client = await UserUtility.GetAuthenticatedClient(); var issues = await client.Issue.GetAllForRepository(repoId, filter); return(new ObservableCollection <Issue>(issues)); } catch { return(null); } }
/// <summary> /// Creates the report in markdown and returns it as a string /// </summary> /// <returns>The Markdown string</returns> public async Task <string> CreateReport() { Console.WriteLine($"{Resources.AppTitle}"); Console.WriteLine($"{Resources.Copyright}"); Console.WriteLine($"{Resources.PlsWait}"); Console.WriteLine($"{Resources.Step} 1 {Resources.of} 3 - {Resources.Headers}"); StringBuilder sb = new StringBuilder(); sb.AppendLine($"# {Resources.Specification}"); sb.AppendLine(""); sb.AppendLine($"## {Resources.SpecificationFor} {_repository} {Resources.CreatedOn} {DateTime.Now}"); sb.AppendLine(""); sb.AppendLine($"## {Resources.OverallInfo}"); sb.AppendLine(""); var client = new GitHubClient(new ProductHeaderValue("project-documentor")); var tokenAuth = new Credentials(_token); client.Credentials = tokenAuth; // var user = await client.User.Current().ConfigureAwait(true); var repo = await client.Repository.Get(_owner, _repository).ConfigureAwait(true); sb.AppendLine($"| {Resources.Item} | {Resources.Value} |"); sb.AppendLine($"| -- | -- |"); sb.AppendLine($"| {Resources.DateCreated} | {repo.CreatedAt} |"); sb.AppendLine($"| {Resources.DefaultBranch} | {repo.DefaultBranch} |"); sb.AppendLine($"| {Resources.Description} | {repo.Description} |"); sb.AppendLine($"| {Resources.FullName} | {repo.FullName} |"); sb.AppendLine($"| {Resources.URL} | {repo.GitUrl} |"); sb.AppendLine($"| {Resources.Name} | {repo.Name} |"); sb.AppendLine($"| {Resources.CurrentOpenIssues} | {repo.OpenIssuesCount} |"); sb.AppendLine($"| {Resources.LastCodeUpdate} | {repo.PushedAt} |"); sb.AppendLine($"| {Resources.Subscribers} | {repo.WatchersCount} |"); sb.AppendLine($"| {Resources.Last_Update} | {repo.UpdatedAt} |"); sb.AppendLine(""); sb.AppendLine("## Project Information"); sb.AppendLine(""); var readme = await client.Repository.Content.GetReadme(_owner, _repository).ConfigureAwait(false); sb.Append(readme.Content); sb.AppendLine(""); sb.AppendLine("## Non-Functional Requirements"); sb.AppendLine("Non functional requirements are those which affect the environment of the running application but offer no additional value to the user"); sb.AppendLine(""); sb.AppendLine("### Overview"); sb.AppendLine(""); sb.AppendLine("| Id | Title | Priority |"); sb.AppendLine("| --: | --- | --- |"); var issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("Pri1"); issuefilter.Labels.Add("NonFunctionalReq"); var issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine($"| [{item.Number}]({item.HtmlUrl}) | {item.Title} | 1 |"); } issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("Pri2"); issuefilter.Labels.Add("NonFunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine($"| [{item.Number}]({item.HtmlUrl}) | {item.Title} | 2 |"); } issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, Since = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(120)), SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("Pri3"); issuefilter.Labels.Add("NonFunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine($"| [{item.Number}]({item.HtmlUrl}) | {item.Title} | 3 |"); } //Do Pri 1 NonFunc //Do Pri 2 NonFunc //Do Pri 3 NonFunc sb.AppendLine(""); sb.AppendLine("### Details"); //Do All NonFunc with details issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("NonFunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine(""); sb.AppendLine($"### {item.Number} - {item.Title}"); sb.AppendLine(""); foreach (var lbl in item.Labels) { switch (lbl.Name) { case "Pri1": sb.AppendLine("> **Priority One**"); sb.AppendLine(""); break; case "Pri2": sb.AppendLine("> ***Priority Two***"); sb.AppendLine(""); break; case "Pri3": sb.AppendLine("> *Priority Three*"); sb.AppendLine(""); break; case "SpecRequired": sb.AppendLine("**Note:** This item still requires detailed specifications"); break; } } sb.AppendLine(""); sb.Append(item.Body); sb.AppendLine(""); } sb.AppendLine(""); sb.AppendLine("## Functional Requirements"); sb.AppendLine(""); sb.AppendLine("### Overview"); sb.AppendLine(""); sb.AppendLine("| Id | Title | Priority |"); sb.AppendLine("| --: | --- | --- |"); //Do Pri 1 Func issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("Pri1"); issuefilter.Labels.Add("FunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine($"| [{item.Number}]({item.HtmlUrl}) | {item.Title} | 1 |"); } issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("Pri2"); issuefilter.Labels.Add("FunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine($"| [{item.Number}]({item.HtmlUrl}) | {item.Title} | 2 |"); } issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("Pri3"); issuefilter.Labels.Add("FunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine($"| [{item.Number}]({item.HtmlUrl}) | {item.Title} | 3 |"); } //Do Pri 2 Func //Do Pri 3 Func sb.AppendLine(""); sb.AppendLine("### Details"); //Do All Func with details sb.AppendLine(""); issuefilter = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.All, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending }; issuefilter.Labels.Add("FunctionalReq"); issues = await client.Issue.GetAllForRepository(_owner, _repository, issuefilter).ConfigureAwait(true); foreach (var item in issues) { sb.AppendLine(""); sb.AppendLine($"### {item.Number} - {item.Title}"); sb.AppendLine(""); foreach (var lbl in item.Labels) { switch (lbl.Name) { case "Pri1": sb.AppendLine("> **Priority One**"); sb.AppendLine(""); break; case "Pri2": sb.AppendLine("> ***Priority Two***"); sb.AppendLine(""); break; case "Pri3": sb.AppendLine("> *Priority Three*"); sb.AppendLine(""); break; case "SpecRequired": sb.AppendLine("**Note:** This item still requires detailed specifications"); break; } } sb.AppendLine(""); sb.Append(item.Body); sb.AppendLine(""); } return(sb.ToString()); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = null)] HttpRequestMessage req, ILogger log) { var payloadJson = await req.Content.ReadAsStringAsync(); SimpleJsonSerializer serializer = new SimpleJsonSerializer(); IssueEventPayload issuePayload = serializer.Deserialize <IssueEventPayload>(payloadJson); if (issuePayload.Issue.User.Type.HasValue && issuePayload.Issue.User.Type.Value == AccountType.Bot) { log.LogInformation("Comment is from DarcBot, ignoring."); return(new OkObjectResult($"Ignoring DarcBot comment")); } if (issuePayload.Action != "opened" && issuePayload.Action != "reopened" && issuePayload.Action != "closed") { log.LogInformation($"Received github action '{issuePayload.Action}', nothing to do"); return(new OkObjectResult($"DarcBot has nothing to do with github issue action '{issuePayload.Action}'")); } // Determine identifiable information for triage item TriageItem triageItem = GetTriageItemProperties(issuePayload.Issue.Body); triageItem.Url = issuePayload.Issue.HtmlUrl; if (triageItem == null) { /* Item is not a triage item (does not contain identifiable information), do nothing */ log.LogInformation($"{issuePayload.Issue.Url} is not a triage type issue."); return(new OkObjectResult("No identifiable information detected")); } int.TryParse(System.Environment.GetEnvironmentVariable("AppId"), out int appId); // Create jwt token // Private key is stored in Azure Key vault by downloading the private key (pem file) from GitHub, then // using the Azure CLI to store the value in key vault. // ie: az keyvault secret set --vault-name [vault name] --name GitHubApp-DarcBot-PrivateKey --encoding base64 --file [pem key file path] GitHubAppTokenProvider gitHubTokenProvider = new GitHubAppTokenProvider(); var installationToken = gitHubTokenProvider.GetAppTokenFromEnvironmentVariableBase64(appId, "PrivateKey"); // create client using jwt as a bearer token var userAgent = new Octokit.ProductHeaderValue("DarcBot"); GitHubClient appClient = new GitHubClient(userAgent) { Credentials = new Credentials(installationToken, AuthenticationType.Bearer), }; // using the client, create an installation token AccessToken token = await appClient.GitHubApps.CreateInstallationToken(issuePayload.Installation.Id); // with the installation token, create a new GitHubClient that has the apps permissions var gitHubClient = new GitHubClient(new ProductHeaderValue("DarcBot-Installation")) { Credentials = new Credentials(token.Token) }; if (issuePayload.Action == "created" || issuePayload.Action == "opened" || issuePayload.Action == "reopened") { // First, look for duplicate issues that are open var openIssues = new RepositoryIssueRequest { Filter = IssueFilter.All, State = ItemStateFilter.Open, SortProperty = IssueSort.Created, SortDirection = SortDirection.Ascending, }; openIssues.Labels.Add(_darcBotLabelName); log.LogInformation("Getting open issues"); var issues = await gitHubClient.Issue.GetAllForRepository(issuePayload.Repository.Id, openIssues); log.LogInformation($"There are {issues.Count} open issues with the '{_darcBotLabelName}' label"); foreach (var checkissue in issues) { if (checkissue.Number != issuePayload.Issue.Number) { TriageItem issueItem = GetTriageItemProperties(checkissue.Body); if (triageItem.Equals(issueItem)) { await gitHubClient.Issue.Comment.Create(issuePayload.Repository.Id, issuePayload.Issue.Number, $"DarcBot has detected a duplicate issue.\n\nClosing as duplicate of {checkissue.HtmlUrl}\n\nFor more information see {_docLink}"); var issueUpdate = new IssueUpdate { State = ItemState.Closed, }; await gitHubClient.Issue.Update(issuePayload.Repository.Id, issuePayload.Issue.Number, issueUpdate); return(new OkObjectResult($"Resolved as duplicate of {checkissue.Number}")); } } } // No duplicates, add label and move issue to triage var issue = await gitHubClient.Issue.Get(issuePayload.Repository.Id, issuePayload.Issue.Number); var update = issue.ToUpdate(); update.AddLabel(_darcBotLabelName); await gitHubClient.Issue.Update(issuePayload.Repository.Id, issuePayload.Issue.Number, update); triageItem.UpdatedCategory = "InTriage"; } if (issuePayload.Action == "closed") { IReadOnlyList <IssueComment> comments = gitHubClient.Issue.Comment.GetAllForIssue(issuePayload.Repository.Id, issuePayload.Issue.Number).Result; foreach (var comment in comments) { // Look for category information in comment string category = GetDarcBotProperty("category", comment.Body); if (!string.IsNullOrEmpty(category)) { triageItem.UpdatedCategory = category; } } } log.LogInformation($"buildId: {triageItem.BuildId}, recordId: {triageItem.RecordId}, index: {triageItem.Index}, category: {triageItem.UpdatedCategory}, url: {triageItem.Url}"); await IngestTriageItemsIntoKusto(new[] { triageItem }, log); await gitHubClient.Issue.Comment.Create(issuePayload.Repository.Id, issuePayload.Issue.Number, $"DarcBot has updated the 'TimelineIssuesTriage' database.\n**PowerBI reports may take up to 24 hours to refresh**\n\nSee {_docLink} for more information and 'darcbot' usage."); return(new OkObjectResult("Success")); }
/// <summary> /// Gets issues for a repository. /// </summary> /// <remarks> /// http://developer.github.com/v3/issues/#list-issues-for-a-repository /// </remarks> /// <param name="owner">The owner of the repository</param> /// <param name="name">The name of the repository</param> /// <param name="request">Used to filter and sort the list of issues returned</param> public IObservable<Issue> GetAllForRepository(string owner, string name, RepositoryIssueRequest request) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(request, "request"); return GetAllForRepository(owner, name, request, ApiOptions.None); }
/// <summary> /// Gets all issues for a given repository /// </summary> /// <param name="repoId"></param> /// <param name="filter"></param> /// <returns></returns> public static async Task <ObservableCollection <Issue> > GetAllIssuesForRepo(long repoId, RepositoryIssueRequest filter, int pageIndex) { try { ApiOptions options = new ApiOptions { PageCount = 1, PageSize = 10, StartPage = pageIndex }; var issues = await GlobalHelper.GithubClient.Issue.GetAllForRepository(repoId, filter, options); return(new ObservableCollection <Issue>(issues)); } catch { return(null); } }
/// <summary> /// Gets issues for a repository. /// </summary> /// <remarks> /// http://developer.github.com/v3/issues/#list-issues-for-a-repository /// </remarks> /// <param name="owner">The owner of the repository</param> /// <param name="name">The name of the repository</param> /// <param name="request">Used to filter and sort the list of issues returned</param> /// <param name="options">Options for changing the API response</param> public IObservable<Issue> GetAllForRepository(string owner, string name, RepositoryIssueRequest request, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(request, "request"); Ensure.ArgumentNotNull(options, "options"); return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.Issues(owner, name), request.ToParametersDictionary(), AcceptHeaders.ReactionsPreview, options); }
public List <UserActivityDTO> GetUserActivity(ActivityDTO activity) { List <UserActivityDTO> user_activities = new List <UserActivityDTO>(); var user_profiles = dc.UserProfiles.Where(i => i.ProfilePropertyDefinition.PropertyName == activity.settings["Profile"].ToString()).Select(i => new { user_id = i.UserID, gitHub_login = i.PropertyValue }).ToList(); GitHubClient gitHubClient = new GitHubClient(new ProductHeaderValue("Dnn.CommunityActivity")); gitHubClient.Credentials = new Credentials(activity.settings["Credentials"].ToString()); RepositoryIssueRequest objOptions = new RepositoryIssueRequest(); objOptions.Filter = IssueFilter.All; objOptions.State = ItemStateFilter.All; List <Repository> repositories = new List <Repository>(); var totalCount = int.MaxValue; var page = 1; // get a list of all the repos matching the search criteria while (repositories.Count() < totalCount) { var request = new SearchRepositoriesRequest(activity.settings["Query"].ToString()) { Page = page, }; var result = gitHubClient.Search.SearchRepo(request).Result; totalCount = result.TotalCount; repositories.AddRange(result.Items); page++; } foreach (Repository repository in repositories) { IReadOnlyList <Issue> issues = gitHubClient.Issue.GetAllForRepository(repository.Id, objOptions).Result; if (issues.Any()) { foreach (var user_profile in user_profiles) { Nullable <DateTime> last_activity_date = dc.CommunityMetrics_UserActivities.Where(i => i.user_id == user_profile.user_id && i.activity_id == activity.id).OrderByDescending(i => i.date).Select(i => i.date).FirstOrDefault(); var recent_issues = issues .Where(i => i.User != null && i.User.Login == user_profile.gitHub_login && i.CreatedAt.Date > last_activity_date.GetValueOrDefault() && i.CreatedAt.Date < DateTime.Now.Date ); foreach (Issue issue in recent_issues) { if (issue.PullRequest == null) // exclude issues which are automatically generated for pull requests { var user_activity = user_activities.Where(i => i.user_id == user_profile.user_id && i.date == issue.CreatedAt.Date).SingleOrDefault(); if (user_activity == null) { user_activity = new UserActivityDTO() { user_id = user_profile.user_id, activity_id = activity.id, count = 0, date = issue.CreatedAt.Date }; user_activities.Add(user_activity); } user_activity.count++; } } } } } return(user_activities); }
/// <summary> /// Gets issues for a repository. /// </summary> /// <remarks> /// http://developer.github.com/v3/issues/#list-issues-for-a-repository /// </remarks> /// <param name="owner">The owner of the repository</param> /// <param name="name">The name of the repository</param> /// <param name="request">Used to filter and sort the list of issues returned</param> /// <returns></returns> public IObservable<Issue> GetForRepository(string owner, string name, RepositoryIssueRequest request) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(request, "request"); return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.Issues(owner, name), request.ToParametersDictionary()); }
// The incoming param values come (ultimately) come from parsing the incoming URL in QueryCountComponent in // count.component.ts. The URLs could include milestones and/or labels. Here, we have to translate the values // to GitHub/Octokit to get the desired result set. There are some quirks that need clarification below... private async Task <IReadOnlyList <Issue> > GetIssuesAsync(string owner, string repository, string milestone, string labels, string excludedLabels) { // First, for milestone. The URL handled by the Angular app might not have a milestone query parameter so it // would look something like this: // https://<host>/count/nuget/home?label=VS1ES // In that case, the angular app will set the milestone to "undefined" before calling this service, which will // receive a URL like: // https://<host>/api/CountByMilestone/nuget/home/undefined/VS1ES // Map "undefined" and null/whitespace to `null` in the Octokit issue request. This tells Octokit "don't // consider milestones in this query." Then Octokit returns issues regardless of their milestone setting - // including issues with _no_ milestone setting. The URL could have "milestone='*'" - the milestone parameter // will pass that value. GitHub/Octokit treats milestone = '*' as "any _set_ milestone." So Octokit would // return all issues that have any milestone setting of any value - as long as the issue has one is set. // However, with '*' it won't return issues that have NO milestone setting. Finally, if the URL includes a // valid milestone (eg "milestone=15.8"), translate that string value into the corresponding milestone ID and // put that in the issue request... if (string.IsNullOrWhiteSpace(milestone) || milestone == "undefined") { milestone = null; } else if (milestone == "any" || milestone == "*") { milestone = "*"; } else if (milestone != "none") { // This throws a KeyNotFoundException if the incoming milestone value doesn't exist in the repo's collection // of milestone values. Catch it in the calling function... var milestonesClient = new MilestonesClient(new ApiConnection(_gitHubClient.Connection)); var milestoneRequest = new MilestoneRequest { State = ItemStateFilter.Open }; var milestones = await milestonesClient.GetAllForRepository(owner, repository, milestoneRequest); var milestonesByName = milestones.ToDictionary(m => m.Title, m => m.Number); milestone = milestonesByName[milestone].ToString(); } var issueRequest = new RepositoryIssueRequest { Milestone = milestone, State = ItemStateFilter.Open, }; // Second, for labels. In GitHub, issues can have zero or more label values, and the incoming URL could specify a // query for multiple values. Those URL values are passed to this function as a string of comma separated values. // No values in the URL results in labels param value of "undefined" (same as above for milestone); A URL value of // "label=test&label=VS1ES" results in "test,VS1ES" --> split those and add each value to the issue request // Labels collection... if (!string.IsNullOrWhiteSpace(labels) && !(labels == "undefined")) { var labelvalues = labels.Split(','); foreach (var label in labelvalues) { issueRequest.Labels.Add(label); } } // This could throw an ApiValidationException if the milestone doesn't exist in the repo. // Catch it in the calling function... var issues = await _gitHubClient.Issue.GetAllForRepository(owner, repository, issueRequest); issues = issues.Where(i => i.PullRequest == null).ToList(); // We now need to exclude all the issues that have labels that should be excluded if (!String.IsNullOrEmpty(excludedLabels) && !(excludedLabels == "undefined")) { var filteredIssues = new List <Issue>(); var excludedLabelValues = excludedLabels.Split(','); foreach (Issue i in issues) { bool skip = false; foreach (Label l in i.Labels) { if (excludedLabelValues.Contains(l.Name)) { skip = true; } } if (!skip) { filteredIssues.Add(i); } } issues = filteredIssues; } return(issues); }