From 38f8051ae35ceca33e07bb7aad2172188ea068ed Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sat, 2 Nov 2024 05:43:45 +0900 Subject: [PATCH 01/12] Add tests for SlackReporter --- Runtime/Reporters/SlackReporter.cs | 25 +++- Tests/Runtime/Reporters/SlackReporterTest.cs | 107 ++++++++++++++++++ .../Reporters/SlackReporterTest.cs.meta | 3 + .../TestDoubles/SpySlackMessageSender.cs | 44 +++++++ .../TestDoubles/SpySlackMessageSender.cs.meta | 3 + Tests/Runtime/TestDoubles/SpyTerminatable.cs | 3 +- 6 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 Tests/Runtime/Reporters/SlackReporterTest.cs create mode 100644 Tests/Runtime/Reporters/SlackReporterTest.cs.meta create mode 100644 Tests/Runtime/TestDoubles/SpySlackMessageSender.cs create mode 100644 Tests/Runtime/TestDoubles/SpySlackMessageSender.cs.meta diff --git a/Runtime/Reporters/SlackReporter.cs b/Runtime/Reporters/SlackReporter.cs index 15f8c4e..8ddd180 100644 --- a/Runtime/Reporters/SlackReporter.cs +++ b/Runtime/Reporters/SlackReporter.cs @@ -49,7 +49,22 @@ public class SlackReporter : AbstractReporter /// public bool withScreenshotOnNormally; - private readonly ISlackMessageSender _sender = new SlackMessageSender(new SlackAPI()); + internal ISlackMessageSender _sender = new SlackMessageSender(new SlackAPI()); + + private static ILogger Logger + { + get + { + var settings = AutopilotState.Instance.settings; + if (settings != null) + { + return settings.LoggerAsset.Logger; + } + + Debug.LogWarning("Autopilot is not running"); // impossible to reach here + return null; + } + } /// public override async UniTask PostReportAsync( @@ -68,7 +83,11 @@ public override async UniTask PostReportAsync( // TODO: build message body with template and placeholders OverwriteByCommandlineArguments(); - // TODO: log warn if slackToken or slackChannels is empty + if (string.IsNullOrEmpty(slackToken) || string.IsNullOrEmpty(slackChannels)) + { + Logger?.Log(LogType.Warning, "Slack token or channels is empty"); + return; + } // NOTE: In _sender.send, switch the execution thread to the main thread, so UniTask.WhenAll is meaningless. foreach (var slackChannel in slackChannels.Split(',')) @@ -100,7 +119,7 @@ await _sender.Send( withScreenshot, cancellationToken ); - // TODO: can log slack post url? + Logger?.Log($"Slack message sent to {slackChannel}"); } private void OverwriteByCommandlineArguments() diff --git a/Tests/Runtime/Reporters/SlackReporterTest.cs b/Tests/Runtime/Reporters/SlackReporterTest.cs new file mode 100644 index 0000000..c070cfc --- /dev/null +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs @@ -0,0 +1,107 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System.Threading.Tasks; +using DeNA.Anjin.Loggers; +using DeNA.Anjin.Settings; +using DeNA.Anjin.TestDoubles; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DeNA.Anjin.Reporters +{ + [TestFixture] + public class SlackReporterTest + { + private SpySlackMessageSender _spy; + private SlackReporter _sut; + + [SetUp] + public void SetUp() + { + _sut = ScriptableObject.CreateInstance(); + _sut.mentionSubTeamIDs = string.Empty; + _spy = new SpySlackMessageSender(); + _sut._sender = _spy; + + // for log output test + AutopilotState.Instance.settings = ScriptableObject.CreateInstance(); + AutopilotState.Instance.settings!.LoggerAsset.loggerAssets.Add( + ScriptableObject.CreateInstance()); + } + + [TearDown] + public void TearDown() + { + AutopilotState.Instance.Reset(); + } + + [Test] + public async Task PostReportAsync_OnError_Sent() + { + _sut.slackToken = "token"; + _sut.slackChannels = "dev,qa"; // two channels + _sut.mentionSubTeamIDs = "alpha,bravo"; + _sut.addHereInSlackMessage = true; + _sut.withScreenshotOnError = false; + _sut.withScreenshotOnNormally = true; // dummy + await _sut.PostReportAsync("message", "stack trace", ExitCode.AutopilotFailed); + + Assert.That(_spy.CalledList.Count, Is.EqualTo(2)); + Assert.That(_spy.CalledList[0].SlackToken, Is.EqualTo("token")); + Assert.That(_spy.CalledList[0].SlackChannel, Is.EqualTo("dev")); + Assert.That(_spy.CalledList[1].SlackChannel, Is.EqualTo("qa")); + Assert.That(_spy.CalledList[0].MentionSubTeamIDs, Is.EquivalentTo(new[] { "alpha", "bravo" })); + Assert.That(_spy.CalledList[0].AddHereInSlackMessage, Is.True); + Assert.That(_spy.CalledList[0].LogString, Is.EqualTo("message")); + Assert.That(_spy.CalledList[0].StackTrace, Is.EqualTo("stack trace")); + Assert.That(_spy.CalledList[0].WithScreenshot, Is.False); // use withScreenshotOnError + } + + [Test] + public async Task PostReportAsync_OnNormallyAndPostOnNormally_Sent() + { + _sut.slackToken = "token"; + _sut.slackChannels = "dev"; + _sut.postOnNormally = true; + _sut.withScreenshotOnNormally = true; + _sut.withScreenshotOnError = false; // dummy + await _sut.PostReportAsync(string.Empty, string.Empty, ExitCode.Normally); + + Assert.That(_spy.CalledList.Count, Is.EqualTo(1)); + Assert.That(_spy.CalledList[0].WithScreenshot, Is.True); // use withScreenshotOnNormally + } + + [Test] + public async Task PostReportAsync_OnNormallyAndNotPostOnNormally_NotSent() + { + _sut.postOnNormally = false; + await _sut.PostReportAsync(string.Empty, string.Empty, ExitCode.Normally); + + Assert.That(_spy.CalledList, Is.Empty); + } + + [Test] + public async Task PostReportAsync_NoSlackToken_NotSentAndLogWarning() + { + _sut.slackToken = string.Empty; + _sut.slackChannels = "dev,qa"; + await _sut.PostReportAsync(string.Empty, string.Empty, ExitCode.AutopilotFailed); + + Assert.That(_spy.CalledList, Is.Empty); + LogAssert.Expect(LogType.Warning, "Slack token or channels is empty"); + } + + [Test] + public async Task PostReportAsync_NoSlackChannels_NotSentAndLogWarning() + { + _sut.slackToken = "token"; + _sut.slackChannels = string.Empty; + await _sut.PostReportAsync(string.Empty, string.Empty, ExitCode.AutopilotFailed); + + Assert.That(_spy.CalledList, Is.Empty); + LogAssert.Expect(LogType.Warning, "Slack token or channels is empty"); + } + } +} diff --git a/Tests/Runtime/Reporters/SlackReporterTest.cs.meta b/Tests/Runtime/Reporters/SlackReporterTest.cs.meta new file mode 100644 index 0000000..28a455a --- /dev/null +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0e4df8fbf0eb4567a914985dd25d9b27 +timeCreated: 1730487313 \ No newline at end of file diff --git a/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs new file mode 100644 index 0000000..ae57379 --- /dev/null +++ b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using Cysharp.Threading.Tasks; +using DeNA.Anjin.Reporters; + +namespace DeNA.Anjin.TestDoubles +{ + public class SpySlackMessageSender : ISlackMessageSender + { + public struct CallArguments + { + public string SlackToken { get; set; } + public string SlackChannel { get; set; } + public IEnumerable MentionSubTeamIDs { get; set; } + public bool AddHereInSlackMessage { get; set; } + public string LogString { get; set; } + public string StackTrace { get; set; } + public bool WithScreenshot { get; set; } + } + + public List CalledList { get; } = new List(); + + public async UniTask Send(string slackToken, string slackChannel, IEnumerable mentionSubTeamIDs, + bool addHereInSlackMessage, string logString, string stackTrace, bool withScreenshot, + CancellationToken cancellationToken = default) + { + var called = new CallArguments + { + SlackToken = slackToken, + SlackChannel = slackChannel, + MentionSubTeamIDs = mentionSubTeamIDs, + AddHereInSlackMessage = addHereInSlackMessage, + LogString = logString, + StackTrace = stackTrace, + WithScreenshot = withScreenshot, + }; + CalledList.Add(called); + await UniTask.CompletedTask; + } + } +} diff --git a/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs.meta b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs.meta new file mode 100644 index 0000000..6780174 --- /dev/null +++ b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bbbaa1f65d754c9eb39ce76717797301 +timeCreated: 1730487705 \ No newline at end of file diff --git a/Tests/Runtime/TestDoubles/SpyTerminatable.cs b/Tests/Runtime/TestDoubles/SpyTerminatable.cs index b26fa19..4131de2 100644 --- a/Tests/Runtime/TestDoubles/SpyTerminatable.cs +++ b/Tests/Runtime/TestDoubles/SpyTerminatable.cs @@ -4,8 +4,6 @@ using System.Threading; using Cysharp.Threading.Tasks; -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - namespace DeNA.Anjin.TestDoubles { public class SpyTerminatable : ITerminatable @@ -24,6 +22,7 @@ public async UniTask TerminateAsync(ExitCode exitCode, string message = null, st CapturedMessage = message; CapturedStackTrace = stackTrace; CapturedReporting = reporting; + await UniTask.CompletedTask; } } } From 7733286909055ade94722b1dd43f8887f5c4e970 Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sat, 2 Nov 2024 06:29:57 +0900 Subject: [PATCH 02/12] Add message body template --- .editorconfig | 1 + Editor/Localization/ja.po | 22 ++++++++++++-- Editor/UI/Reporters/SlackReporterEditor.cs | 30 ++++++++++++++++---- Runtime/Reporters/MessageBuilder.cs | 28 ++++++++++++++++++ Runtime/Reporters/MessageBuilder.cs.meta | 3 ++ Runtime/Reporters/SlackReporter.cs | 27 ++++++++++++++---- Tests/Runtime/Reporters/SlackReporterTest.cs | 15 ++++++---- 7 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 Runtime/Reporters/MessageBuilder.cs create mode 100644 Runtime/Reporters/MessageBuilder.cs.meta diff --git a/.editorconfig b/.editorconfig index 8c560ab..04e1396 100644 --- a/.editorconfig +++ b/.editorconfig @@ -160,6 +160,7 @@ csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true +csharp_place_field_attribute_on_same_line = false # Indentation preferences csharp_indent_block_contents = true diff --git a/Editor/Localization/ja.po b/Editor/Localization/ja.po index 46ad5e3..0980075 100644 --- a/Editor/Localization/ja.po +++ b/Editor/Localization/ja.po @@ -501,8 +501,16 @@ msgstr "@hereをメッセージにつける" msgid "Whether adding @here into Slack messages or not" msgstr "Slack通知メッセージに@hereを付けます" +# messageBodyTemplateOnError +msgid "Message Body Template" +msgstr "メッセージ本文テンプレート" + +# messageBodyTemplateOnError tooltip +msgid "Message body template when posting an error terminated report. You can specify placeholders like a "{message}"; see the README for more information." +msgstr "エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" + # withScreenshotOnError -msgid "Take screenshot" +msgid "Take Screenshot" msgstr "スクリーンショット" # withScreenshotOnError tooltip @@ -510,15 +518,23 @@ msgid "Take a screenshot when posting an error terminated report" msgstr "エラー終了時にスクリーンショットを撮影します" # postOnNormally -msgid "Normally terminated report" +msgid "Normally Terminated Report" msgstr "正常終了時にもレポート" # postOnNormally tooltip msgid "Post a report if normally terminates." msgstr "正常終了時にもレポートをポストします" +# messageBodyTemplateOnNormally (same as messageBodyTemplateOnError) +msgid "Message Body Template" +msgstr "メッセージ本文テンプレート" + +# messageBodyTemplateOnError tooltip +msgid "Message body template when posting a normally terminated report. You can specify placeholders like a "{message}"; see the README for more information." +msgstr "正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" + # withScreenshotOnNormally (same as withScreenshotOnError) -msgid "Take screenshot" +msgid "Take Screenshot" msgstr "スクリーンショット" # withScreenshotOnNormally tooltip diff --git a/Editor/UI/Reporters/SlackReporterEditor.cs b/Editor/UI/Reporters/SlackReporterEditor.cs index 43870b1..8ae5ca6 100644 --- a/Editor/UI/Reporters/SlackReporterEditor.cs +++ b/Editor/UI/Reporters/SlackReporterEditor.cs @@ -15,6 +15,7 @@ public class SlackReporterEditor : UnityEditor.Editor { private const float SpacerPixels = 10f; + // @formatter:off private static readonly string s_description = L10n.Tr("Description"); private static readonly string s_descriptionTooltip = L10n.Tr("Description about this Reporter instance"); private SerializedProperty _descriptionProp; @@ -40,29 +41,40 @@ public class SlackReporterEditor : UnityEditor.Editor private SerializedProperty _addHereInSlackMessageProp; private GUIContent _addHereInSlackMessageGUIContent; - private static readonly string s_withScreenshotOnError = L10n.Tr("Take screenshot"); + private static readonly string s_messageBodyTemplateOnError = L10n.Tr("Message Body Template"); + private static readonly string s_messageBodyTemplateOnErrorTooltip = L10n.Tr("Message body template when posting an error terminated report. You can specify placeholders like a \"{message}\"; see the README for more information."); + private SerializedProperty _messageBodyTemplateOnErrorProp; + private GUIContent _messageBodyTemplateOnErrorGUIContent; + + private static readonly string s_withScreenshotOnError = L10n.Tr("Take Screenshot"); private static readonly string s_withScreenshotOnErrorTooltip = L10n.Tr("Take a screenshot when posting an error terminated report"); private SerializedProperty _withScreenshotOnErrorProp; private GUIContent _withScreenshotOnErrorGUIContent; - private static readonly string s_postOnNormally = L10n.Tr("Normally terminated report"); + private static readonly string s_postOnNormally = L10n.Tr("Normally Terminated Report"); private static readonly string s_postOnNormallyTooltip = L10n.Tr("Post a report if normally terminates."); private SerializedProperty _postOnNormallyProp; private GUIContent _postOnNormallyGUIContent; - private static readonly string s_withScreenshotOnNormally = L10n.Tr("Take screenshot"); + private static readonly string s_messageBodyTemplateOnNormally = L10n.Tr("Message Body Template"); + private static readonly string s_messageBodyTemplateOnNormallyTooltip = L10n.Tr("Message body template when posting a normally terminated report. You can specify placeholders like a \"{message}\"; see the README for more information."); + private SerializedProperty _messageBodyTemplateOnNormallyProp; + private GUIContent _messageBodyTemplateOnNormallyGUIContent; + + private static readonly string s_withScreenshotOnNormally = L10n.Tr("Take Screenshot"); private static readonly string s_withScreenshotOnNormallyTooltip = L10n.Tr("Take a screenshot when posting a normally terminated report"); private SerializedProperty _withScreenshotOnNormallyProp; private GUIContent _withScreenshotOnNormallyGUIContent; + // @formatter:on private void OnEnable() { Initialize(); } - private void Initialize() { + // @formatter:off _descriptionProp = serializedObject.FindProperty(nameof(SlackReporter.description)); _descriptionGUIContent = new GUIContent(s_description, s_descriptionTooltip); @@ -78,17 +90,23 @@ private void Initialize() _addHereInSlackMessageProp = serializedObject.FindProperty(nameof(SlackReporter.addHereInSlackMessage)); _addHereInSlackMessageGUIContent = new GUIContent(s_addHereInSlackMessage, s_addHereInSlackMessageTooltip); + _messageBodyTemplateOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.messageBodyTemplateOnError)); + _messageBodyTemplateOnErrorGUIContent = new GUIContent(s_messageBodyTemplateOnError, s_messageBodyTemplateOnErrorTooltip); + _withScreenshotOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.withScreenshotOnError)); _withScreenshotOnErrorGUIContent = new GUIContent(s_withScreenshotOnError, s_withScreenshotOnErrorTooltip); _postOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.postOnNormally)); _postOnNormallyGUIContent = new GUIContent(s_postOnNormally, s_postOnNormallyTooltip); + _messageBodyTemplateOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.messageBodyTemplateOnNormally)); + _messageBodyTemplateOnNormallyGUIContent = new GUIContent(s_messageBodyTemplateOnNormally, s_messageBodyTemplateOnNormallyTooltip); + _withScreenshotOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.withScreenshotOnNormally)); _withScreenshotOnNormallyGUIContent = new GUIContent(s_withScreenshotOnNormally, s_withScreenshotOnNormallyTooltip); + // @formatter:on } - public override void OnInspectorGUI() { serializedObject.Update(); @@ -104,12 +122,14 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_addHereInSlackMessageProp, _addHereInSlackMessageGUIContent); GUILayout.Space(SpacerPixels); + EditorGUILayout.PropertyField(_messageBodyTemplateOnErrorProp, _messageBodyTemplateOnErrorGUIContent); EditorGUILayout.PropertyField(_withScreenshotOnErrorProp, _withScreenshotOnErrorGUIContent); GUILayout.Space(SpacerPixels); EditorGUILayout.PropertyField(_postOnNormallyProp, _postOnNormallyGUIContent); EditorGUI.BeginDisabledGroup(!_postOnNormallyProp.boolValue); + EditorGUILayout.PropertyField(_messageBodyTemplateOnNormallyProp, _messageBodyTemplateOnNormallyGUIContent); EditorGUILayout.PropertyField(_withScreenshotOnNormallyProp, _withScreenshotOnNormallyGUIContent); EditorGUI.EndDisabledGroup(); diff --git a/Runtime/Reporters/MessageBuilder.cs b/Runtime/Reporters/MessageBuilder.cs new file mode 100644 index 0000000..92a1117 --- /dev/null +++ b/Runtime/Reporters/MessageBuilder.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +namespace DeNA.Anjin.Reporters +{ + /// + /// Message builder for Reporters. + /// + /// Placeholder: + /// - {message}: Message with terminate (e.g., error log message) + /// + public static class MessageBuilder + { + /// + /// Returns messages that replaced placeholders in the template. + /// + /// Message body template + /// Replace placeholder "{message}" in the template with this string + /// Messages that replaced placeholders in the template + public static string BuildWithTemplate(string template, string message) + { + return template + .Replace("{message}", message); + + // TODO: Add more placeholders + } + } +} diff --git a/Runtime/Reporters/MessageBuilder.cs.meta b/Runtime/Reporters/MessageBuilder.cs.meta new file mode 100644 index 0000000..b38486b --- /dev/null +++ b/Runtime/Reporters/MessageBuilder.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ccefeabd9ea84ab490d2af483b6153de +timeCreated: 1730495361 \ No newline at end of file diff --git a/Runtime/Reporters/SlackReporter.cs b/Runtime/Reporters/SlackReporter.cs index 8ddd180..ed2a245 100644 --- a/Runtime/Reporters/SlackReporter.cs +++ b/Runtime/Reporters/SlackReporter.cs @@ -35,7 +35,13 @@ public class SlackReporter : AbstractReporter public bool addHereInSlackMessage; /// - /// With take a screenshot or not (on error terminates). + /// Message body template (use on error terminates). + /// + [Multiline] + public string messageBodyTemplateOnError = @"{message}"; + + /// + /// With take a screenshot or not (use on error terminates). /// public bool withScreenshotOnError = true; @@ -45,7 +51,13 @@ public class SlackReporter : AbstractReporter public bool postOnNormally; /// - /// With take a screenshot or not (on normally terminates). + /// Message body template (use on normally terminates). + /// + [Multiline] + public string messageBodyTemplateOnNormally = @"{message}"; + + /// + /// With take a screenshot or not (use on normally terminates). /// public bool withScreenshotOnNormally; @@ -79,8 +91,13 @@ public override async UniTask PostReportAsync( return; } - var withScreenshot = exitCode == ExitCode.Normally ? withScreenshotOnNormally : withScreenshotOnError; - // TODO: build message body with template and placeholders + var withScreenshot = (exitCode == ExitCode.Normally) + ? withScreenshotOnNormally + : withScreenshotOnError; + var messageBody = MessageBuilder.BuildWithTemplate((exitCode == ExitCode.Normally) + ? messageBodyTemplateOnNormally + : messageBodyTemplateOnError, + message); OverwriteByCommandlineArguments(); if (string.IsNullOrEmpty(slackToken) || string.IsNullOrEmpty(slackChannels)) @@ -97,7 +114,7 @@ public override async UniTask PostReportAsync( return; } - await PostReportAsync(slackChannel, message, stackTrace, withScreenshot, cancellationToken); + await PostReportAsync(slackChannel, messageBody, stackTrace, withScreenshot, cancellationToken); } } diff --git a/Tests/Runtime/Reporters/SlackReporterTest.cs b/Tests/Runtime/Reporters/SlackReporterTest.cs index c070cfc..3ac1966 100644 --- a/Tests/Runtime/Reporters/SlackReporterTest.cs +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs @@ -44,6 +44,8 @@ public async Task PostReportAsync_OnError_Sent() _sut.slackChannels = "dev,qa"; // two channels _sut.mentionSubTeamIDs = "alpha,bravo"; _sut.addHereInSlackMessage = true; + _sut.messageBodyTemplateOnError = "Error terminate with {message}"; + _sut.messageBodyTemplateOnNormally = "Not use this"; // dummy _sut.withScreenshotOnError = false; _sut.withScreenshotOnNormally = true; // dummy await _sut.PostReportAsync("message", "stack trace", ExitCode.AutopilotFailed); @@ -54,9 +56,9 @@ public async Task PostReportAsync_OnError_Sent() Assert.That(_spy.CalledList[1].SlackChannel, Is.EqualTo("qa")); Assert.That(_spy.CalledList[0].MentionSubTeamIDs, Is.EquivalentTo(new[] { "alpha", "bravo" })); Assert.That(_spy.CalledList[0].AddHereInSlackMessage, Is.True); - Assert.That(_spy.CalledList[0].LogString, Is.EqualTo("message")); + Assert.That(_spy.CalledList[0].LogString, Is.EqualTo("Error terminate with message")); // use OnNormally Assert.That(_spy.CalledList[0].StackTrace, Is.EqualTo("stack trace")); - Assert.That(_spy.CalledList[0].WithScreenshot, Is.False); // use withScreenshotOnError + Assert.That(_spy.CalledList[0].WithScreenshot, Is.False); // use OnError } [Test] @@ -65,12 +67,15 @@ public async Task PostReportAsync_OnNormallyAndPostOnNormally_Sent() _sut.slackToken = "token"; _sut.slackChannels = "dev"; _sut.postOnNormally = true; + _sut.messageBodyTemplateOnNormally = "Normally terminate with {message}"; + _sut.messageBodyTemplateOnError = "Not use this"; // dummy _sut.withScreenshotOnNormally = true; _sut.withScreenshotOnError = false; // dummy - await _sut.PostReportAsync(string.Empty, string.Empty, ExitCode.Normally); + await _sut.PostReportAsync("message", string.Empty, ExitCode.Normally); Assert.That(_spy.CalledList.Count, Is.EqualTo(1)); - Assert.That(_spy.CalledList[0].WithScreenshot, Is.True); // use withScreenshotOnNormally + Assert.That(_spy.CalledList[0].LogString, Is.EqualTo("Normally terminate with message")); // use OnNormally + Assert.That(_spy.CalledList[0].WithScreenshot, Is.True); // use OnNormally } [Test] @@ -86,7 +91,7 @@ public async Task PostReportAsync_OnNormallyAndNotPostOnNormally_NotSent() public async Task PostReportAsync_NoSlackToken_NotSentAndLogWarning() { _sut.slackToken = string.Empty; - _sut.slackChannels = "dev,qa"; + _sut.slackChannels = "dev"; await _sut.PostReportAsync(string.Empty, string.Empty, ExitCode.AutopilotFailed); Assert.That(_spy.CalledList, Is.Empty); From deb2b7f70af2323130ad042b82f2bdaab3de8e06 Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sat, 2 Nov 2024 17:37:14 +0900 Subject: [PATCH 03/12] Add more placeholders --- Editor/Localization/ja.po | 8 ++++ Editor/UI/Settings/AutopilotSettingsEditor.cs | 6 +++ Runtime/Reporters/MessageBuilder.cs | 38 +++++++++++++---- Runtime/Settings/AutopilotSettings.cs | 18 +++++++- Tests/Runtime/Reporters/MessageBuilderTest.cs | 41 +++++++++++++++++++ .../Reporters/MessageBuilderTest.cs.meta | 3 ++ 6 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 Tests/Runtime/Reporters/MessageBuilderTest.cs create mode 100644 Tests/Runtime/Reporters/MessageBuilderTest.cs.meta diff --git a/Editor/Localization/ja.po b/Editor/Localization/ja.po index 0980075..1c18d17 100644 --- a/Editor/Localization/ja.po +++ b/Editor/Localization/ja.po @@ -23,6 +23,14 @@ msgstr "説明" msgid "Description about this setting instance" msgstr "このAutopilotSettingsインスタンスの説明" +# name +msgid "Name" +msgstr "名前" + +# description tooltip +msgid "Custom name of this setting used by Reporter. If omitted, the asset file name is used." +msgstr "このAutopilotSettingsインスタンスの名前。Reporterで使用されます。省略時はアセットファイル名がデフォルトとして使用されます" + # Header: Agent Assignment msgid "Agent Assignment" msgstr "Agent割り当て設定" diff --git a/Editor/UI/Settings/AutopilotSettingsEditor.cs b/Editor/UI/Settings/AutopilotSettingsEditor.cs index 629df44..683075c 100644 --- a/Editor/UI/Settings/AutopilotSettingsEditor.cs +++ b/Editor/UI/Settings/AutopilotSettingsEditor.cs @@ -18,6 +18,9 @@ public class AutopilotSettingsEditor : UnityEditor.Editor private static readonly string s_description = L10n.Tr("Description"); private static readonly string s_descriptionTooltip = L10n.Tr("Description about this setting instance"); + private static readonly string s_name = L10n.Tr("Name"); + private static readonly string s_nameTooltip = L10n.Tr("Custom name of this setting used by Reporter. If omitted, the asset file name is used."); + private static readonly string s_agentAssignmentHeader = L10n.Tr("Agent Assignment"); private static readonly string s_sceneAgentMaps = L10n.Tr("Scene Agent Mapping"); private static readonly string s_sceneAgentMapsTooltip = L10n.Tr("Scene to Agent assign mapping"); @@ -30,6 +33,7 @@ public class AutopilotSettingsEditor : UnityEditor.Editor private static readonly string s_autopilotRunSettingsHeader = L10n.Tr("Autopilot Run Settings"); private static readonly string s_lifespanSec = L10n.Tr("Lifespan Sec"); private static readonly string s_lifespanSecTooltip = L10n.Tr("Autopilot running lifespan [sec]. When specified zero, so unlimited running"); + private static readonly string s_randomSeed = L10n.Tr("Random Seed"); private static readonly string s_randomSeedTooltip = L10n.Tr("Random using the specified seed value"); private static readonly string s_timeScale = L10n.Tr("Time Scale"); @@ -69,6 +73,8 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.description)), new GUIContent(s_description, s_descriptionTooltip)); + EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.name)), + new GUIContent(s_name, s_nameTooltip)); DrawHeader(s_agentAssignmentHeader); EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.sceneAgentMaps)), diff --git a/Runtime/Reporters/MessageBuilder.cs b/Runtime/Reporters/MessageBuilder.cs index 92a1117..92219b1 100644 --- a/Runtime/Reporters/MessageBuilder.cs +++ b/Runtime/Reporters/MessageBuilder.cs @@ -1,28 +1,52 @@ // Copyright (c) 2023-2024 DeNA Co., Ltd. // This software is released under the MIT License. +using System; +using System.Collections.Generic; +using System.Linq; +using DeNA.Anjin.Settings; + namespace DeNA.Anjin.Reporters { /// /// Message builder for Reporters. - /// - /// Placeholder: - /// - {message}: Message with terminate (e.g., error log message) /// public static class MessageBuilder { /// /// Returns messages that replaced placeholders in the template. /// - /// Message body template + /// Message body template. + /// You can specify placeholders: + /// - {message}: Message with terminate (e.g., error log message) + /// - {settings}: Name of running AutopilotSettings + /// - {env.KEY}: Environment variable + /// /// Replace placeholder "{message}" in the template with this string /// Messages that replaced placeholders in the template public static string BuildWithTemplate(string template, string message) { - return template - .Replace("{message}", message); + var settings = AutopilotState.Instance.settings; + var placeholders = GetPlaceholders().ToList(); + placeholders.Add(("{message}", message)); + placeholders.Add(("{settings}", settings ? settings.Name : "null")); + + var replacedMessage = template; + placeholders.ForEach(placeholder => + { + replacedMessage = replacedMessage.Replace(placeholder.placeholder, placeholder.replacement); + }); - // TODO: Add more placeholders + return replacedMessage; + } + + private static IEnumerable<(string placeholder, string replacement)> GetPlaceholders() + { + var env = Environment.GetEnvironmentVariables(); + foreach (var key in env.Keys) + { + yield return ("{env." + key + "}", env[key] as string); + } } } } diff --git a/Runtime/Settings/AutopilotSettings.cs b/Runtime/Settings/AutopilotSettings.cs index a40829e..9047c4b 100644 --- a/Runtime/Settings/AutopilotSettings.cs +++ b/Runtime/Settings/AutopilotSettings.cs @@ -27,7 +27,8 @@ public struct SceneAgentMap /// /// Scene /// - [ScenePicker("Scene")] public string scenePath; + [ScenePicker("Scene")] + public string scenePath; /// /// Agent @@ -47,9 +48,22 @@ public class AutopilotSettings : ScriptableObject /// /// Description about this setting instance /// - [Multiline] public string description; + [Multiline] + public string description; #endif + /// + /// Custom name of this setting used by Reporter. + /// When using it from within code, use the Name property. + /// + public new string name; + + /// + /// Name of this setting used by Reporter. + /// If omitted, the asset file name is used. + /// + public string Name => string.IsNullOrEmpty(this.name) ? base.name : this.name; + /// /// Scene to Agent assign mapping /// diff --git a/Tests/Runtime/Reporters/MessageBuilderTest.cs b/Tests/Runtime/Reporters/MessageBuilderTest.cs new file mode 100644 index 0000000..8847d4b --- /dev/null +++ b/Tests/Runtime/Reporters/MessageBuilderTest.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using DeNA.Anjin.Settings; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DeNA.Anjin.Reporters +{ + public class MessageBuilderTest + { + [Test] + public void BuildWithTemplate_Message_ReplacesMessage() + { + var actual = MessageBuilder.BuildWithTemplate("Error: {message}.", "Something went wrong"); + Assert.That(actual, Is.EqualTo("Error: Something went wrong.")); + } + + [Test] + public void BuildWithTemplate_Settings_ReplacesSettingsName() + { + AutopilotState.Instance.settings = ScriptableObject.CreateInstance(); + AutopilotState.Instance.settings!.name = "Settings for test"; + + var actual = MessageBuilder.BuildWithTemplate("Autopilot settings: {settings}.", null); + Assert.That(actual, Is.EqualTo("Autopilot settings: Settings for test.")); + + // teardown + AutopilotState.Instance.Reset(); + } + + [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.WindowsEditor, RuntimePlatform.WindowsPlayer })] + public void BuildWithTemplate_EnvKey_ReplacesEnvironmentVariable() + { + var actual = MessageBuilder.BuildWithTemplate("Environment variable SHELL: {env.SHELL}.", null); + Assert.That(actual, Is.EqualTo("Environment variable SHELL: /bin/bash.")); + } + } +} diff --git a/Tests/Runtime/Reporters/MessageBuilderTest.cs.meta b/Tests/Runtime/Reporters/MessageBuilderTest.cs.meta new file mode 100644 index 0000000..539253c --- /dev/null +++ b/Tests/Runtime/Reporters/MessageBuilderTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 94a8571113e045d6a5c84df1305fdb69 +timeCreated: 1730532095 \ No newline at end of file From dc0e4eec890b3e34ecd9973c9470540e76c9b928 Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sun, 3 Nov 2024 02:52:33 +0900 Subject: [PATCH 04/12] Mod READMEs --- README.md | 8 ++++++++ README_ja.md | 8 ++++++++ Runtime/Reporters/MessageBuilder.cs | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f8a0d9d..8aeed0a 100644 --- a/README.md +++ b/README.md @@ -437,8 +437,10 @@ The instance of this Reporter (.asset file) can have the following settings. The bot must be invited to the channel.
Mention Sub Team IDs
Comma Separated Team IDs to mention in notification message
Add Here In Slack Message
Add @here to notification message. Default is off
+
Message Body Template
Message body template when posting an error terminated report. You can specify placeholders like a "{message}".
Take screenshot
Take a screenshot when posting an error terminated report. Default is on
Normally terminated report
Post a report if normally terminates. Default is off
+
Message Body Template
Message body template when posting a normally terminated report. You can specify placeholders like a "{message}".
Take screenshot
Take a screenshot when posting a normally terminated report. Default is off
@@ -450,6 +452,12 @@ The Slack Bot needs the following permissions: - chat:write - files:write +The placeholders you can include in the message body template are: + +- "{message}": Message with terminate (e.g., error log message) +- "{settings}": Name of running AutopilotSettings +- "{env.KEY}": Environment variables + ## Implementation of game-title-specific diff --git a/README_ja.md b/README_ja.md index bcb780b..d168acb 100644 --- a/README_ja.md +++ b/README_ja.md @@ -442,8 +442,10 @@ Slackにレポート送信するReporterです。 チャンネルにはBotを招待しておく必要があります。
Mention Sub Team IDs
通知メッセージでメンションするチームのIDをカンマ区切りで指定します
Add Here In Slack Message
通知メッセージに@hereを付けます(デフォルト: off)
+
Message Body Template
エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
Take screenshot
エラー終了時にスクリーンショットを撮影します(デフォルト: on)
Normally terminated report
正常終了時にもレポートをポストします(デフォルト: off)
+
Message Body Template
正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
Take screenshot
正常終了時にスクリーンショットを撮影します(デフォルト: off)
@@ -455,6 +457,12 @@ Slack Botには次の権限が必要です。 - chat:write - files:write +メッセージ本文のテンプレートに記述できるプレースホルダーは次のとおりです。 + +- "{message}": 終了要因メッセージ(エラーログのメッセージなど) +- "{settings}": 実行中の AutopilotSettings 名 +- "{env.KEY}": 環境変数 + ## ゲームタイトル固有の実装 diff --git a/Runtime/Reporters/MessageBuilder.cs b/Runtime/Reporters/MessageBuilder.cs index 92219b1..dfa0e15 100644 --- a/Runtime/Reporters/MessageBuilder.cs +++ b/Runtime/Reporters/MessageBuilder.cs @@ -20,7 +20,7 @@ public static class MessageBuilder /// You can specify placeholders: /// - {message}: Message with terminate (e.g., error log message) /// - {settings}: Name of running AutopilotSettings - /// - {env.KEY}: Environment variable + /// - {env.KEY}: Environment variables /// /// Replace placeholder "{message}" in the template with this string /// Messages that replaced placeholders in the template From 23e13a4cdfb28d2ee27756cdcc77d8a52cabb7bd Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sun, 3 Nov 2024 03:32:10 +0900 Subject: [PATCH 05/12] Fix tests depend on environment variables --- Tests/Runtime/Reporters/MessageBuilderTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Runtime/Reporters/MessageBuilderTest.cs b/Tests/Runtime/Reporters/MessageBuilderTest.cs index 8847d4b..5695a66 100644 --- a/Tests/Runtime/Reporters/MessageBuilderTest.cs +++ b/Tests/Runtime/Reporters/MessageBuilderTest.cs @@ -3,8 +3,8 @@ using DeNA.Anjin.Settings; using NUnit.Framework; +using TestHelper.Attributes; using UnityEngine; -using UnityEngine.TestTools; namespace DeNA.Anjin.Reporters { @@ -31,11 +31,11 @@ public void BuildWithTemplate_Settings_ReplacesSettingsName() } [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.WindowsEditor, RuntimePlatform.WindowsPlayer })] + [IgnoreWindowMode("Need command line arguments. see Makefile")] public void BuildWithTemplate_EnvKey_ReplacesEnvironmentVariable() { - var actual = MessageBuilder.BuildWithTemplate("Environment variable SHELL: {env.SHELL}.", null); - Assert.That(actual, Is.EqualTo("Environment variable SHELL: /bin/bash.")); + var actual = MessageBuilder.BuildWithTemplate("Environment variable STR_ENV: {env.STR_ENV}.", null); + Assert.That(actual, Is.EqualTo("Environment variable STR_ENV: STRING_BY_ENVIRONMENT_VARIABLE.")); } } } From 135af88c59669087fcefabd301680e9499eb7c6a Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sun, 3 Nov 2024 03:34:07 +0900 Subject: [PATCH 06/12] Fix expression to work with Unity 2019 --- Tests/Runtime/Reporters/MessageBuilderTest.cs | 5 +++-- Tests/Runtime/Reporters/SlackReporterTest.cs | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Tests/Runtime/Reporters/MessageBuilderTest.cs b/Tests/Runtime/Reporters/MessageBuilderTest.cs index 5695a66..c15b3d1 100644 --- a/Tests/Runtime/Reporters/MessageBuilderTest.cs +++ b/Tests/Runtime/Reporters/MessageBuilderTest.cs @@ -20,8 +20,9 @@ public void BuildWithTemplate_Message_ReplacesMessage() [Test] public void BuildWithTemplate_Settings_ReplacesSettingsName() { - AutopilotState.Instance.settings = ScriptableObject.CreateInstance(); - AutopilotState.Instance.settings!.name = "Settings for test"; + var settings = ScriptableObject.CreateInstance(); + settings.name = "Settings for test"; + AutopilotState.Instance.settings = settings; var actual = MessageBuilder.BuildWithTemplate("Autopilot settings: {settings}.", null); Assert.That(actual, Is.EqualTo("Autopilot settings: Settings for test.")); diff --git a/Tests/Runtime/Reporters/SlackReporterTest.cs b/Tests/Runtime/Reporters/SlackReporterTest.cs index 3ac1966..23e9d59 100644 --- a/Tests/Runtime/Reporters/SlackReporterTest.cs +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs @@ -26,9 +26,9 @@ public void SetUp() _sut._sender = _spy; // for log output test - AutopilotState.Instance.settings = ScriptableObject.CreateInstance(); - AutopilotState.Instance.settings!.LoggerAsset.loggerAssets.Add( - ScriptableObject.CreateInstance()); + var settings = ScriptableObject.CreateInstance(); + settings.LoggerAsset.loggerAssets.Add(ScriptableObject.CreateInstance()); + AutopilotState.Instance.settings = settings; } [TearDown] From 3e2c4fafd097dd4be9e01cea3b6040081607786d Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sun, 3 Nov 2024 03:39:19 +0900 Subject: [PATCH 07/12] Remove message body from thread --- Runtime/Reporters/SlackMessageSender.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Runtime/Reporters/SlackMessageSender.cs b/Runtime/Reporters/SlackMessageSender.cs index 84006c7..301e393 100644 --- a/Runtime/Reporters/SlackMessageSender.cs +++ b/Runtime/Reporters/SlackMessageSender.cs @@ -77,7 +77,7 @@ public async UniTask Send( return; } - var title = Title(logString, mentionSubTeamIDs, addHereInSlackMessage); + var title = CreateTitle(logString, mentionSubTeamIDs, addHereInSlackMessage); await UniTask.SwitchToMainThread(); @@ -118,13 +118,13 @@ public async UniTask Send( if (stackTrace != null && stackTrace.Length > 0) { - var body = Body(logString, stackTrace); + var body = CreateStackTrace(stackTrace); await _slackAPI.Post(slackToken, slackChannel, body, postTitleTask.Ts, cancellationToken); } } - private static string Title(string logString, IEnumerable mentionSubTeamIDs, bool withHere) + private static string CreateTitle(string logString, IEnumerable mentionSubTeamIDs, bool withHere) { var sb = new StringBuilder(); foreach (var s in mentionSubTeamIDs) @@ -146,9 +146,10 @@ private static string Title(string logString, IEnumerable mentionSubTeam } - private static string Body(string logString, string stackTrace) + private static string CreateStackTrace( string stackTrace) { - return $"{logString}\n\n```{stackTrace}```"; + return $"```{stackTrace}```"; + // TODO: Split every 4k characters and quote. } private class CoroutineRunner : MonoBehaviour From 9272c3bd1acd119cd612c501b3b2f58d04b69f8b Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sun, 3 Nov 2024 04:12:47 +0900 Subject: [PATCH 08/12] Fix tests --- Tests/Runtime/Reporters/MessageBuilderTest.cs | 3 ++- Tests/Runtime/Reporters/SlackMessageSenderTest.cs | 8 ++++---- Tests/Runtime/Settings/ArgumentCapture/ArgumentTest.cs | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Tests/Runtime/Reporters/MessageBuilderTest.cs b/Tests/Runtime/Reporters/MessageBuilderTest.cs index c15b3d1..4f8a8ba 100644 --- a/Tests/Runtime/Reporters/MessageBuilderTest.cs +++ b/Tests/Runtime/Reporters/MessageBuilderTest.cs @@ -32,7 +32,8 @@ public void BuildWithTemplate_Settings_ReplacesSettingsName() } [Test] - [IgnoreWindowMode("Need command line arguments. see Makefile")] + [IgnoreWindowMode("This test requires environment variables. see Makefile")] + [Category("IgnoreCI")] // This test requires environment variables. public void BuildWithTemplate_EnvKey_ReplacesEnvironmentVariable() { var actual = MessageBuilder.BuildWithTemplate("Environment variable STR_ENV: {env.STR_ENV}.", null); diff --git a/Tests/Runtime/Reporters/SlackMessageSenderTest.cs b/Tests/Runtime/Reporters/SlackMessageSenderTest.cs index 6c95b01..6946f5d 100644 --- a/Tests/Runtime/Reporters/SlackMessageSenderTest.cs +++ b/Tests/Runtime/Reporters/SlackMessageSenderTest.cs @@ -42,7 +42,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```STACKTRACE```" }, + { "message", "```STACKTRACE```" }, { "ts", "1" } }, }; @@ -76,7 +76,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```STACKTRACE```" }, + { "message", "```STACKTRACE```" }, { "ts", "1" } }, }; @@ -115,7 +115,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```STACKTRACE```" }, + { "message", "```STACKTRACE```" }, { "ts", "1" } }, }; @@ -149,7 +149,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```STACKTRACE```" }, + { "message", "```STACKTRACE```" }, { "ts", "1" } }, }; diff --git a/Tests/Runtime/Settings/ArgumentCapture/ArgumentTest.cs b/Tests/Runtime/Settings/ArgumentCapture/ArgumentTest.cs index 6c98569..87099dc 100644 --- a/Tests/Runtime/Settings/ArgumentCapture/ArgumentTest.cs +++ b/Tests/Runtime/Settings/ArgumentCapture/ArgumentTest.cs @@ -23,7 +23,8 @@ public void String_inArgument_gotValue() } [Test] - [IgnoreWindowMode("Need command line arguments. see Makefile")] + [IgnoreWindowMode("This test requires environment variables. see Makefile")] + [Category("IgnoreCI")] // This test requires environment variables. public void String_inEnvironmentVariable_gotValue() { var arg = new Argument("STR_ENV"); From 1a9a900a49472053e16e9af284b2dc204b9d2cf7 Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Sun, 3 Nov 2024 06:49:45 +0900 Subject: [PATCH 09/12] Add Slack attachment color settings UI --- Editor/Localization/ja.po | 22 +++++++++++++++++--- Editor/UI/Reporters/SlackReporterEditor.cs | 24 ++++++++++++++++++++-- README.md | 6 ++++-- README_ja.md | 6 ++++-- Runtime/Reporters/SlackReporter.cs | 10 +++++++++ 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Editor/Localization/ja.po b/Editor/Localization/ja.po index 1c18d17..56ac371 100644 --- a/Editor/Localization/ja.po +++ b/Editor/Localization/ja.po @@ -517,8 +517,16 @@ msgstr "メッセージ本文テンプレート" msgid "Message body template when posting an error terminated report. You can specify placeholders like a "{message}"; see the README for more information." msgstr "エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" +# colorOnError +msgid "Color" +msgstr "色" + +# colorOnError tooltip +msgid "Attachment color when posting an error terminated report" +msgstr "エラー終了時に送信するメッセージのアタッチメントに指定する色" + # withScreenshotOnError -msgid "Take Screenshot" +msgid "Screenshot" msgstr "スクリーンショット" # withScreenshotOnError tooltip @@ -537,12 +545,20 @@ msgstr "正常終了時にもレポートをポストします" msgid "Message Body Template" msgstr "メッセージ本文テンプレート" -# messageBodyTemplateOnError tooltip +# messageBodyTemplateOnNormally tooltip msgid "Message body template when posting a normally terminated report. You can specify placeholders like a "{message}"; see the README for more information." msgstr "正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" +# colorOnNormally +msgid "Color" +msgstr "色" + +# colorOnNormally tooltip +msgid "Attachment color when posting a normally terminated report" +msgstr "正常終了時に送信するメッセージのアタッチメントに指定する色" + # withScreenshotOnNormally (same as withScreenshotOnError) -msgid "Take Screenshot" +msgid "Screenshot" msgstr "スクリーンショット" # withScreenshotOnNormally tooltip diff --git a/Editor/UI/Reporters/SlackReporterEditor.cs b/Editor/UI/Reporters/SlackReporterEditor.cs index 8ae5ca6..1286f0d 100644 --- a/Editor/UI/Reporters/SlackReporterEditor.cs +++ b/Editor/UI/Reporters/SlackReporterEditor.cs @@ -46,7 +46,12 @@ public class SlackReporterEditor : UnityEditor.Editor private SerializedProperty _messageBodyTemplateOnErrorProp; private GUIContent _messageBodyTemplateOnErrorGUIContent; - private static readonly string s_withScreenshotOnError = L10n.Tr("Take Screenshot"); + private static readonly string s_colorOnError = L10n.Tr("Color"); + private static readonly string s_colorOnErrorTooltip = L10n.Tr("Attachment color when posting an error terminated report"); + private SerializedProperty _colorOnErrorProp; + private GUIContent _colorOnErrorGUIContent; + + private static readonly string s_withScreenshotOnError = L10n.Tr("Screenshot"); private static readonly string s_withScreenshotOnErrorTooltip = L10n.Tr("Take a screenshot when posting an error terminated report"); private SerializedProperty _withScreenshotOnErrorProp; private GUIContent _withScreenshotOnErrorGUIContent; @@ -61,7 +66,12 @@ public class SlackReporterEditor : UnityEditor.Editor private SerializedProperty _messageBodyTemplateOnNormallyProp; private GUIContent _messageBodyTemplateOnNormallyGUIContent; - private static readonly string s_withScreenshotOnNormally = L10n.Tr("Take Screenshot"); + private static readonly string s_colorOnNormally = L10n.Tr("Color"); + private static readonly string s_colorOnNormallyTooltip = L10n.Tr("Attachment color when posting a normally terminated report"); + private SerializedProperty _colorOnNormallyProp; + private GUIContent _colorOnNormallyGUIContent; + + private static readonly string s_withScreenshotOnNormally = L10n.Tr("Screenshot"); private static readonly string s_withScreenshotOnNormallyTooltip = L10n.Tr("Take a screenshot when posting a normally terminated report"); private SerializedProperty _withScreenshotOnNormallyProp; private GUIContent _withScreenshotOnNormallyGUIContent; @@ -93,6 +103,9 @@ private void Initialize() _messageBodyTemplateOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.messageBodyTemplateOnError)); _messageBodyTemplateOnErrorGUIContent = new GUIContent(s_messageBodyTemplateOnError, s_messageBodyTemplateOnErrorTooltip); + _colorOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.colorOnError)); + _colorOnErrorGUIContent = new GUIContent(s_colorOnError, s_colorOnErrorTooltip); + _withScreenshotOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.withScreenshotOnError)); _withScreenshotOnErrorGUIContent = new GUIContent(s_withScreenshotOnError, s_withScreenshotOnErrorTooltip); @@ -102,9 +115,14 @@ private void Initialize() _messageBodyTemplateOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.messageBodyTemplateOnNormally)); _messageBodyTemplateOnNormallyGUIContent = new GUIContent(s_messageBodyTemplateOnNormally, s_messageBodyTemplateOnNormallyTooltip); + _colorOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.colorOnNormally)); + _colorOnNormallyGUIContent = new GUIContent(s_colorOnNormally, s_colorOnNormallyTooltip); + _withScreenshotOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.withScreenshotOnNormally)); _withScreenshotOnNormallyGUIContent = new GUIContent(s_withScreenshotOnNormally, s_withScreenshotOnNormallyTooltip); // @formatter:on + + serializedObject.UpdateIfRequiredOrScript(); } public override void OnInspectorGUI() @@ -123,6 +141,7 @@ public override void OnInspectorGUI() GUILayout.Space(SpacerPixels); EditorGUILayout.PropertyField(_messageBodyTemplateOnErrorProp, _messageBodyTemplateOnErrorGUIContent); + EditorGUILayout.PropertyField(_colorOnErrorProp, _colorOnErrorGUIContent); EditorGUILayout.PropertyField(_withScreenshotOnErrorProp, _withScreenshotOnErrorGUIContent); GUILayout.Space(SpacerPixels); @@ -130,6 +149,7 @@ public override void OnInspectorGUI() EditorGUI.BeginDisabledGroup(!_postOnNormallyProp.boolValue); EditorGUILayout.PropertyField(_messageBodyTemplateOnNormallyProp, _messageBodyTemplateOnNormallyGUIContent); + EditorGUILayout.PropertyField(_colorOnNormallyProp, _colorOnNormallyGUIContent); EditorGUILayout.PropertyField(_withScreenshotOnNormallyProp, _withScreenshotOnNormallyGUIContent); EditorGUI.EndDisabledGroup(); diff --git a/README.md b/README.md index 8aeed0a..c5a4628 100644 --- a/README.md +++ b/README.md @@ -438,10 +438,12 @@ The instance of this Reporter (.asset file) can have the following settings.
Mention Sub Team IDs
Comma Separated Team IDs to mention in notification message
Add Here In Slack Message
Add @here to notification message. Default is off
Message Body Template
Message body template when posting an error terminated report. You can specify placeholders like a "{message}".
-
Take screenshot
Take a screenshot when posting an error terminated report. Default is on
+
Color
Attachment color when posting an error terminated report.
+
Screenshot
Take a screenshot when posting an error terminated report. Default is on
Normally terminated report
Post a report if normally terminates. Default is off
Message Body Template
Message body template when posting a normally terminated report. You can specify placeholders like a "{message}".
-
Take screenshot
Take a screenshot when posting a normally terminated report. Default is off
+
Color
Attachment color when posting a normally terminated report.
+
Screenshot
Take a screenshot when posting a normally terminated report. Default is off
You can create a Slack Bot on the following page: diff --git a/README_ja.md b/README_ja.md index d168acb..93fdaa5 100644 --- a/README_ja.md +++ b/README_ja.md @@ -443,10 +443,12 @@ Slackにレポート送信するReporterです。
Mention Sub Team IDs
通知メッセージでメンションするチームのIDをカンマ区切りで指定します
Add Here In Slack Message
通知メッセージに@hereを付けます(デフォルト: off)
Message Body Template
エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
-
Take screenshot
エラー終了時にスクリーンショットを撮影します(デフォルト: on)
+
Color
エラー終了時に送信するメッセージのアタッチメントに指定する色
+
Screenshot
エラー終了時にスクリーンショットを撮影します(デフォルト: on)
Normally terminated report
正常終了時にもレポートをポストします(デフォルト: off)
Message Body Template
正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
-
Take screenshot
正常終了時にスクリーンショットを撮影します(デフォルト: off)
+
Color
正常終了時に送信するメッセージのアタッチメントに指定する色
+
Screenshot
正常終了時にスクリーンショットを撮影します(デフォルト: off)
Slack Botは次のページで作成できます。 diff --git a/Runtime/Reporters/SlackReporter.cs b/Runtime/Reporters/SlackReporter.cs index ed2a245..ca2fa9f 100644 --- a/Runtime/Reporters/SlackReporter.cs +++ b/Runtime/Reporters/SlackReporter.cs @@ -40,6 +40,11 @@ public class SlackReporter : AbstractReporter [Multiline] public string messageBodyTemplateOnError = @"{message}"; + /// + /// Attachment color (use on error terminates). + /// + public Color colorOnError = new Color(0.86f, 0.21f, 0.27f); + /// /// With take a screenshot or not (use on error terminates). /// @@ -56,6 +61,11 @@ public class SlackReporter : AbstractReporter [Multiline] public string messageBodyTemplateOnNormally = @"{message}"; + /// + /// Attachment color (use on normally terminates). + /// + public Color colorOnNormally = new Color(0.16f, 0.65f, 0.27f); + /// /// With take a screenshot or not (use on normally terminates). /// From f0f195d3419d3e12494eadf59ecabebb78edd84b Mon Sep 17 00:00:00 2001 From: Koji Hasegawa Date: Mon, 4 Nov 2024 00:55:45 +0900 Subject: [PATCH 10/12] Mod Slack API using JSON, attachments, and blocks --- Runtime/Reporters/Slack.meta | 3 + Runtime/Reporters/Slack/Payloads.meta | 3 + Runtime/Reporters/Slack/Payloads/Text.meta | 3 + .../Slack/Payloads/Text/Attachment.cs | 32 +++++ .../Slack/Payloads/Text/Attachment.cs.meta | 3 + .../Slack/Payloads/Text/ContextBlock.cs | 26 +++++ .../Slack/Payloads/Text/ContextBlock.cs.meta | 3 + .../Reporters/Slack/Payloads/Text/Payload.cs | 35 ++++++ .../Slack/Payloads/Text/Payload.cs.meta | 3 + Runtime/Reporters/Slack/Payloads/Text/Text.cs | 34 ++++++ .../Slack/Payloads/Text/Text.cs.meta | 3 + Runtime/Reporters/{ => Slack}/SlackAPI.cs | 109 +++++++++++++++--- .../Reporters/{ => Slack}/SlackAPI.cs.meta | 0 .../{ => Slack}/SlackMessageSender.cs | 38 +++--- .../{ => Slack}/SlackMessageSender.cs.meta | 0 Runtime/Reporters/SlackReporter.cs | 10 +- Runtime/Utilities/LogMessageHandler.cs | 1 + Tests/Runtime/Reporters/Slack.meta | 3 + Tests/Runtime/Reporters/Slack/Payloads.meta | 3 + .../Reporters/Slack/Payloads/Text.meta | 3 + .../Slack/Payloads/Text/PayloadTest.cs | 78 +++++++++++++ .../Slack/Payloads/Text/PayloadTest.cs.meta | 3 + .../{ => Slack}/SlackMessageSenderTest.cs | 71 ++++++++---- .../SlackMessageSenderTest.cs.meta | 0 Tests/Runtime/Reporters/SlackReporterTest.cs | 4 +- Tests/Runtime/TestDoubles/SpySlackAPI.cs | 33 ++++-- .../TestDoubles/SpySlackMessageSender.cs | 11 +- 27 files changed, 436 insertions(+), 79 deletions(-) create mode 100644 Runtime/Reporters/Slack.meta create mode 100644 Runtime/Reporters/Slack/Payloads.meta create mode 100644 Runtime/Reporters/Slack/Payloads/Text.meta create mode 100644 Runtime/Reporters/Slack/Payloads/Text/Attachment.cs create mode 100644 Runtime/Reporters/Slack/Payloads/Text/Attachment.cs.meta create mode 100644 Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs create mode 100644 Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs.meta create mode 100644 Runtime/Reporters/Slack/Payloads/Text/Payload.cs create mode 100644 Runtime/Reporters/Slack/Payloads/Text/Payload.cs.meta create mode 100644 Runtime/Reporters/Slack/Payloads/Text/Text.cs create mode 100644 Runtime/Reporters/Slack/Payloads/Text/Text.cs.meta rename Runtime/Reporters/{ => Slack}/SlackAPI.cs (52%) rename Runtime/Reporters/{ => Slack}/SlackAPI.cs.meta (100%) rename Runtime/Reporters/{ => Slack}/SlackMessageSender.cs (81%) rename Runtime/Reporters/{ => Slack}/SlackMessageSender.cs.meta (100%) create mode 100644 Tests/Runtime/Reporters/Slack.meta create mode 100644 Tests/Runtime/Reporters/Slack/Payloads.meta create mode 100644 Tests/Runtime/Reporters/Slack/Payloads/Text.meta create mode 100644 Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs create mode 100644 Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs.meta rename Tests/Runtime/Reporters/{ => Slack}/SlackMessageSenderTest.cs (71%) rename Tests/Runtime/Reporters/{ => Slack}/SlackMessageSenderTest.cs.meta (100%) diff --git a/Runtime/Reporters/Slack.meta b/Runtime/Reporters/Slack.meta new file mode 100644 index 0000000..42cd2e1 --- /dev/null +++ b/Runtime/Reporters/Slack.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c249b27a9dd740b8938e74d2862b44a3 +timeCreated: 1730598210 \ No newline at end of file diff --git a/Runtime/Reporters/Slack/Payloads.meta b/Runtime/Reporters/Slack/Payloads.meta new file mode 100644 index 0000000..0e703a4 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 76259da1b8a64cb69e24aad716b48b0a +timeCreated: 1730595542 \ No newline at end of file diff --git a/Runtime/Reporters/Slack/Payloads/Text.meta b/Runtime/Reporters/Slack/Payloads/Text.meta new file mode 100644 index 0000000..d8e1f94 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4ac852e8f98f4050b71e3f9cfc627225 +timeCreated: 1730647776 \ No newline at end of file diff --git a/Runtime/Reporters/Slack/Payloads/Text/Attachment.cs b/Runtime/Reporters/Slack/Payloads/Text/Attachment.cs new file mode 100644 index 0000000..c8a3e0f --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/Attachment.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System; +using UnityEngine; + +namespace DeNA.Anjin.Reporters.Slack.Payloads.Text +{ + /// + /// Attachment. + /// + /// + [Serializable] + public class Attachment + { + public ContextBlock[] blocks; + public string color; // e.g., "#36a64f" + + public static Attachment CreateAttachment(ContextBlock[] blocks, Color color) + { + return new Attachment { blocks = blocks, color = ColorToString(color) }; + } + + private static string ColorToString(Color color) + { + var r = ((int)(color.r * 255)).ToString("x2"); + var g = ((int)(color.g * 255)).ToString("x2"); + var b = ((int)(color.b * 255)).ToString("x2"); + return $"#{r}{g}{b}"; + } + } +} diff --git a/Runtime/Reporters/Slack/Payloads/Text/Attachment.cs.meta b/Runtime/Reporters/Slack/Payloads/Text/Attachment.cs.meta new file mode 100644 index 0000000..f1627e9 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/Attachment.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 80289286bf8f4ff59a0a45e4ef77430b +timeCreated: 1730595600 \ No newline at end of file diff --git a/Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs b/Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs new file mode 100644 index 0000000..6ad0cb2 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System; + +namespace DeNA.Anjin.Reporters.Slack.Payloads.Text +{ + /// + /// Context Block. + /// + /// + /// Abstract classes and interfaces are not available. JsonUtility.ToJson can not process them. + /// + /// + [Serializable] + public class ContextBlock + { + public string type = "context"; + public Text[] elements; // Note: Text or Image + + public static ContextBlock CreateContextBlock(Text[] elements) + { + return new ContextBlock { elements = elements }; + } + } +} diff --git a/Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs.meta b/Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs.meta new file mode 100644 index 0000000..f30e42f --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/ContextBlock.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 62cb8348edac474aa61826a37bf2fef4 +timeCreated: 1730595610 \ No newline at end of file diff --git a/Runtime/Reporters/Slack/Payloads/Text/Payload.cs b/Runtime/Reporters/Slack/Payloads/Text/Payload.cs new file mode 100644 index 0000000..726cf15 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/Payload.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System; +using UnityEngine; + +namespace DeNA.Anjin.Reporters.Slack.Payloads.Text +{ + /// + /// Slack post payload. + /// + /// + [Serializable] + public class Payload + { + public string channel; + public string text; + public Attachment[] attachments; + + // ReSharper disable InconsistentNaming + public readonly string link_names = "1"; + public string thread_ts; + // ReSharper restore InconsistentNaming + + public string ToJson(bool prettyPrint = false) + { + return JsonUtility.ToJson(this, prettyPrint); + } + + public static Payload CreatePayload(string channel, string text, Attachment[] attachments, string ts = null) + { + return new Payload { channel = channel, text = text, attachments = attachments, thread_ts = ts }; + } + } +} diff --git a/Runtime/Reporters/Slack/Payloads/Text/Payload.cs.meta b/Runtime/Reporters/Slack/Payloads/Text/Payload.cs.meta new file mode 100644 index 0000000..8da66c6 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/Payload.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f56aaeaf4eb5455381845e7be3290469 +timeCreated: 1730591986 \ No newline at end of file diff --git a/Runtime/Reporters/Slack/Payloads/Text/Text.cs b/Runtime/Reporters/Slack/Payloads/Text/Text.cs new file mode 100644 index 0000000..2cb92af --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/Text.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System; + +namespace DeNA.Anjin.Reporters.Slack.Payloads.Text +{ + /// + /// Text object. + /// + /// + /// Abstract classes and interfaces are not available. JsonUtility.ToJson can not process them. + /// + /// + [Serializable] + public class Text + { + public string type; // "plain_text" or "mrkdwn" + public string text; + + // Note: bool emoji is only usable when `type` is "plain_text". + // Note: bool verbatim is only usable when `type` is "mrkdwn". + + public static Text CreatePlainText(string text) + { + return new Text { type = "plain_text", text = text }; + } + + public static Text CreateMarkdownText(string text) + { + return new Text { type = "mrkdwn", text = text }; + } + } +} diff --git a/Runtime/Reporters/Slack/Payloads/Text/Text.cs.meta b/Runtime/Reporters/Slack/Payloads/Text/Text.cs.meta new file mode 100644 index 0000000..8be0f65 --- /dev/null +++ b/Runtime/Reporters/Slack/Payloads/Text/Text.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7bf2083372b643b985a90a68fcca8917 +timeCreated: 1730596101 \ No newline at end of file diff --git a/Runtime/Reporters/SlackAPI.cs b/Runtime/Reporters/Slack/SlackAPI.cs similarity index 52% rename from Runtime/Reporters/SlackAPI.cs rename to Runtime/Reporters/Slack/SlackAPI.cs index 0556893..3562b61 100644 --- a/Runtime/Reporters/SlackAPI.cs +++ b/Runtime/Reporters/Slack/SlackAPI.cs @@ -1,13 +1,14 @@ -// Copyright (c) 2023 DeNA Co., Ltd. +// Copyright (c) 2023-2024 DeNA Co., Ltd. // This software is released under the MIT License. +using System; using System.Text.RegularExpressions; -using System.Threading; using Cysharp.Threading.Tasks; +using DeNA.Anjin.Reporters.Slack.Payloads.Text; using UnityEngine; using UnityEngine.Networking; -namespace DeNA.Anjin.Reporters +namespace DeNA.Anjin.Reporters.Slack { /// /// Slack post message API response @@ -48,25 +49,57 @@ public class SlackAPI /// /// Slack token /// Send target channels - /// Message body + /// Lead text (out of attachment) + /// Message body text (into attachment) + /// Attachment color /// Thread timestamp - /// Cancellation token /// - public virtual async UniTask Post(string token, string channel, string text, - string ts = null, CancellationToken cancellationToken = default) + public virtual async UniTask Post(string token, string channel, string lead, string message, + Color color, string ts = null) { const string URL = URLBase + "chat.postMessage"; - var form = new WWWForm(); - form.AddField("token", token); - form.AddField("channel", channel); - form.AddField("text", text); - form.AddField("link_names", "1"); - if (ts != null) - { - form.AddField("thread_ts", ts); - } + var payload = Payload.CreatePayload( + channel, + lead, + new[] + { + Attachment.CreateAttachment( + new[] + { + ContextBlock.CreateContextBlock( + new[] { Text.CreateMarkdownText(message) } + ) + }, + color + ) + }, + ts + ); - return await Post(URL, form); + return await Post(URL, token, payload); + } + + /// + /// Post text message to thread. + /// Without attachments. + /// + /// Slack token + /// Send target channels + /// Text (out of attachment) + /// Thread timestamp + /// + public virtual async UniTask PostWithoutAttachments(string token, string channel, string text, + string ts = null) + { + const string URL = URLBase + "chat.postMessage"; + var payload = Payload.CreatePayload( + channel, + text, + null, + ts + ); + + return await Post(URL, token, payload); } /// @@ -76,10 +109,9 @@ public virtual async UniTask Post(string token, string channel, s /// Send target channels /// Image (screenshot) /// Thread timestamp - /// Cancellation token /// public virtual async UniTask Post(string token, string channel, byte[] image, - string ts = null, CancellationToken cancellationToken = default) + string ts = null) { const string URL = URLBase + "files.upload"; var form = new WWWForm(); @@ -94,12 +126,51 @@ public virtual async UniTask Post(string token, string channel, b return await Post(URL, form); } + [Obsolete] private static async UniTask Post(string url, WWWForm form) { using (var www = UnityWebRequest.Post(url, form)) { await www.SendWebRequest(); +#if UNITY_2020_2_OR_NEWER + if (www.result != UnityWebRequest.Result.Success) + { + Debug.LogWarning($"{www.responseCode}"); + return new SlackResponse(false, null); + } +#else + if (www.isNetworkError || www.isHttpError) + { + Debug.LogWarning($"{www.responseCode}"); + return new SlackResponse(false, null); + } +#endif + + if (www.downloadHandler.text.Contains("\"ok\":false")) + { + Debug.LogWarning($"{www.downloadHandler.text}"); + return new SlackResponse(false, null); + } + + string ts = null; + var tsMatch = new Regex("\"ts\":\"(\\d+\\.\\d+)\"").Match(www.downloadHandler.text); + if (tsMatch.Success && tsMatch.Length > 0) + { + ts = tsMatch.Groups[1].Value; + } + + return new SlackResponse(true, ts); + } + } + + private static async UniTask Post(string url, string token, Payload payload) + { + using (var www = UnityWebRequest.Post(url, payload.ToJson(), "application/json; charset=utf-8")) + { + www.SetRequestHeader("Authorization", $"Bearer {token}"); + await www.SendWebRequest(); + #if UNITY_2020_2_OR_NEWER if (www.result != UnityWebRequest.Result.Success) { diff --git a/Runtime/Reporters/SlackAPI.cs.meta b/Runtime/Reporters/Slack/SlackAPI.cs.meta similarity index 100% rename from Runtime/Reporters/SlackAPI.cs.meta rename to Runtime/Reporters/Slack/SlackAPI.cs.meta diff --git a/Runtime/Reporters/SlackMessageSender.cs b/Runtime/Reporters/Slack/SlackMessageSender.cs similarity index 81% rename from Runtime/Reporters/SlackMessageSender.cs rename to Runtime/Reporters/Slack/SlackMessageSender.cs index 301e393..59f8adf 100644 --- a/Runtime/Reporters/SlackMessageSender.cs +++ b/Runtime/Reporters/Slack/SlackMessageSender.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2023 DeNA Co., Ltd. +// Copyright (c) 2023-2024 DeNA Co., Ltd. // This software is released under the MIT License. using System.Collections.Generic; @@ -7,7 +7,7 @@ using Cysharp.Threading.Tasks; using UnityEngine; -namespace DeNA.Anjin.Reporters +namespace DeNA.Anjin.Reporters.Slack { /// /// An interface for Slack message senders. The derived class of this interface must define message format, and @@ -23,8 +23,9 @@ public interface ISlackMessageSender /// Slack Channel to send notification /// Sub team IDs to mention /// Whether adding @here or not - /// Log message + /// Log message /// Stack trace + /// Attachment color /// With screenshot /// Cancellation token /// @@ -33,8 +34,9 @@ UniTask Send( string slackChannel, IEnumerable mentionSubTeamIDs, bool addHereInSlackMessage, - string logString, + string message, string stackTrace, + Color color, bool withScreenshot, CancellationToken cancellationToken = default ); @@ -66,8 +68,9 @@ public async UniTask Send( string slackChannel, IEnumerable mentionSubTeamIDs, bool addHereInSlackMessage, - string logString, + string message, string stackTrace, + Color color, bool withScreenshot, CancellationToken cancellationToken = default ) @@ -77,15 +80,16 @@ public async UniTask Send( return; } - var title = CreateTitle(logString, mentionSubTeamIDs, addHereInSlackMessage); + var lead = CreateLead(mentionSubTeamIDs, addHereInSlackMessage); await UniTask.SwitchToMainThread(); var postTitleTask = await _slackAPI.Post( slackToken, slackChannel, - title, - cancellationToken: cancellationToken + lead, + message, + color ); if (!postTitleTask.Success) { @@ -107,8 +111,7 @@ public async UniTask Send( slackToken, slackChannel, withoutAlpha.EncodeToPNG(), - postTitleTask.Ts, - cancellationToken + postTitleTask.Ts ); if (!postScreenshotTask.Success) { @@ -116,15 +119,14 @@ public async UniTask Send( } } - if (stackTrace != null && stackTrace.Length > 0) + if (!string.IsNullOrEmpty(stackTrace)) { - var body = CreateStackTrace(stackTrace); - await _slackAPI.Post(slackToken, slackChannel, body, postTitleTask.Ts, cancellationToken); + await _slackAPI.PostWithoutAttachments(slackToken, slackChannel, stackTrace, postTitleTask.Ts); } } - private static string CreateTitle(string logString, IEnumerable mentionSubTeamIDs, bool withHere) + private static string CreateLead(IEnumerable mentionSubTeamIDs, bool withHere) { var sb = new StringBuilder(); foreach (var s in mentionSubTeamIDs) @@ -141,17 +143,9 @@ private static string CreateTitle(string logString, IEnumerable mentionS sb.Append(" "); } - sb.Append(logString); return sb.ToString(); } - - private static string CreateStackTrace( string stackTrace) - { - return $"```{stackTrace}```"; - // TODO: Split every 4k characters and quote. - } - private class CoroutineRunner : MonoBehaviour { } diff --git a/Runtime/Reporters/SlackMessageSender.cs.meta b/Runtime/Reporters/Slack/SlackMessageSender.cs.meta similarity index 100% rename from Runtime/Reporters/SlackMessageSender.cs.meta rename to Runtime/Reporters/Slack/SlackMessageSender.cs.meta diff --git a/Runtime/Reporters/SlackReporter.cs b/Runtime/Reporters/SlackReporter.cs index ca2fa9f..a719d21 100644 --- a/Runtime/Reporters/SlackReporter.cs +++ b/Runtime/Reporters/SlackReporter.cs @@ -3,6 +3,7 @@ using System.Threading; using Cysharp.Threading.Tasks; +using DeNA.Anjin.Reporters.Slack; using DeNA.Anjin.Settings; using UnityEngine; @@ -101,13 +102,12 @@ public override async UniTask PostReportAsync( return; } - var withScreenshot = (exitCode == ExitCode.Normally) - ? withScreenshotOnNormally - : withScreenshotOnError; var messageBody = MessageBuilder.BuildWithTemplate((exitCode == ExitCode.Normally) ? messageBodyTemplateOnNormally : messageBodyTemplateOnError, message); + var color = (exitCode == ExitCode.Normally) ? colorOnNormally : colorOnError; + var withScreenshot = (exitCode == ExitCode.Normally) ? withScreenshotOnNormally : withScreenshotOnError; OverwriteByCommandlineArguments(); if (string.IsNullOrEmpty(slackToken) || string.IsNullOrEmpty(slackChannels)) @@ -124,7 +124,7 @@ public override async UniTask PostReportAsync( return; } - await PostReportAsync(slackChannel, messageBody, stackTrace, withScreenshot, cancellationToken); + await PostReportAsync(slackChannel, messageBody, stackTrace, color, withScreenshot, cancellationToken); } } @@ -132,6 +132,7 @@ private async UniTask PostReportAsync( string slackChannel, string message, string stackTrace, + Color color, bool withScreenshot, CancellationToken cancellationToken = default ) @@ -143,6 +144,7 @@ await _sender.Send( addHereInSlackMessage, message, stackTrace, + color, withScreenshot, cancellationToken ); diff --git a/Runtime/Utilities/LogMessageHandler.cs b/Runtime/Utilities/LogMessageHandler.cs index a8630c0..f0885c3 100644 --- a/Runtime/Utilities/LogMessageHandler.cs +++ b/Runtime/Utilities/LogMessageHandler.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Cysharp.Threading.Tasks; using DeNA.Anjin.Reporters; +using DeNA.Anjin.Reporters.Slack; using DeNA.Anjin.Settings; using UnityEngine; diff --git a/Tests/Runtime/Reporters/Slack.meta b/Tests/Runtime/Reporters/Slack.meta new file mode 100644 index 0000000..39048b2 --- /dev/null +++ b/Tests/Runtime/Reporters/Slack.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b64301074d6a439399fb517fb55742bb +timeCreated: 1730624945 \ No newline at end of file diff --git a/Tests/Runtime/Reporters/Slack/Payloads.meta b/Tests/Runtime/Reporters/Slack/Payloads.meta new file mode 100644 index 0000000..99e1cfa --- /dev/null +++ b/Tests/Runtime/Reporters/Slack/Payloads.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d0aa6830d3f6439a8d180122f6a384d9 +timeCreated: 1730624966 \ No newline at end of file diff --git a/Tests/Runtime/Reporters/Slack/Payloads/Text.meta b/Tests/Runtime/Reporters/Slack/Payloads/Text.meta new file mode 100644 index 0000000..c6ac4c3 --- /dev/null +++ b/Tests/Runtime/Reporters/Slack/Payloads/Text.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e05189ecd4cc41da956dc81231e9f79c +timeCreated: 1730649235 \ No newline at end of file diff --git a/Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs b/Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs new file mode 100644 index 0000000..62d9a7d --- /dev/null +++ b/Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs @@ -0,0 +1,78 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using NUnit.Framework; +using UnityEngine; + +namespace DeNA.Anjin.Reporters.Slack.Payloads.Text +{ + [TestFixture] + public class PayloadTest + { + [Test] + public void ToJson_PlainText() + { + var payload = Payload.CreatePayload( + "#channel", + "lead", + new Attachment[] + { + Attachment.CreateAttachment( + new ContextBlock[] + { + ContextBlock.CreateContextBlock( + new Text[] { Text.CreatePlainText("message") } + ) + }, + Color.magenta + ) + }, + "ts" + ); + var json = payload.ToJson(true); + Debug.Log(json); + + Assert.That(json, Is.EqualTo(@"{ + ""channel"": ""#channel"", + ""text"": ""lead"", + ""attachments"": [ + { + ""blocks"": [ + { + ""type"": ""context"", + ""elements"": [ + { + ""type"": ""plain_text"", + ""text"": ""message"" + } + ] + } + ], + ""color"": ""#ff00ff"" + } + ], + ""thread_ts"": ""ts"" +}")); + } + + [Test] + public void ToJson_PlainTextWithoutText() + { + var payload = Payload.CreatePayload( + "#channel", + null, // focus of this test + null, // it becomes noise + "ts" + ); + var json = payload.ToJson(true); + Debug.Log(json); + + Assert.That(json, Is.EqualTo(@"{ + ""channel"": ""#channel"", + ""text"": """", + ""attachments"": [], + ""thread_ts"": ""ts"" +}")); + } + } +} diff --git a/Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs.meta b/Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs.meta new file mode 100644 index 0000000..9a6400b --- /dev/null +++ b/Tests/Runtime/Reporters/Slack/Payloads/Text/PayloadTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 418151d61adf4c538b5e3f132c8f49ef +timeCreated: 1730625027 \ No newline at end of file diff --git a/Tests/Runtime/Reporters/SlackMessageSenderTest.cs b/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs similarity index 71% rename from Tests/Runtime/Reporters/SlackMessageSenderTest.cs rename to Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs index 6946f5d..ba2ad74 100644 --- a/Tests/Runtime/Reporters/SlackMessageSenderTest.cs +++ b/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs @@ -9,8 +9,9 @@ using DeNA.Anjin.TestDoubles; using NUnit.Framework; using TestHelper.Attributes; +using UnityEngine; -namespace DeNA.Anjin.Reporters +namespace DeNA.Anjin.Reporters.Slack { [TestFixture] public class SlackMessageSenderTest @@ -28,6 +29,7 @@ await sender.Send( false, "MESSAGE", "STACKTRACE", + Color.magenta, false ); @@ -36,19 +38,24 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", "MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "lead", "" }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected), Format(actual)); } - + [Test] public async Task WithMention() { @@ -58,10 +65,11 @@ public async Task WithMention() await sender.Send( "TOKEN", "CHANNEL", - new []{"MENTION1", "MENTION2"}, + new[] { "MENTION1", "MENTION2" }, false, "MESSAGE", "STACKTRACE", + Color.magenta, false ); @@ -70,19 +78,24 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", " MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "lead", " " }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected), Format(actual)); } - + [Test] [FocusGameView] public async Task WithScreenshot() @@ -93,10 +106,11 @@ public async Task WithScreenshot() await sender.Send( "TOKEN", "CHANNEL", - new []{"MENTION1", "MENTION2"}, + new[] { "MENTION1", "MENTION2" }, false, "MESSAGE", "STACKTRACE", + Color.magenta, true ); @@ -105,23 +119,33 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", " MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "lead", " " }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", "IMAGE" }, { "ts", "1" } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "lead", null }, + { "message", "IMAGE" }, + { "color", "RGBA(0.000, 0.000, 0.000, 0.000)" }, // default + { "ts", "1" } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected)); } - + [Test] public async Task WithAtHere() { @@ -135,6 +159,7 @@ await sender.Send( true, "MESSAGE", "STACKTRACE", + Color.magenta, false ); @@ -143,20 +168,24 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", " MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "lead", " " }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected), Format(actual)); } - private static string Format(List> dicts) { var sb = new StringBuilder(); @@ -175,8 +204,10 @@ private static string Format(List> dicts) sb.Append(value); sb.AppendLine(","); } + sb.AppendLine("\t},"); } + sb.AppendLine("]"); return sb.ToString(); } diff --git a/Tests/Runtime/Reporters/SlackMessageSenderTest.cs.meta b/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs.meta similarity index 100% rename from Tests/Runtime/Reporters/SlackMessageSenderTest.cs.meta rename to Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs.meta diff --git a/Tests/Runtime/Reporters/SlackReporterTest.cs b/Tests/Runtime/Reporters/SlackReporterTest.cs index 23e9d59..b2794ad 100644 --- a/Tests/Runtime/Reporters/SlackReporterTest.cs +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs @@ -56,7 +56,7 @@ public async Task PostReportAsync_OnError_Sent() Assert.That(_spy.CalledList[1].SlackChannel, Is.EqualTo("qa")); Assert.That(_spy.CalledList[0].MentionSubTeamIDs, Is.EquivalentTo(new[] { "alpha", "bravo" })); Assert.That(_spy.CalledList[0].AddHereInSlackMessage, Is.True); - Assert.That(_spy.CalledList[0].LogString, Is.EqualTo("Error terminate with message")); // use OnNormally + Assert.That(_spy.CalledList[0].Message, Is.EqualTo("Error terminate with message")); // use OnNormally Assert.That(_spy.CalledList[0].StackTrace, Is.EqualTo("stack trace")); Assert.That(_spy.CalledList[0].WithScreenshot, Is.False); // use OnError } @@ -74,7 +74,7 @@ public async Task PostReportAsync_OnNormallyAndPostOnNormally_Sent() await _sut.PostReportAsync("message", string.Empty, ExitCode.Normally); Assert.That(_spy.CalledList.Count, Is.EqualTo(1)); - Assert.That(_spy.CalledList[0].LogString, Is.EqualTo("Normally terminate with message")); // use OnNormally + Assert.That(_spy.CalledList[0].Message, Is.EqualTo("Normally terminate with message")); // use OnNormally Assert.That(_spy.CalledList[0].WithScreenshot, Is.True); // use OnNormally } diff --git a/Tests/Runtime/TestDoubles/SpySlackAPI.cs b/Tests/Runtime/TestDoubles/SpySlackAPI.cs index cceb745..9cc377d 100644 --- a/Tests/Runtime/TestDoubles/SpySlackAPI.cs +++ b/Tests/Runtime/TestDoubles/SpySlackAPI.cs @@ -2,9 +2,9 @@ // This software is released under the MIT License. using System.Collections.Generic; -using System.Threading; using Cysharp.Threading.Tasks; -using DeNA.Anjin.Reporters; +using DeNA.Anjin.Reporters.Slack; +using UnityEngine; namespace DeNA.Anjin.TestDoubles { @@ -12,25 +12,42 @@ public class SpySlackAPI : SlackAPI { public List> Arguments { get; } = new List>(); - public override async UniTask Post(string token, string channel, string text, - string ts = null, CancellationToken cancellationToken = default) + public override async UniTask Post(string token, string channel, string lead, string message, + Color color, string ts = null) { var args = new Dictionary(); args.Add("token", token); args.Add("channel", channel); - args.Add("message", text); + args.Add("lead", lead); + args.Add("message", message); + args.Add("color", color.ToString()); args.Add("ts", ts); Arguments.Add(args); - await UniTask.NextFrame(cancellationToken); + await UniTask.NextFrame(); + + return new SlackResponse(true, "1"); + } + + public override async UniTask PostWithoutAttachments(string token, string channel, string text, + string ts = null) + { + var args = new Dictionary(); + args.Add("token", token); + args.Add("channel", channel); + args.Add("text", text); + args.Add("ts", ts); + Arguments.Add(args); + + await UniTask.NextFrame(); return new SlackResponse(true, "1"); } public override async UniTask Post(string token, string channel, byte[] image, - string ts = null, CancellationToken cancellationToken = default) + string ts = null) { - return await Post(token, channel, "IMAGE", ts, cancellationToken); + return await Post(token, channel, null, "IMAGE", default, ts); } } } diff --git a/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs index ae57379..a205d78 100644 --- a/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs +++ b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs @@ -4,7 +4,8 @@ using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; -using DeNA.Anjin.Reporters; +using DeNA.Anjin.Reporters.Slack; +using UnityEngine; namespace DeNA.Anjin.TestDoubles { @@ -16,15 +17,16 @@ public struct CallArguments public string SlackChannel { get; set; } public IEnumerable MentionSubTeamIDs { get; set; } public bool AddHereInSlackMessage { get; set; } - public string LogString { get; set; } + public string Message { get; set; } public string StackTrace { get; set; } + public Color Color { get; set; } public bool WithScreenshot { get; set; } } public List CalledList { get; } = new List(); public async UniTask Send(string slackToken, string slackChannel, IEnumerable mentionSubTeamIDs, - bool addHereInSlackMessage, string logString, string stackTrace, bool withScreenshot, + bool addHereInSlackMessage, string message, string stackTrace, Color color, bool withScreenshot, CancellationToken cancellationToken = default) { var called = new CallArguments @@ -33,8 +35,9 @@ public async UniTask Send(string slackToken, string slackChannel, IEnumerable Date: Mon, 4 Nov 2024 02:48:18 +0900 Subject: [PATCH 11/12] Add lead, mention, and here for normally termination --- Editor/Localization/ja.po | 94 +++++++++++++------ Editor/UI/Reporters/SlackReporterEditor.cs | 84 +++++++++++++---- README.md | 18 ++-- README_ja.md | 14 ++- Runtime/Reporters/MessageBuilder.cs | 2 +- Runtime/Reporters/Slack/SlackAPI.cs | 6 +- Runtime/Reporters/Slack/SlackMessageSender.cs | 16 +++- Runtime/Reporters/SlackReporter.cs | 72 ++++++++++---- .../Reporters/Slack/SlackMessageSenderTest.cs | 14 ++- Tests/Runtime/Reporters/SlackReporterTest.cs | 9 ++ Tests/Runtime/TestDoubles/SpySlackAPI.cs | 4 +- .../TestDoubles/SpySlackMessageSender.cs | 6 +- 12 files changed, 237 insertions(+), 102 deletions(-) diff --git a/Editor/Localization/ja.po b/Editor/Localization/ja.po index 56ac371..1702c25 100644 --- a/Editor/Localization/ja.po +++ b/Editor/Localization/ja.po @@ -478,43 +478,51 @@ msgid "Slack Token" msgstr "Slackトークン" # slackToken tooltip -msgid "Slack API token" -msgstr "Slack通知に使用するWeb APIトークン(省略時は通知されない)" +msgid "Slack API token. If omitted, it will not be sent." +msgstr "Slack通知に使用するWeb APIトークン(省略時は送信されません)" # slackChannels msgid "Slack Channels" msgstr "Slackチャンネル" # slackChannels tooltip -msgid "Slack channels to send notification" -msgstr "Slack通知を送るチャンネル(省略時は通知されない。現時点では1チャンネルのみ有効ですが、将来的にはカンマ区切りで複数指定対応予定)" +msgid "Comma-separated Slack channels to post. If omitted, it will not be sent." +msgstr "Slack通知を送るチャンネルをカンマ区切りで指定します(省略時は送信されません)" -# Header: Slack Mention Settings -msgid "Slack Mention Settings" -msgstr "Slackメンション設定" +# Header: Error Report Settings +msgid "Error Report Settings" +msgstr "エラーレポート設定" # mentionSubTeamIDs -msgid "Sub Team IDs to Mention" -msgstr "メンション宛先" +msgid "Mention Sub Team IDs" +msgstr "メンション宛先チームID" # mentionSubTeamIDs tooltip -msgid "Sub team IDs to mention (comma separates)" -msgstr "Slack通知メッセージでメンションするチームのIDをカンマ区切りで指定します" +msgid "Comma-separated sub team IDs to mention when posting error reports." +msgstr "エラー終了時に送信する通知をメンションするチームのIDをカンマ区切りで指定します" # addHereInSlackMessage -msgid "Add @here Into Slack Message" -msgstr "@hereをメッセージにつける" +msgid "Add @here" +msgstr "@hereをつける" # addHereInSlackMessage tooltip -msgid "Whether adding @here into Slack messages or not" -msgstr "Slack通知メッセージに@hereを付けます" +msgid "Add @here to the post when posting error reports." +msgstr "エラー終了時に送信する通知に@hereを付けます" + +# leadTextOnError +msgid "Lead Text" +msgstr "リード文" + +# leadTextOnError tooltip +msgid "Lead text for error reports. It is used in OS notifications. You can specify placeholders like a \"{message}\"; see the README for more information." +msgstr "エラー終了時に送信する通知のリード文。OSの通知に使用されます。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" # messageBodyTemplateOnError -msgid "Message Body Template" -msgstr "メッセージ本文テンプレート" +msgid "Message" +msgstr "メッセージ" # messageBodyTemplateOnError tooltip -msgid "Message body template when posting an error terminated report. You can specify placeholders like a "{message}"; see the README for more information." +msgid "Message body template for error reports. You can specify placeholders like a \"{message}\"; see the README for more information." msgstr "エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" # colorOnError @@ -522,7 +530,7 @@ msgid "Color" msgstr "色" # colorOnError tooltip -msgid "Attachment color when posting an error terminated report" +msgid "Attachments color for error reports." msgstr "エラー終了時に送信するメッセージのアタッチメントに指定する色" # withScreenshotOnError @@ -530,23 +538,51 @@ msgid "Screenshot" msgstr "スクリーンショット" # withScreenshotOnError tooltip -msgid "Take a screenshot when posting an error terminated report" +msgid "Take a screenshot for error reports." msgstr "エラー終了時にスクリーンショットを撮影します" +# Header: Completion Report Settings +msgid "Completion Report Settings" +msgstr "正常終了レポート設定" + # postOnNormally -msgid "Normally Terminated Report" -msgstr "正常終了時にもレポート" +msgid "Post if completes" +msgstr "正常終了時にも送信" # postOnNormally tooltip -msgid "Post a report if normally terminates." -msgstr "正常終了時にもレポートをポストします" +msgid "Also post a report if completed autopilot normally." +msgstr "正常終了時にもレポートを送信します" + +# mentionSubTeamIDs (same as mentionSubTeamIDsOnError) +msgid "Mention Sub Team IDs" +msgstr "メンション宛先チームID" + +# mentionSubTeamIDs tooltip +msgid "Comma-separated sub team IDs to mention when posting completion reports." +msgstr "正常終了時に送信する通知をメンションするチームのIDをカンマ区切りで指定します" + +# addHereInSlackMessage (same as addHereInSlackMessageOnError) +msgid "Add @here" +msgstr "@hereをつける" + +# addHereInSlackMessage tooltip +msgid "Add @here to the post when posting completion reports." +msgstr "正常終了時に送信する通知に@hereを付けます" + +# leadTextOnError (same as leadTextOnErrorOnError) +msgid "Lead Text" +msgstr "リード文" + +# leadTextOnError tooltip +msgid "Lead text for completion reports. It is used in OS notifications. You can specify placeholders like a \"{message}\"; see the README for more information." +msgstr "正常終了時に送信する通知のリード文。OSの通知に使用されます。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" # messageBodyTemplateOnNormally (same as messageBodyTemplateOnError) -msgid "Message Body Template" -msgstr "メッセージ本文テンプレート" +msgid "Message" +msgstr "メッセージ" # messageBodyTemplateOnNormally tooltip -msgid "Message body template when posting a normally terminated report. You can specify placeholders like a "{message}"; see the README for more information." +msgid "Message body template for completion reports. You can specify placeholders like a \"{message}\"; see the README for more information." msgstr "正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" # colorOnNormally @@ -554,7 +590,7 @@ msgid "Color" msgstr "色" # colorOnNormally tooltip -msgid "Attachment color when posting a normally terminated report" +msgid "Attachments color for completion reports." msgstr "正常終了時に送信するメッセージのアタッチメントに指定する色" # withScreenshotOnNormally (same as withScreenshotOnError) @@ -562,5 +598,5 @@ msgid "Screenshot" msgstr "スクリーンショット" # withScreenshotOnNormally tooltip -msgid "Take a screenshot when posting a normally terminated report" +msgid "Take a screenshot for completion reports." msgstr "正常終了時にスクリーンショットを撮影します" diff --git a/Editor/UI/Reporters/SlackReporterEditor.cs b/Editor/UI/Reporters/SlackReporterEditor.cs index 1286f0d..8ba01c1 100644 --- a/Editor/UI/Reporters/SlackReporterEditor.cs +++ b/Editor/UI/Reporters/SlackReporterEditor.cs @@ -14,6 +14,8 @@ namespace DeNA.Anjin.Editor.UI.Reporters public class SlackReporterEditor : UnityEditor.Editor { private const float SpacerPixels = 10f; + private const float SpacerSectionPixels = 18f; + private const float SpacerUnderHeaderPixels = 8f; // @formatter:off private static readonly string s_description = L10n.Tr("Description"); @@ -22,57 +24,81 @@ public class SlackReporterEditor : UnityEditor.Editor private GUIContent _descriptionGUIContent; private static readonly string s_slackToken = L10n.Tr("Slack Token"); - private static readonly string s_slackTokenTooltip = L10n.Tr("Slack API token"); + private static readonly string s_slackTokenTooltip = L10n.Tr("Slack API token. If omitted, it will not be sent."); private SerializedProperty _slackTokenProp; private GUIContent _slackTokenGUIContent; private static readonly string s_slackChannels = L10n.Tr("Slack Channels"); - private static readonly string s_slackChannelsTooltip = L10n.Tr("Slack channels to send notification"); + private static readonly string s_slackChannelsTooltip = L10n.Tr("Comma-separated Slack channels to post. If omitted, it will not be sent."); private SerializedProperty _slackChannelsProp; private GUIContent _slackChannelsGUIContent; - private static readonly string s_mentionSubTeamIDs = L10n.Tr("Sub Team IDs to Mention"); - private static readonly string s_mentionSubTeamIDsTooltip = L10n.Tr("Sub team IDs to mention (comma separates)"); + private static readonly string s_errorSettingsHeader = L10n.Tr("Error Report Settings"); + + private static readonly string s_mentionSubTeamIDs = L10n.Tr("Mention Sub Team IDs"); + private static readonly string s_mentionSubTeamIDsTooltip = L10n.Tr("Comma-separated sub team IDs to mention when posting error reports."); private SerializedProperty _mentionSubTeamIDsProp; private GUIContent _mentionSubTeamIDsGUIContent; - private static readonly string s_addHereInSlackMessage = L10n.Tr("Add @here Into Slack Message"); - private static readonly string s_addHereInSlackMessageTooltip = L10n.Tr("Whether adding @here into Slack messages or not"); + private static readonly string s_addHereInSlackMessage = L10n.Tr("Add @here"); + private static readonly string s_addHereInSlackMessageTooltip = L10n.Tr("Add @here to the post when posting error reports."); private SerializedProperty _addHereInSlackMessageProp; private GUIContent _addHereInSlackMessageGUIContent; - private static readonly string s_messageBodyTemplateOnError = L10n.Tr("Message Body Template"); - private static readonly string s_messageBodyTemplateOnErrorTooltip = L10n.Tr("Message body template when posting an error terminated report. You can specify placeholders like a \"{message}\"; see the README for more information."); + private static readonly string s_leadTextOnError = L10n.Tr("Lead Text"); + private static readonly string s_leadTextOnErrorTooltip = L10n.Tr("Lead text for error reports. It is used in OS notifications. You can specify placeholders like a \"{message}\"; see the README for more information."); + private SerializedProperty _leadTextOnErrorProp; + private GUIContent _leadTextOnErrorGUIContent; + + private static readonly string s_messageBodyTemplateOnError = L10n.Tr("Message"); + private static readonly string s_messageBodyTemplateOnErrorTooltip = L10n.Tr("Message body template for error reports. You can specify placeholders like a \"{message}\"; see the README for more information."); private SerializedProperty _messageBodyTemplateOnErrorProp; private GUIContent _messageBodyTemplateOnErrorGUIContent; private static readonly string s_colorOnError = L10n.Tr("Color"); - private static readonly string s_colorOnErrorTooltip = L10n.Tr("Attachment color when posting an error terminated report"); + private static readonly string s_colorOnErrorTooltip = L10n.Tr("Attachments color for error reports."); private SerializedProperty _colorOnErrorProp; private GUIContent _colorOnErrorGUIContent; private static readonly string s_withScreenshotOnError = L10n.Tr("Screenshot"); - private static readonly string s_withScreenshotOnErrorTooltip = L10n.Tr("Take a screenshot when posting an error terminated report"); + private static readonly string s_withScreenshotOnErrorTooltip = L10n.Tr("Take a screenshot for error reports."); private SerializedProperty _withScreenshotOnErrorProp; private GUIContent _withScreenshotOnErrorGUIContent; - private static readonly string s_postOnNormally = L10n.Tr("Normally Terminated Report"); - private static readonly string s_postOnNormallyTooltip = L10n.Tr("Post a report if normally terminates."); + private static readonly string s_normallyHeader = L10n.Tr("Completion Report Settings"); + + private static readonly string s_postOnNormally = L10n.Tr("Post if completes"); + private static readonly string s_postOnNormallyTooltip = L10n.Tr("Also post a report if completed autopilot normally."); private SerializedProperty _postOnNormallyProp; private GUIContent _postOnNormallyGUIContent; - private static readonly string s_messageBodyTemplateOnNormally = L10n.Tr("Message Body Template"); - private static readonly string s_messageBodyTemplateOnNormallyTooltip = L10n.Tr("Message body template when posting a normally terminated report. You can specify placeholders like a \"{message}\"; see the README for more information."); + private static readonly string s_mentionSubTeamIDsOnNormally = L10n.Tr("Mention Sub Team IDs"); + private static readonly string s_mentionSubTeamIDsOnNormallyTooltip = L10n.Tr("Comma-separated sub team IDs to mention when posting completion reports."); + private SerializedProperty _mentionSubTeamIDsOnNormallyProp; + private GUIContent _mentionSubTeamIDsOnNormallyGUIContent; + + private static readonly string s_addHereInSlackMessageOnNormally = L10n.Tr("Add @here"); + private static readonly string s_addHereInSlackMessageOnNormallyTooltip = L10n.Tr("Add @here to the post when posting completion reports."); + private SerializedProperty _addHereInSlackMessageOnNormallyProp; + private GUIContent _addHereInSlackMessageOnNormallyGUIContent; + + private static readonly string s_leadTextOnNormally = L10n.Tr("Lead Text"); + private static readonly string s_leadTextOnNormallyTooltip = L10n.Tr("Lead text for completion reports. It is used in OS notifications. You can specify placeholders like a \"{message}\"; see the README for more information."); + private SerializedProperty _leadTextOnNormallyProp; + private GUIContent _leadTextOnNormallyGUIContent; + + private static readonly string s_messageBodyTemplateOnNormally = L10n.Tr("Message"); + private static readonly string s_messageBodyTemplateOnNormallyTooltip = L10n.Tr("Message body template for completion reports. You can specify placeholders like a \"{message}\"; see the README for more information."); private SerializedProperty _messageBodyTemplateOnNormallyProp; private GUIContent _messageBodyTemplateOnNormallyGUIContent; private static readonly string s_colorOnNormally = L10n.Tr("Color"); - private static readonly string s_colorOnNormallyTooltip = L10n.Tr("Attachment color when posting a normally terminated report"); + private static readonly string s_colorOnNormallyTooltip = L10n.Tr("Attachments color for completion reports."); private SerializedProperty _colorOnNormallyProp; private GUIContent _colorOnNormallyGUIContent; private static readonly string s_withScreenshotOnNormally = L10n.Tr("Screenshot"); - private static readonly string s_withScreenshotOnNormallyTooltip = L10n.Tr("Take a screenshot when posting a normally terminated report"); + private static readonly string s_withScreenshotOnNormallyTooltip = L10n.Tr("Take a screenshot for completion reports."); private SerializedProperty _withScreenshotOnNormallyProp; private GUIContent _withScreenshotOnNormallyGUIContent; // @formatter:on @@ -100,6 +126,9 @@ private void Initialize() _addHereInSlackMessageProp = serializedObject.FindProperty(nameof(SlackReporter.addHereInSlackMessage)); _addHereInSlackMessageGUIContent = new GUIContent(s_addHereInSlackMessage, s_addHereInSlackMessageTooltip); + _leadTextOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.leadTextOnError)); + _leadTextOnErrorGUIContent = new GUIContent(s_leadTextOnError, s_leadTextOnErrorTooltip); + _messageBodyTemplateOnErrorProp = serializedObject.FindProperty(nameof(SlackReporter.messageBodyTemplateOnError)); _messageBodyTemplateOnErrorGUIContent = new GUIContent(s_messageBodyTemplateOnError, s_messageBodyTemplateOnErrorTooltip); @@ -112,6 +141,15 @@ private void Initialize() _postOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.postOnNormally)); _postOnNormallyGUIContent = new GUIContent(s_postOnNormally, s_postOnNormallyTooltip); + _mentionSubTeamIDsOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.mentionSubTeamIDsOnNormally)); + _mentionSubTeamIDsOnNormallyGUIContent = new GUIContent(s_mentionSubTeamIDsOnNormally, s_mentionSubTeamIDsOnNormallyTooltip); + + _addHereInSlackMessageOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.addHereInSlackMessageOnNormally)); + _addHereInSlackMessageOnNormallyGUIContent = new GUIContent(s_addHereInSlackMessageOnNormally, s_addHereInSlackMessageOnNormallyTooltip); + + _leadTextOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.leadTextOnNormally)); + _leadTextOnNormallyGUIContent = new GUIContent(s_leadTextOnNormally, s_leadTextOnNormallyTooltip); + _messageBodyTemplateOnNormallyProp = serializedObject.FindProperty(nameof(SlackReporter.messageBodyTemplateOnNormally)); _messageBodyTemplateOnNormallyGUIContent = new GUIContent(s_messageBodyTemplateOnNormally, s_messageBodyTemplateOnNormallyTooltip); @@ -134,20 +172,26 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_slackTokenProp, _slackTokenGUIContent); EditorGUILayout.PropertyField(_slackChannelsProp, _slackChannelsGUIContent); + GUILayout.Space(SpacerSectionPixels); - GUILayout.Space(SpacerPixels); + GUILayout.Label(s_errorSettingsHeader); + GUILayout.Space(SpacerUnderHeaderPixels); EditorGUILayout.PropertyField(_mentionSubTeamIDsProp, _mentionSubTeamIDsGUIContent); EditorGUILayout.PropertyField(_addHereInSlackMessageProp, _addHereInSlackMessageGUIContent); - - GUILayout.Space(SpacerPixels); + EditorGUILayout.PropertyField(_leadTextOnErrorProp, _leadTextOnErrorGUIContent); EditorGUILayout.PropertyField(_messageBodyTemplateOnErrorProp, _messageBodyTemplateOnErrorGUIContent); EditorGUILayout.PropertyField(_colorOnErrorProp, _colorOnErrorGUIContent); EditorGUILayout.PropertyField(_withScreenshotOnErrorProp, _withScreenshotOnErrorGUIContent); + GUILayout.Space(SpacerSectionPixels); - GUILayout.Space(SpacerPixels); + GUILayout.Label(s_normallyHeader); + GUILayout.Space(SpacerUnderHeaderPixels); EditorGUILayout.PropertyField(_postOnNormallyProp, _postOnNormallyGUIContent); EditorGUI.BeginDisabledGroup(!_postOnNormallyProp.boolValue); + EditorGUILayout.PropertyField(_mentionSubTeamIDsOnNormallyProp, _mentionSubTeamIDsOnNormallyGUIContent); + EditorGUILayout.PropertyField(_addHereInSlackMessageOnNormallyProp, _addHereInSlackMessageOnNormallyGUIContent); + EditorGUILayout.PropertyField(_leadTextOnNormallyProp, _leadTextOnNormallyGUIContent); EditorGUILayout.PropertyField(_messageBodyTemplateOnNormallyProp, _messageBodyTemplateOnNormallyGUIContent); EditorGUILayout.PropertyField(_colorOnNormallyProp, _colorOnNormallyGUIContent); EditorGUILayout.PropertyField(_withScreenshotOnNormallyProp, _withScreenshotOnNormallyGUIContent); diff --git a/README.md b/README.md index c5a4628..62022b2 100644 --- a/README.md +++ b/README.md @@ -435,15 +435,13 @@ The instance of this Reporter (.asset file) can have the following settings.
Slack Channels
Channels to send notifications. If omitted, not notified. Multiple channels can be specified by separating them with commas. This setting can be overwritten with the command line argument -SLACK_CHANNELS. The bot must be invited to the channel.
-
Mention Sub Team IDs
Comma Separated Team IDs to mention in notification message
-
Add Here In Slack Message
Add @here to notification message. Default is off
-
Message Body Template
Message body template when posting an error terminated report. You can specify placeholders like a "{message}".
-
Color
Attachment color when posting an error terminated report.
-
Screenshot
Take a screenshot when posting an error terminated report. Default is on
-
Normally terminated report
Post a report if normally terminates. Default is off
-
Message Body Template
Message body template when posting a normally terminated report. You can specify placeholders like a "{message}".
-
Color
Attachment color when posting a normally terminated report.
-
Screenshot
Take a screenshot when posting a normally terminated report. Default is off
+
Mention Sub Team IDs
Comma-separated sub team IDs to mention when posting error reports.
+
Add @here
Add @here to the post when posting error reports.
+
Lead Text
Lead text for error reports. It is used in OS notifications. You can specify placeholders like a "{message}".
+
Message
Message body template for error reports. You can specify placeholders like a "{message}".
+
Color
Attachments color for error reports.
+
Screenshot
Take a screenshot for error reports. Default is on.
+
Post if completes
Also post a report if completed autopilot normally. Default is off.
You can create a Slack Bot on the following page: @@ -454,7 +452,7 @@ The Slack Bot needs the following permissions: - chat:write - files:write -The placeholders you can include in the message body template are: +The placeholders you can include in the lead and message body template are: - "{message}": Message with terminate (e.g., error log message) - "{settings}": Name of running AutopilotSettings diff --git a/README_ja.md b/README_ja.md index 93fdaa5..65f2f1b 100644 --- a/README_ja.md +++ b/README_ja.md @@ -440,15 +440,13 @@ Slackにレポート送信するReporterです。
Slack Channels
通知を送るチャンネル(省略時は通知されない)。カンマ区切りで複数指定できます。 コマンドライン引数 -SLACK_CHANNELS で上書きできます。 チャンネルにはBotを招待しておく必要があります。
-
Mention Sub Team IDs
通知メッセージでメンションするチームのIDをカンマ区切りで指定します
-
Add Here In Slack Message
通知メッセージに@hereを付けます(デフォルト: off)
-
Message Body Template
エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
+
Mention Sub Team IDs
エラー終了時に送信する通知をメンションするチームのIDをカンマ区切りで指定します
+
Add @here
エラー終了時に送信する通知に@hereを付けます
+
Lead Text
エラー終了時に送信する通知のリード文。OSの通知に使用されます。"{message}" のようなプレースホルダーを指定できます
+
Message
エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
Color
エラー終了時に送信するメッセージのアタッチメントに指定する色
Screenshot
エラー終了時にスクリーンショットを撮影します(デフォルト: on)
-
Normally terminated report
正常終了時にもレポートをポストします(デフォルト: off)
-
Message Body Template
正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
-
Color
正常終了時に送信するメッセージのアタッチメントに指定する色
-
Screenshot
正常終了時にスクリーンショットを撮影します(デフォルト: off)
+
Normally terminated report
正常終了時にもレポートを送信します(デフォルト: off)
Slack Botは次のページで作成できます。 @@ -459,7 +457,7 @@ Slack Botには次の権限が必要です。 - chat:write - files:write -メッセージ本文のテンプレートに記述できるプレースホルダーは次のとおりです。 +リード及びメッセージ本文のテンプレートに記述できるプレースホルダーは次のとおりです。 - "{message}": 終了要因メッセージ(エラーログのメッセージなど) - "{settings}": 実行中の AutopilotSettings 名 diff --git a/Runtime/Reporters/MessageBuilder.cs b/Runtime/Reporters/MessageBuilder.cs index dfa0e15..e7c4ab0 100644 --- a/Runtime/Reporters/MessageBuilder.cs +++ b/Runtime/Reporters/MessageBuilder.cs @@ -24,7 +24,7 @@ public static class MessageBuilder /// /// Replace placeholder "{message}" in the template with this string /// Messages that replaced placeholders in the template - public static string BuildWithTemplate(string template, string message) + public static string BuildWithTemplate(string template, string message = null) { var settings = AutopilotState.Instance.settings; var placeholders = GetPlaceholders().ToList(); diff --git a/Runtime/Reporters/Slack/SlackAPI.cs b/Runtime/Reporters/Slack/SlackAPI.cs index 3562b61..8407c4e 100644 --- a/Runtime/Reporters/Slack/SlackAPI.cs +++ b/Runtime/Reporters/Slack/SlackAPI.cs @@ -49,18 +49,18 @@ public class SlackAPI ///
/// Slack token /// Send target channels - /// Lead text (out of attachment) + /// Lead text (out of attachment) /// Message body text (into attachment) /// Attachment color /// Thread timestamp /// - public virtual async UniTask Post(string token, string channel, string lead, string message, + public virtual async UniTask Post(string token, string channel, string text, string message, Color color, string ts = null) { const string URL = URLBase + "chat.postMessage"; var payload = Payload.CreatePayload( channel, - lead, + text, new[] { Attachment.CreateAttachment( diff --git a/Runtime/Reporters/Slack/SlackMessageSender.cs b/Runtime/Reporters/Slack/SlackMessageSender.cs index 59f8adf..d0fe80a 100644 --- a/Runtime/Reporters/Slack/SlackMessageSender.cs +++ b/Runtime/Reporters/Slack/SlackMessageSender.cs @@ -23,7 +23,8 @@ public interface ISlackMessageSender /// Slack Channel to send notification /// Sub team IDs to mention /// Whether adding @here or not - /// Log message + /// Lead text (out of attachment) + /// Message body text (into attachment) /// Stack trace /// Attachment color /// With screenshot @@ -34,6 +35,7 @@ UniTask Send( string slackChannel, IEnumerable mentionSubTeamIDs, bool addHereInSlackMessage, + string lead, string message, string stackTrace, Color color, @@ -68,6 +70,7 @@ public async UniTask Send( string slackChannel, IEnumerable mentionSubTeamIDs, bool addHereInSlackMessage, + string lead, string message, string stackTrace, Color color, @@ -80,14 +83,14 @@ public async UniTask Send( return; } - var lead = CreateLead(mentionSubTeamIDs, addHereInSlackMessage); + var text = CreateLead(lead, mentionSubTeamIDs, addHereInSlackMessage); await UniTask.SwitchToMainThread(); var postTitleTask = await _slackAPI.Post( slackToken, slackChannel, - lead, + text, message, color ); @@ -126,7 +129,7 @@ public async UniTask Send( } - private static string CreateLead(IEnumerable mentionSubTeamIDs, bool withHere) + private static string CreateLead(string lead, IEnumerable mentionSubTeamIDs, bool withHere) { var sb = new StringBuilder(); foreach (var s in mentionSubTeamIDs) @@ -143,6 +146,11 @@ private static string CreateLead(IEnumerable mentionSubTeamIDs, bool wit sb.Append(" "); } + if (!string.IsNullOrEmpty(lead)) + { + sb.Append(lead); + } + return sb.ToString(); } diff --git a/Runtime/Reporters/SlackReporter.cs b/Runtime/Reporters/SlackReporter.cs index a719d21..b412362 100644 --- a/Runtime/Reporters/SlackReporter.cs +++ b/Runtime/Reporters/SlackReporter.cs @@ -16,59 +16,81 @@ namespace DeNA.Anjin.Reporters public class SlackReporter : AbstractReporter { /// - /// Slack API token + /// Slack API token. /// public string slackToken; /// - /// Slack channels to send notification (comma separated) + /// Comma-separated Slack channels to post. If omitted, it will not be sent. /// public string slackChannels; /// - /// Sub team IDs to mention (comma separated) + /// Comma-separated sub team IDs to mention when posting error reports. /// public string mentionSubTeamIDs; /// - /// Whether adding @here or not + /// Add @here to the post when posting error reports. /// public bool addHereInSlackMessage; /// - /// Message body template (use on error terminates). + /// Lead text for error reports. It is used in OS notifications. /// [Multiline] - public string messageBodyTemplateOnError = @"{message}"; + public string leadTextOnError = "{settings} occurred an error."; /// - /// Attachment color (use on error terminates). + /// Message body template for error reports. /// - public Color colorOnError = new Color(0.86f, 0.21f, 0.27f); + [Multiline] + public string messageBodyTemplateOnError = "{message}"; + + /// + /// Attachments color for error reports. + /// + public Color colorOnError = new Color(0.64f, 0.01f, 0f); /// - /// With take a screenshot or not (use on error terminates). + /// Take a screenshot for error reports. /// public bool withScreenshotOnError = true; /// - /// Post a report if normally terminates. + /// Also post a report if completed autopilot normally. /// public bool postOnNormally; /// - /// Message body template (use on normally terminates). + /// Comma-separated sub team IDs to mention when posting completion reports. + /// + public string mentionSubTeamIDsOnNormally; + + /// + /// Add @here to the post when posting completion reports. + /// + public bool addHereInSlackMessageOnNormally; + + /// + /// Lead text for completion reports. + /// + [Multiline] + public string leadTextOnNormally = "{settings} completed normally."; + + /// + /// Message body template for completion reports. /// [Multiline] - public string messageBodyTemplateOnNormally = @"{message}"; + public string messageBodyTemplateOnNormally = "{message}"; /// - /// Attachment color (use on normally terminates). + /// Attachments color for completion reports. /// - public Color colorOnNormally = new Color(0.16f, 0.65f, 0.27f); + public Color colorOnNormally = new Color(0.24f, 0.65f, 0.34f); /// - /// With take a screenshot or not (use on normally terminates). + /// Take a screenshot for completion reports. /// public bool withScreenshotOnNormally; @@ -102,6 +124,15 @@ public override async UniTask PostReportAsync( return; } + var mention = (exitCode == ExitCode.Normally) + ? mentionSubTeamIDsOnNormally + : mentionSubTeamIDs; + var here = (exitCode == ExitCode.Normally) + ? addHereInSlackMessageOnNormally + : addHereInSlackMessage; + var lead = MessageBuilder.BuildWithTemplate((exitCode == ExitCode.Normally) + ? leadTextOnNormally + : leadTextOnError); var messageBody = MessageBuilder.BuildWithTemplate((exitCode == ExitCode.Normally) ? messageBodyTemplateOnNormally : messageBodyTemplateOnError, @@ -124,12 +155,16 @@ public override async UniTask PostReportAsync( return; } - await PostReportAsync(slackChannel, messageBody, stackTrace, color, withScreenshot, cancellationToken); + await PostReportAsync(slackChannel, mention, here, lead, messageBody, stackTrace, color, withScreenshot, + cancellationToken); } } private async UniTask PostReportAsync( string slackChannel, + string mention, + bool here, + string lead, string message, string stackTrace, Color color, @@ -140,8 +175,9 @@ private async UniTask PostReportAsync( await _sender.Send( slackToken, slackChannel, - mentionSubTeamIDs.Split(','), - addHereInSlackMessage, + mention.Split(','), + here, + lead, message, stackTrace, color, diff --git a/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs b/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs index ba2ad74..7961cd4 100644 --- a/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs +++ b/Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs @@ -27,6 +27,7 @@ await sender.Send( "CHANNEL", Array.Empty(), false, + "LEAD", "MESSAGE", "STACKTRACE", Color.magenta, @@ -40,7 +41,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "lead", "" }, + { "text", "LEAD" }, { "message", "MESSAGE" }, { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, { "ts", null } @@ -67,6 +68,7 @@ await sender.Send( "CHANNEL", new[] { "MENTION1", "MENTION2" }, false, + "LEAD", "MESSAGE", "STACKTRACE", Color.magenta, @@ -80,7 +82,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "lead", " " }, + { "text", " LEAD" }, { "message", "MESSAGE" }, { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, { "ts", null } @@ -108,6 +110,7 @@ await sender.Send( "CHANNEL", new[] { "MENTION1", "MENTION2" }, false, + "LEAD", "MESSAGE", "STACKTRACE", Color.magenta, @@ -121,7 +124,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "lead", " " }, + { "text", " LEAD" }, { "message", "MESSAGE" }, { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, { "ts", null } @@ -130,7 +133,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "lead", null }, + { "text", null }, { "message", "IMAGE" }, { "color", "RGBA(0.000, 0.000, 0.000, 0.000)" }, // default { "ts", "1" } @@ -157,6 +160,7 @@ await sender.Send( "CHANNEL", Array.Empty(), true, + "LEAD", "MESSAGE", "STACKTRACE", Color.magenta, @@ -170,7 +174,7 @@ await sender.Send( { { "token", "TOKEN" }, { "channel", "CHANNEL" }, - { "lead", " " }, + { "text", " LEAD" }, { "message", "MESSAGE" }, { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, { "ts", null } diff --git a/Tests/Runtime/Reporters/SlackReporterTest.cs b/Tests/Runtime/Reporters/SlackReporterTest.cs index b2794ad..cb0209b 100644 --- a/Tests/Runtime/Reporters/SlackReporterTest.cs +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs @@ -67,6 +67,12 @@ public async Task PostReportAsync_OnNormallyAndPostOnNormally_Sent() _sut.slackToken = "token"; _sut.slackChannels = "dev"; _sut.postOnNormally = true; + _sut.mentionSubTeamIDsOnNormally = "charlie"; + _sut.mentionSubTeamIDs = "alpha,bravo"; // dummy + _sut.addHereInSlackMessageOnNormally = true; + _sut.addHereInSlackMessage = false; // dummy + _sut.leadTextOnNormally = "Normally terminate lead"; + _sut.leadTextOnError = "Not use this"; // dummy _sut.messageBodyTemplateOnNormally = "Normally terminate with {message}"; _sut.messageBodyTemplateOnError = "Not use this"; // dummy _sut.withScreenshotOnNormally = true; @@ -74,6 +80,9 @@ public async Task PostReportAsync_OnNormallyAndPostOnNormally_Sent() await _sut.PostReportAsync("message", string.Empty, ExitCode.Normally); Assert.That(_spy.CalledList.Count, Is.EqualTo(1)); + Assert.That(_spy.CalledList[0].MentionSubTeamIDs, Is.EquivalentTo(new[] { "charlie" })); + Assert.That(_spy.CalledList[0].AddHereInSlackMessage, Is.True); + Assert.That(_spy.CalledList[0].Lead, Is.EqualTo("Normally terminate lead")); // use OnNormally Assert.That(_spy.CalledList[0].Message, Is.EqualTo("Normally terminate with message")); // use OnNormally Assert.That(_spy.CalledList[0].WithScreenshot, Is.True); // use OnNormally } diff --git a/Tests/Runtime/TestDoubles/SpySlackAPI.cs b/Tests/Runtime/TestDoubles/SpySlackAPI.cs index 9cc377d..62a352c 100644 --- a/Tests/Runtime/TestDoubles/SpySlackAPI.cs +++ b/Tests/Runtime/TestDoubles/SpySlackAPI.cs @@ -12,13 +12,13 @@ public class SpySlackAPI : SlackAPI { public List> Arguments { get; } = new List>(); - public override async UniTask Post(string token, string channel, string lead, string message, + public override async UniTask Post(string token, string channel, string text, string message, Color color, string ts = null) { var args = new Dictionary(); args.Add("token", token); args.Add("channel", channel); - args.Add("lead", lead); + args.Add("text", text); args.Add("message", message); args.Add("color", color.ToString()); args.Add("ts", ts); diff --git a/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs index a205d78..c384bdb 100644 --- a/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs +++ b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs @@ -17,6 +17,7 @@ public struct CallArguments public string SlackChannel { get; set; } public IEnumerable MentionSubTeamIDs { get; set; } public bool AddHereInSlackMessage { get; set; } + public string Lead { get; set; } public string Message { get; set; } public string StackTrace { get; set; } public Color Color { get; set; } @@ -26,8 +27,8 @@ public struct CallArguments public List CalledList { get; } = new List(); public async UniTask Send(string slackToken, string slackChannel, IEnumerable mentionSubTeamIDs, - bool addHereInSlackMessage, string message, string stackTrace, Color color, bool withScreenshot, - CancellationToken cancellationToken = default) + bool addHereInSlackMessage, string lead, string message, string stackTrace, Color color, + bool withScreenshot, CancellationToken cancellationToken = default) { var called = new CallArguments { @@ -35,6 +36,7 @@ public async UniTask Send(string slackToken, string slackChannel, IEnumerable Date: Mon, 4 Nov 2024 06:50:38 +0900 Subject: [PATCH 12/12] Fix API to work with Unity 2022.1 or older --- Runtime/Reporters/Slack/SlackAPI.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Runtime/Reporters/Slack/SlackAPI.cs b/Runtime/Reporters/Slack/SlackAPI.cs index 8407c4e..c2be8a2 100644 --- a/Runtime/Reporters/Slack/SlackAPI.cs +++ b/Runtime/Reporters/Slack/SlackAPI.cs @@ -166,8 +166,14 @@ private static async UniTask Post(string url, WWWForm form) private static async UniTask Post(string url, string token, Payload payload) { +#if UNITY_2022_2_OR_NEWER using (var www = UnityWebRequest.Post(url, payload.ToJson(), "application/json; charset=utf-8")) { +#else + using (var www = UnityWebRequest.Post(url, payload.ToJson())) + { + www.SetRequestHeader("Content-Type", "application/json; charset=utf-8"); +#endif www.SetRequestHeader("Authorization", $"Bearer {token}"); await www.SendWebRequest();