mirror of https://github.com/microsoft/autogen.git
152 lines
6.6 KiB
C#
152 lines
6.6 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// GithubWebHookProcessor.cs
|
|
|
|
using System.Globalization;
|
|
using Google.Protobuf;
|
|
using Microsoft.AutoGen.Contracts;
|
|
using Microsoft.AutoGen.Core;
|
|
using Octokit.Webhooks;
|
|
using Octokit.Webhooks.Events;
|
|
using Octokit.Webhooks.Events.IssueComment;
|
|
using Octokit.Webhooks.Events.Issues;
|
|
using Octokit.Webhooks.Models;
|
|
|
|
namespace DevTeam.Backend.Services;
|
|
|
|
public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logger, Client client) : WebhookEventProcessor
|
|
{
|
|
private readonly ILogger<GithubWebHookProcessor> _logger = logger;
|
|
private readonly Client _client = client;
|
|
|
|
protected override async Task ProcessIssuesWebhookAsync(WebhookHeaders headers, IssuesEvent issuesEvent, IssuesAction action)
|
|
{
|
|
try
|
|
{
|
|
ArgumentNullException.ThrowIfNull(headers, nameof(headers));
|
|
ArgumentNullException.ThrowIfNull(issuesEvent, nameof(issuesEvent));
|
|
ArgumentNullException.ThrowIfNull(action, nameof(action));
|
|
|
|
_logger.LogInformation("Processing issue event");
|
|
var org = issuesEvent.Repository?.Owner.Login ?? throw new InvalidOperationException("Repository owner login is null");
|
|
var repo = issuesEvent.Repository?.Name ?? throw new InvalidOperationException("Repository name is null");
|
|
var issueNumber = issuesEvent.Issue?.Number ?? throw new InvalidOperationException("Issue number is null");
|
|
var input = issuesEvent.Issue?.Body ?? string.Empty;
|
|
// Assumes the label follows the following convention: Skill.Function example: PM.Readme
|
|
// Also, we've introduced the Parent label, that ties the sub-issue with the parent issue
|
|
var labels = issuesEvent.Issue?.Labels
|
|
.Select(l => l.Name.Split('.'))
|
|
.Where(parts => parts.Length == 2)
|
|
.ToDictionary(parts => parts[0], parts => parts[1]);
|
|
if (labels == null || labels.Count == 0)
|
|
{
|
|
_logger.LogWarning("No labels found in issue. Skipping processing.");
|
|
return;
|
|
}
|
|
|
|
long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value) : null;
|
|
var skillName = labels.Keys.Where(k => k != "Parent").FirstOrDefault();
|
|
|
|
if (skillName == null)
|
|
{
|
|
_logger.LogWarning("No skill name found in issue. Skipping processing.");
|
|
return;
|
|
}
|
|
|
|
var suffix = $"{org}-{repo}";
|
|
if (issuesEvent.Action == IssuesAction.Opened)
|
|
{
|
|
_logger.LogInformation("Processing HandleNewAsk");
|
|
await HandleNewAsk(issueNumber, skillName, labels[skillName], suffix, input, org, repo);
|
|
}
|
|
else if (issuesEvent.Action == IssuesAction.Closed && issuesEvent.Issue?.User.Type.Value == UserType.Bot)
|
|
{
|
|
_logger.LogInformation("Processing HandleClosingIssue");
|
|
await HandleClosingIssue(issueNumber, skillName, labels[skillName], suffix);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Processing issue event");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
protected override async Task ProcessIssueCommentWebhookAsync(
|
|
WebhookHeaders headers,
|
|
IssueCommentEvent issueCommentEvent,
|
|
IssueCommentAction action)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(headers);
|
|
ArgumentNullException.ThrowIfNull(issueCommentEvent);
|
|
ArgumentNullException.ThrowIfNull(action);
|
|
|
|
try
|
|
{
|
|
_logger.LogInformation("Processing issue comment event");
|
|
var org = issueCommentEvent.Repository!.Owner.Login;
|
|
var repo = issueCommentEvent.Repository.Name;
|
|
var issueNumber = issueCommentEvent.Issue.Number;
|
|
var input = issueCommentEvent.Comment.Body;
|
|
// Assumes the label follows the following convention: Skill.Function example: PM.Readme
|
|
var labels = issueCommentEvent.Issue.Labels
|
|
.Select(l => l.Name.Split('.'))
|
|
.Where(parts => parts.Length == 2)
|
|
.ToDictionary(parts => parts[0], parts => parts[1]);
|
|
var skillName = labels.Keys.First(k => k != "Parent");
|
|
long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value, CultureInfo.InvariantCulture) : null;
|
|
var suffix = $"{org}-{repo}";
|
|
|
|
// we only respond to non-bot comments
|
|
if (issueCommentEvent.Sender!.Type.Value != UserType.Bot)
|
|
{
|
|
await HandleNewAsk(issueNumber, skillName, labels[skillName], suffix, input, org, repo);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Processing issue comment event");
|
|
throw;
|
|
}
|
|
|
|
}
|
|
|
|
private async Task HandleClosingIssue(long issueNumber, string skillName, string functionName, string suffix)
|
|
{
|
|
var subject = suffix + issueNumber.ToString();
|
|
|
|
IMessage evt = (skillName, functionName) switch
|
|
{
|
|
("PM", "Readme") => new ReadmeChainClosed { },
|
|
("DevLead", "Plan") => new DevPlanChainClosed { },
|
|
("Developer", "Implement") => new CodeChainClosed { },
|
|
_ => new CloudEvent() // TODO: default event
|
|
};
|
|
|
|
await _client.PublishMessageAsync(evt, Consts.TopicName, subject);
|
|
}
|
|
|
|
private async Task HandleNewAsk(long issueNumber, string skillName, string functionName, string suffix, string input, string org, string repo)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("Handling new ask");
|
|
var subject = suffix + issueNumber.ToString();
|
|
|
|
IMessage evt = (skillName, functionName) switch
|
|
{
|
|
("Do", "It") => new NewAsk { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
|
("PM", "Readme") => new ReadmeRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
|
("DevLead", "Plan") => new DevPlanRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
|
("Developer", "Implement") => new CodeGenerationRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
|
|
_ => new CloudEvent()
|
|
};
|
|
await _client.PublishMessageAsync(evt, Consts.TopicName, subject);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Handling new ask");
|
|
throw;
|
|
}
|
|
}
|
|
}
|