diff --git a/csharp/Platform.Bot/Program.cs b/csharp/Platform.Bot/Program.cs index 521a6b95..6628ec20 100644 --- a/csharp/Platform.Bot/Program.cs +++ b/csharp/Platform.Bot/Program.cs @@ -95,7 +95,7 @@ private static async Task Main(string[] args) var dbContext = new FileStorage(databaseFilePath?.FullName ?? new TemporaryFile().Filename); Console.WriteLine($"Bot has been started. {Environment.NewLine}Press CTRL+C to close"); var githubStorage = new GitHubStorage(githubUserName, githubApiToken, githubApplicationName); - var issueTracker = new IssueTracker(githubStorage, new HelloWorldTrigger(githubStorage, dbContext, fileSetName), new OrganizationLastMonthActivityTrigger(githubStorage), new LastCommitActivityTrigger(githubStorage), new AdminAuthorIssueTriggerDecorator(new ProtectDefaultBranchTrigger(githubStorage), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationRepositoriesDefaultBranchTrigger(githubStorage, dbContext), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationPullRequestsBaseBranchTrigger(githubStorage, dbContext), githubStorage)); + var issueTracker = new IssueTracker(githubStorage, new HelloWorldTrigger(githubStorage, dbContext, fileSetName), new CallUsersByLanguageTrigger(githubStorage), new OrganizationLastMonthActivityTrigger(githubStorage), new LastCommitActivityTrigger(githubStorage), new AdminAuthorIssueTriggerDecorator(new ProtectDefaultBranchTrigger(githubStorage), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationRepositoriesDefaultBranchTrigger(githubStorage, dbContext), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationPullRequestsBaseBranchTrigger(githubStorage, dbContext), githubStorage)); var pullRequenstTracker = new PullRequestTracker(githubStorage, new MergeDependabotBumpsTrigger(githubStorage)); var timestampTracker = new DateTimeTracker(githubStorage, new CreateAndSaveOrganizationRepositoriesMigrationTrigger(githubStorage, dbContext, Path.Combine(Directory.GetCurrentDirectory(), "/github-migrations"))); var cancellation = new CancellationTokenSource(); diff --git a/csharp/Platform.Bot/Triggers/CallUsersByLanguageTrigger.cs b/csharp/Platform.Bot/Triggers/CallUsersByLanguageTrigger.cs new file mode 100644 index 00000000..be6b12d3 --- /dev/null +++ b/csharp/Platform.Bot/Triggers/CallUsersByLanguageTrigger.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Interfaces; +using Octokit; +using Storage.Remote.GitHub; + +namespace Platform.Bot.Triggers +{ + using TContext = Issue; + + /// + /// + /// Represents the call users by language trigger. + /// + /// + /// + /// + internal class CallUsersByLanguageTrigger : ITrigger + { + private readonly GitHubStorage _storage; + private readonly Dictionary _languageMap; + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A git hub api. + /// + /// + public CallUsersByLanguageTrigger(GitHubStorage storage) + { + _storage = storage; + _languageMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"C++", "C++"}, + {"CPP", "C++"}, + {"C#", "C#"}, + {"CSHARP", "C#"}, + {"C", "C"}, + {"Java", "Java"}, + {"JavaScript", "JavaScript"}, + {"JS", "JavaScript"}, + {"Python", "Python"}, + {"PY", "Python"}, + {"Ruby", "Ruby"}, + {"RB", "Ruby"}, + {"Go", "Go"}, + {"Golang", "Go"}, + {"PHP", "PHP"}, + {"Swift", "Swift"}, + {"Kotlin", "Kotlin"}, + {"Rust", "Rust"}, + {"TypeScript", "TypeScript"}, + {"TS", "TypeScript"}, + {"Scala", "Scala"}, + {"R", "R"}, + {"Dart", "Dart"}, + {"Lua", "Lua"}, + {"Perl", "Perl"}, + {"Haskell", "Haskell"}, + {"Clojure", "Clojure"}, + {"F#", "F#"}, + {"FSHARP", "F#"}, + {"Objective-C", "Objective-C"}, + {"Shell", "Shell"}, + {"PowerShell", "PowerShell"}, + {"HTML", "HTML"}, + {"CSS", "CSS"}, + {"Vim", "Vim script"}, + {"Assembly", "Assembly"}, + {"MATLAB", "MATLAB"}, + {"Groovy", "Groovy"}, + {"Elixir", "Elixir"}, + {"Erlang", "Erlang"}, + {"Julia", "Julia"}, + {"Nim", "Nim"}, + {"Crystal", "Crystal"} + }; + } + + /// + /// + /// Actions the context. + /// + /// + /// + /// + /// The context. + /// + /// + public async Task Action(TContext context) + { + var languageText = context.Body.Trim().Substring(1).Trim(); + + if (!_languageMap.TryGetValue(languageText, out var language)) + { + await _storage.CreateIssueComment(context.Repository.Id, context.Number, + $"Sorry, I don't recognize the programming language '{languageText}'. " + + $"Supported languages include: {string.Join(", ", _languageMap.Keys.Take(10))} and more."); + return; + } + + try + { + // Search for repositories with the specified language using raw query + var query = $"language:{language}"; + var repositorySearchRequest = new SearchRepositoriesRequest(query) + { + SortField = RepoSearchSort.Stars, + Order = SortDirection.Descending, + PerPage = 100 + }; + + var repositoryResults = await _storage.Client.Search.SearchRepo(repositorySearchRequest); + + if (!repositoryResults.Items.Any()) + { + await _storage.CreateIssueComment(context.Repository.Id, context.Number, + $"No repositories found with {languageText} as the primary language."); + return; + } + + // Get unique users from these repositories + var uniqueUsers = repositoryResults.Items + .Where(repo => repo.Owner != null) + .GroupBy(repo => repo.Owner.Login) + .Select(g => g.First().Owner) + .Take(10) + .ToList(); + + if (!uniqueUsers.Any()) + { + await _storage.CreateIssueComment(context.Repository.Id, context.Number, + $"No users found with {languageText} repositories."); + return; + } + + var userMentions = uniqueUsers + .Select(user => $"@{user.Login}") + .ToList(); + + var message = $"Calling all {languageText} developers! 👋\n\n{string.Join(" ", userMentions)}\n\n" + + $"Found {uniqueUsers.Count} top {languageText} developers based on repository stars."; + + await _storage.CreateIssueComment(context.Repository.Id, context.Number, message); + } + catch (Exception ex) + { + await _storage.CreateIssueComment(context.Repository.Id, context.Number, + $"Sorry, I encountered an error while searching for {languageText} developers: {ex.Message}"); + } + } + + /// + /// + /// Determines whether this instance condition. + /// + /// + /// + /// + /// The context. + /// + /// + /// + /// The bool + /// + /// + public async Task Condition(TContext context) + { + return !string.IsNullOrEmpty(context.Body) && + context.Body.Trim().StartsWith("!") && + context.Body.Trim().Length > 1 && + context.Body.Trim().Split(' ').Length >= 1; + } + } +} \ No newline at end of file diff --git a/experiments/test_trigger.cs b/experiments/test_trigger.cs new file mode 100644 index 00000000..eb006cc6 --- /dev/null +++ b/experiments/test_trigger.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using Octokit; +using Platform.Bot.Triggers; + +namespace TestTrigger +{ + class Program + { + static async Task Main(string[] args) + { + // Test trigger condition logic without actual GitHub API calls + var trigger = new CallUsersByLanguageTrigger(null); + + // Test cases + var testIssues = new[] + { + new { Body = "! C++", Expected = true, Description = "Valid C++ call" }, + new { Body = "! Python", Expected = true, Description = "Valid Python call" }, + new { Body = "!JavaScript", Expected = true, Description = "Valid JavaScript call without space" }, + new { Body = "Hello world", Expected = false, Description = "Regular issue body" }, + new { Body = "!", Expected = false, Description = "Just exclamation mark" }, + new { Body = "", Expected = false, Description = "Empty body" }, + }; + + Console.WriteLine("Testing CallUsersByLanguageTrigger conditions:"); + Console.WriteLine("=" + new string('=', 50)); + + foreach (var test in testIssues) + { + var mockIssue = CreateMockIssue(test.Body); + var result = await trigger.Condition(mockIssue); + var status = result == test.Expected ? "✓ PASS" : "✗ FAIL"; + Console.WriteLine($"{status} {test.Description}: '{test.Body}' -> {result} (expected: {test.Expected})"); + } + } + + static Issue CreateMockIssue(string body) + { + // Create a basic mock issue with the given body + // Note: This is a simplified mock for testing purposes + return new Issue( + url: "https://api.github.com/repos/test/test/issues/1", + htmlUrl: "https://github.com/test/test/issues/1", + commentsUrl: "https://api.github.com/repos/test/test/issues/1/comments", + eventsUrl: "https://api.github.com/repos/test/test/issues/1/events", + number: 1, + state: ItemState.Open, + title: "Test Issue", + body: body, + user: null, + labels: null, + assignee: null, + assignees: null, + milestone: null, + comments: 0, + pullRequest: null, + closedAt: null, + createdAt: DateTimeOffset.Now, + updatedAt: DateTimeOffset.Now, + closedBy: null, + nodeId: "test", + locked: false, + repository: null, + reactions: null, + activeLockReason: null, + stateReason: null + ); + } + } +} \ No newline at end of file