diff --git a/Issueneter.Host/Modules/ScanModule.cs b/Issueneter.Host/Modules/ScanModule.cs new file mode 100644 index 0000000..853de6d --- /dev/null +++ b/Issueneter.Host/Modules/ScanModule.cs @@ -0,0 +1,36 @@ +using Issueneter.Domain.Models; +using Issueneter.Host.Composition; +using Issueneter.Runner; +using Issueneter.Telegram; +using Issueneter.Telegram.Formatters; + +namespace Issueneter.Host.Modules; + +public static class ScanModule +{ + public static IServiceCollection AddScanModule(this IServiceCollection services, IHostEnvironment env, IConfigurationRoot configuration) => + services + .AddSingleton(GetIssueFormatter) + .AddSingleton(GetPullRequestFormatter) + .AddSingleton(); + + private static IMessageFormatter GetIssueFormatter(IServiceProvider serviceProvider) + { + var options = serviceProvider.GetRequiredOptions(); + + if (options.IssueMessageTemplate is not null) + return new ConfigurableMessageFormatter(options.IssueMessageTemplate); + + return new IssueMessageFormatter(); + } + + private static IMessageFormatter GetPullRequestFormatter(IServiceProvider serviceProvider) + { + var options = serviceProvider.GetRequiredOptions(); + + if (options.PullRequestMessageTemplate is not null) + return new ConfigurableMessageFormatter(options.PullRequestMessageTemplate); + + return new PullRequestMessageFormatter(); + } +} \ No newline at end of file diff --git a/Issueneter.Host/Program.cs b/Issueneter.Host/Program.cs index 13bc0b5..9b39c6c 100644 --- a/Issueneter.Host/Program.cs +++ b/Issueneter.Host/Program.cs @@ -17,7 +17,7 @@ .AddHangfireModule(env, config) .AddSwaggerModule(env, config) .AddTelegramModule(env, config) - .AddSingleton(); + .AddScanModule(env, config); var app = builder.Build(); diff --git a/Issueneter.Host/appsettings.Development.json b/Issueneter.Host/appsettings.Development.json index 6f3414e..872e5a3 100644 --- a/Issueneter.Host/appsettings.Development.json +++ b/Issueneter.Host/appsettings.Development.json @@ -6,7 +6,9 @@ "Token" : null }, - "TelegramOptions" : { - "Token": null + "TelegramOptions": { + "Token": null, + "IssueMessageTemplate": "изи [ишуя]({value.Url})", + "PullRequestMessageTemplate": "[пр]({entity.Url})" } } \ No newline at end of file diff --git a/Issueneter.Runner/ScanRunner.cs b/Issueneter.Runner/ScanRunner.cs index d8845fd..335592a 100644 --- a/Issueneter.Runner/ScanRunner.cs +++ b/Issueneter.Runner/ScanRunner.cs @@ -1,12 +1,9 @@ -using Issueneter.Domain; -using Issueneter.Domain.Models; -using Issueneter.Filters; +using Issueneter.Domain.Models; using Issueneter.Github; using Issueneter.Json; using Issueneter.Mappings; using Issueneter.Persistence; using Issueneter.Telegram; -using Issueneter.Telegram.Formatters; using Newtonsoft.Json; namespace Issueneter.Runner; @@ -16,12 +13,16 @@ public class ScanRunner private readonly TelegramSender _sender; private readonly GithubApiService _github; private readonly ScanStorage _storage; - - public ScanRunner(TelegramSender sender, GithubApiService github, ScanStorage storage) + private readonly IMessageFormatter _issueFormatter; + private readonly IMessageFormatter _pullRequestFormatter; + + public ScanRunner(TelegramSender sender, GithubApiService github, ScanStorage storage, IMessageFormatter issueMessageIssueFormatter, IMessageFormatter pullRequestMessageFormatter) { _sender = sender; _github = github; _storage = storage; + _issueFormatter = issueMessageIssueFormatter; + _pullRequestFormatter = pullRequestMessageFormatter; } public async Task Run(long scanId) @@ -34,21 +35,19 @@ public async Task Run(long scanId) var source = new ActivitySource(scan.Owner, scan.Repo); if (scan.ScanType == ScanType.Issue) { - var formatter = new IssueMessageFormatter(); var issues = await _github.GetIssues(DateTimeOffset.Now - TimeSpan.FromMinutes(30), source); var filters = JsonConvert.DeserializeObject(scan.Filters); var rootFilter = IssueneterJsonSerializer.Deserialize(filters); var results = issues.Where(k => rootFilter.Apply(k)).ToArray(); - await _sender.SendResults(scan.ChatId, formatter, results); + await _sender.SendResults(scan.ChatId, _issueFormatter, results); } else { var issues = await _github.GetPullRequests(DateTimeOffset.Now - TimeSpan.FromMinutes(30), source); var rootFilter = IssueneterJsonSerializer.Deserialize(scan.Filters); var results = issues.Where(k => rootFilter.Apply(k)).ToArray(); - var formatter = new PullRequestMessageFormatter(); - await _sender.SendResults(scan.ChatId, formatter, results); + await _sender.SendResults(scan.ChatId, _pullRequestFormatter, results); } } } \ No newline at end of file diff --git a/Issueneter.Telegram/Formatters/ConfigurableMessageFormatter.cs b/Issueneter.Telegram/Formatters/ConfigurableMessageFormatter.cs new file mode 100644 index 0000000..3672264 --- /dev/null +++ b/Issueneter.Telegram/Formatters/ConfigurableMessageFormatter.cs @@ -0,0 +1,24 @@ +using Issueneter.Annotation; +using Issueneter.Mappings; + +namespace Issueneter.Telegram.Formatters; + +public class ConfigurableMessageFormatter : IMessageFormatter where T : IFilterable +{ + private readonly string _template; + + public ConfigurableMessageFormatter(string template) + { + _template = template; + } + + public string ToMessage(T entity) + { + var result = _template; + + foreach (string property in ModelsInfo.AvailableScanSources[T.ScanType]) + result = result.Replace($"{{value.{property}}}", entity.GetProperty(property)); + + return result; + } +} \ No newline at end of file diff --git a/Issueneter.Telegram/TelegramOptions.cs b/Issueneter.Telegram/TelegramOptions.cs index 5bfff1e..2d84201 100644 --- a/Issueneter.Telegram/TelegramOptions.cs +++ b/Issueneter.Telegram/TelegramOptions.cs @@ -3,4 +3,6 @@ public class TelegramOptions { public string? Token { get; init; } + public string? IssueMessageTemplate { get; init; } + public string? PullRequestMessageTemplate { get; init; } } \ No newline at end of file diff --git a/Issueneter.Tests/GlobalUsings.cs b/Issueneter.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/Issueneter.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Issueneter.Tests/IssueConfigurableMessageFormatterTests.cs b/Issueneter.Tests/IssueConfigurableMessageFormatterTests.cs new file mode 100644 index 0000000..c7b95c1 --- /dev/null +++ b/Issueneter.Tests/IssueConfigurableMessageFormatterTests.cs @@ -0,0 +1,37 @@ +using System.Collections; +using Issueneter.Domain.Models; +using Issueneter.Domain.Utility; +using Issueneter.Telegram.Formatters; + +namespace Issueneter.Tests +{ + public class IssueConfigurableMessageFormatterTests + { + private static IEnumerable GetTestCases() + { + var dummyRefValue = new Ref>(() => Task.FromResult(new List())); + + var issue = new Issue( + "Issue title", + "Issue author", + "Url", + IssueState.Opened, + Array.Empty(), + dummyRefValue); + + yield return new TestCaseData("Message template", issue, "Message template"); + yield return new TestCaseData("Issue {value.Title}", issue, $"Issue {issue.Title}"); + yield return new TestCaseData("Issue {value.Url}", issue, $"Issue {issue.Url}"); + } + + [TestCaseSource(nameof(GetTestCases))] + public void Formatter_ReturnCorrectString(string template, Issue issue, string expected) + { + var formatter = new ConfigurableMessageFormatter(template); + + var actual = formatter.ToMessage(issue); + + Assert.That(actual, Is.EqualTo(expected)); + } + } +} \ No newline at end of file diff --git a/Issueneter.Tests/Issueneter.Tests.csproj b/Issueneter.Tests/Issueneter.Tests.csproj new file mode 100644 index 0000000..f063e06 --- /dev/null +++ b/Issueneter.Tests/Issueneter.Tests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/Issueneter.Tests/PullRequestConfigurableMessageFormatterTests.cs b/Issueneter.Tests/PullRequestConfigurableMessageFormatterTests.cs new file mode 100644 index 0000000..33e1a1d --- /dev/null +++ b/Issueneter.Tests/PullRequestConfigurableMessageFormatterTests.cs @@ -0,0 +1,36 @@ +using System.Collections; +using Issueneter.Domain.Models; +using Issueneter.Domain.Utility; +using Issueneter.Telegram.Formatters; + +namespace Issueneter.Tests; + +public class PullRequestConfigurableMessageFormatterTests +{ + private static IEnumerable GetTestCases() + { + var dummyRefValue = new Ref>(() => Task.FromResult(new List())); + + var issue = new PullRequest( + "PR title", + "PR author", + "Url", + PullRequestState.Opened, + Array.Empty(), + dummyRefValue); + + yield return new TestCaseData("Message template", issue, "Message template"); + yield return new TestCaseData("Pull request {value.Title}", issue, $"Pull request {issue.Title}"); + yield return new TestCaseData("Pull request {value.Url}", issue, $"Pull request {issue.Url}"); + } + + [TestCaseSource(nameof(GetTestCases))] + public void Formatter_ReturnCorrectString(string template, PullRequest pullRequest, string expected) + { + var formatter = new ConfigurableMessageFormatter(template); + + var actual = formatter.ToMessage(pullRequest); + + Assert.That(actual, Is.EqualTo(expected)); + } +} \ No newline at end of file diff --git a/Issueneter.sln b/Issueneter.sln index dd43e73..9aa3a34 100644 --- a/Issueneter.sln +++ b/Issueneter.sln @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Issueneter.Annotation", "Ge EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Issueneter.ScanSourcesGenerator", "Generations\Issueneter.ScanSourcesGenerator\Issueneter.ScanSourcesGenerator.csproj", "{8E8BA128-DD99-4367-8502-88EFC1205613}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Issueneter.Tests", "Issueneter.Tests\Issueneter.Tests.csproj", "{B696EBD5-4F35-42BC-8158-618DE4D9CF2F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +70,10 @@ Global {8E8BA128-DD99-4367-8502-88EFC1205613}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E8BA128-DD99-4367-8502-88EFC1205613}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E8BA128-DD99-4367-8502-88EFC1205613}.Release|Any CPU.Build.0 = Release|Any CPU + {B696EBD5-4F35-42BC-8158-618DE4D9CF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B696EBD5-4F35-42BC-8158-618DE4D9CF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B696EBD5-4F35-42BC-8158-618DE4D9CF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B696EBD5-4F35-42BC-8158-618DE4D9CF2F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {95386239-56C7-4BB3-A098-258B30404A33} = {4652AD14-6886-4361-B050-8F40F0214E3A} diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..4c232d8 --- /dev/null +++ b/Readme.md @@ -0,0 +1,21 @@ +# Issueneter + +## Configuration + +### Message templates + +Для изменения текста сообщений нужно выставить шаблоны в конфигурационном файле: + +``` +{ + "TelegramOptions": { + "IssueMessageTemplate": "изи [ишуя]({value.Url})", + "PullRequestMessageTemplate": "[пр]({entity.Url})" + } +} +``` + +Шаблоны поддерживают динамически вычисляемые поля. Для того, чтобы в шаблон вставить информацию из issue / pull request нужно указать `{value.*}`, где `*` - это название поля из issue / pull request. Список поддерживаемых динамических полей: + +- Title +- Url \ No newline at end of file