Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion csharp/Platform.Bot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private static async Task<int> 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();
Expand Down
181 changes: 181 additions & 0 deletions csharp/Platform.Bot/Triggers/CallUsersByLanguageTrigger.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <para>
/// Represents the call users by language trigger.
/// </para>
/// <para></para>
/// </summary>
/// <seealso cref="ITrigger{TContext}"/>
internal class CallUsersByLanguageTrigger : ITrigger<TContext>
{
private readonly GitHubStorage _storage;
private readonly Dictionary<string, string> _languageMap;

/// <summary>
/// <para>
/// Initializes a new <see cref="CallUsersByLanguageTrigger"/> instance.
/// </para>
/// <para></para>
/// </summary>
/// <param name="storage">
/// <para>A git hub api.</para>
/// <para></para>
/// </param>
public CallUsersByLanguageTrigger(GitHubStorage storage)
{
_storage = storage;
_languageMap = new Dictionary<string, string>(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"}
};
}

/// <summary>
/// <para>
/// Actions the context.
/// </para>
/// <para></para>
/// </summary>
/// <param name="context">
/// <para>The context.</para>
/// <para></para>
/// </param>
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}");
}
}

/// <summary>
/// <para>
/// Determines whether this instance condition.
/// </para>
/// <para></para>
/// </summary>
/// <param name="context">
/// <para>The context.</para>
/// <para></para>
/// </param>
/// <returns>
/// <para>The bool</para>
/// <para></para>
/// </returns>
public async Task<bool> Condition(TContext context)
{
return !string.IsNullOrEmpty(context.Body) &&
context.Body.Trim().StartsWith("!") &&
context.Body.Trim().Length > 1 &&
context.Body.Trim().Split(' ').Length >= 1;
}
}
}
71 changes: 71 additions & 0 deletions experiments/test_trigger.cs
Original file line number Diff line number Diff line change
@@ -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
);
}
}
}
Loading