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
61 changes: 61 additions & 0 deletions csharp/Interfaces/IPrivateMessageTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Threading.Tasks;

namespace Interfaces
{
/// <summary>
/// <para>
/// Defines a trigger that supports private messaging to users instead of public issue comments.
/// </para>
/// <para></para>
/// </summary>
public interface IPrivateMessageTrigger<TContext> : ITrigger<TContext>
{
/// <summary>
/// <para>
/// Gets the user login to send private message to.
/// </para>
/// <para></para>
/// </summary>
/// <param name="context">
/// <para>The context.</para>
/// <para></para>
/// </param>
/// <returns>
/// <para>The user login string</para>
/// <para></para>
/// </returns>
string GetTargetUserLogin(TContext context);

/// <summary>
/// <para>
/// Gets the subject for the private message.
/// </para>
/// <para></para>
/// </summary>
/// <param name="context">
/// <para>The context.</para>
/// <para></para>
/// </param>
/// <returns>
/// <para>The message subject</para>
/// <para></para>
/// </returns>
string GetMessageSubject(TContext context);

/// <summary>
/// <para>
/// Gets the private message content.
/// </para>
/// <para></para>
/// </summary>
/// <param name="context">
/// <para>The context.</para>
/// <para></para>
/// </param>
/// <returns>
/// <para>The private message content</para>
/// <para></para>
/// </returns>
Task<string> GetPrivateMessageContent(TContext context);
}
}
19 changes: 18 additions & 1 deletion csharp/Platform.Bot/Trackers/IssueTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,27 @@ public async Task Start(CancellationToken cancellationToken)
}
if (await trigger.Condition(issue))
{
await trigger.Action(issue);
if (trigger is IPrivateMessageTrigger<Issue> privateMessageTrigger)
{
await HandlePrivateMessageTrigger(issue, privateMessageTrigger);
}
else
{
await trigger.Action(issue);
}
}
}
}
}

private async Task HandlePrivateMessageTrigger(Issue issue, IPrivateMessageTrigger<Issue> trigger)
{
var targetUser = trigger.GetTargetUserLogin(issue);
var subject = trigger.GetMessageSubject(issue);
var messageContent = await trigger.GetPrivateMessageContent(issue);

var privateMessageIssue = await _storage.SendPrivateMessage(targetUser, subject, messageContent);
await _storage.CreateMinimalIssueComment(issue.Repository.Id, issue.Number, targetUser, subject, privateMessageIssue);
}
}
}
27 changes: 21 additions & 6 deletions csharp/Platform.Bot/Triggers/LastCommitActivityTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace Platform.Bot.Triggers
{
using TContext = Issue;
internal class LastCommitActivityTrigger : ITrigger<TContext>
internal class LastCommitActivityTrigger : IPrivateMessageTrigger<TContext>
{
private readonly GitHubStorage _githubStorage;

Expand All @@ -25,14 +25,30 @@ public async Task<bool> Condition(TContext issue)
}

public async Task Action(TContext issue)
{
Console.WriteLine($"Issue {issue.Title} is processed: {issue.HtmlUrl}");
await _githubStorage.Client.Issue.Update(issue.Repository.Owner.Login, issue.Repository.Name, issue.Number, new IssueUpdate() { State = ItemState.Closed });
}

public string GetTargetUserLogin(TContext issue)
{
return issue.User.Login;
}

public string GetMessageSubject(TContext issue)
{
return "Last 3 Months Commit Activity Report";
}

public async Task<string> GetPrivateMessageContent(TContext issue)
{
var organizationName = issue.Repository.Owner.Login;

var allMembers = await _githubStorage.GetAllOrganizationMembers(organizationName);
var allRepositories = await _githubStorage.GetAllRepositories(organizationName);
if (!allRepositories.Any())
{
return;
return "No repositories found in the organization.";
}

var commitsPerUserInLast3Months = await allRepositories
Expand All @@ -54,16 +70,15 @@ public async Task Action(TContext issue)
}
return dictionary;
});

StringBuilder messageSb = new();
var ShortSummaryMessage = GetShortSummaryMessage(commitsPerUserInLast3Months.Select(pair => pair.Key).ToList());
messageSb.Append(ShortSummaryMessage);
messageSb.AppendLine("---");
var detailedMessage = await GetDetailedMessage(commitsPerUserInLast3Months);
messageSb.Append(detailedMessage);
var message = messageSb.ToString();
await _githubStorage.CreateIssueComment(issue.Repository.Id, issue.Number, message);
Console.WriteLine($"Issue {issue.Title} is processed: {issue.HtmlUrl}");
await _githubStorage.Client.Issue.Update(issue.Repository.Owner.Login, issue.Repository.Name, issue.Number, new IssueUpdate() { State = ItemState.Closed });

return messageSb.ToString();
}

private string GetShortSummaryMessage(List<User> users)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Platform.Bot.Triggers
/// <para></para>
/// </summary>
/// <seealso cref="ITrigger{Issue}"/>
internal class OrganizationLastMonthActivityTrigger : ITrigger<TContext>
internal class OrganizationLastMonthActivityTrigger : IPrivateMessageTrigger<TContext>
{
private readonly GitHubStorage _storage;
private readonly Parser _parser = new();
Expand Down Expand Up @@ -62,11 +62,24 @@ internal class OrganizationLastMonthActivityTrigger : ITrigger<TContext>
/// </param>
public async Task Action(TContext context)
{
var issueService = _storage.Client.Issue;
_storage.CloseIssue(context);
}

public string GetTargetUserLogin(TContext context)
{
return context.User.Login;
}

public string GetMessageSubject(TContext context)
{
return "Organization Last Month Activity Report";
}

public async Task<string> GetPrivateMessageContent(TContext context)
{
var owner = context.Repository.Owner.Login;
var activeUsersString = string.Join("\n", GetActiveUsers(GetIgnoredRepositories(_parser.Parse(context.Body)), owner));
issueService.Comment.Create(owner, context.Repository.Name, context.Number, activeUsersString);
_storage.CloseIssue(context);
return $"# Organization Last Month Activity\n\nActive users in the last month:\n\n{activeUsersString}";
}

/// <summary>
Expand Down
27 changes: 27 additions & 0 deletions csharp/Storage/RemoteStorage/GitHubStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,33 @@ public Task<IssueComment> CreateIssueComment(long repositoryId, int issueNumber,
return Client.Issue.Comment.Create(repositoryId, issueNumber, message);
}

public async Task<Issue> SendPrivateMessage(string userLogin, string subject, string message, string botCommunicationRepoName = "bot-communications")
{
try
{
var repo = await Client.Repository.Get(Owner, botCommunicationRepoName);
var issueTitle = $"Message for @{userLogin}: {subject}";
var issueBody = $"@{userLogin}\n\n{message}";

var newIssue = new NewIssue(issueTitle)
{
Body = issueBody
};

return await Client.Issue.Create(repo.Id, newIssue);
}
catch (NotFoundException)
{
throw new InvalidOperationException($"Bot communication repository '{botCommunicationRepoName}' not found. Please create this repository first.");
}
}

public async Task<IssueComment> CreateMinimalIssueComment(long repositoryId, int issueNumber, string userLogin, string subject, Issue privateMessageIssue)
{
var message = $"@{userLogin} Bot message sent privately: [{subject}]({privateMessageIssue.HtmlUrl})";
return await Client.Issue.Comment.Create(repositoryId, issueNumber, message);
}

#endregion

#region Branch
Expand Down
50 changes: 50 additions & 0 deletions examples/bot-communication-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Bot Communication Setup

This example shows how to set up the private messaging system for the bot.

## Prerequisites

1. Create a private repository called `bot-communications` in your organization
2. Make sure the bot has access to this repository

## How it works

When a trigger implements `IPrivateMessageTrigger<Issue>` instead of `ITrigger<Issue>`:

1. The bot generates the message content privately
2. Creates an issue in the `bot-communications` repository with the message
3. Mentions the target user in that issue (so they get email notification)
4. Posts a minimal comment in the original issue with a link to the private message

## Example Usage

```csharp
// Old way - creates big public comments
internal class MyTrigger : ITrigger<Issue>
{
public async Task Action(Issue issue)
{
await _github.CreateIssueComment(issue.Repository.Id, issue.Number, "Very long message...");
}
}

// New way - sends private messages
internal class MyTrigger : IPrivateMessageTrigger<Issue>
{
public string GetTargetUserLogin(Issue issue) => issue.User.Login;
public string GetMessageSubject(Issue issue) => "Report Title";
public async Task<string> GetPrivateMessageContent(Issue issue) => "Very long message...";
public async Task Action(Issue issue)
{
// Only handle issue closing, messaging is automatic
await _github.Client.Issue.Update(issue.Repository.Owner.Login, issue.Repository.Name, issue.Number, new IssueUpdate() { State = ItemState.Closed });
}
}
```

## Benefits

- βœ… Reduces clutter in main issue threads
- βœ… Users still get notified via email mentions
- βœ… Messages are preserved in a dedicated space
- βœ… Original issues stay clean and readable
Loading