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..1702c25 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割り当て設定" @@ -470,57 +478,125 @@ 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" +msgstr "メッセージ" + +# messageBodyTemplateOnError tooltip +msgid "Message body template for error reports. You can specify placeholders like a \"{message}\"; see the README for more information." +msgstr "エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" + +# colorOnError +msgid "Color" +msgstr "色" + +# colorOnError tooltip +msgid "Attachments color for error reports." +msgstr "エラー終了時に送信するメッセージのアタッチメントに指定する色" # withScreenshotOnError -msgid "Take screenshot" +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" +msgstr "メッセージ" + +# messageBodyTemplateOnNormally tooltip +msgid "Message body template for completion reports. You can specify placeholders like a \"{message}\"; see the README for more information." +msgstr "正常終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます。詳細はREADMEを参照してください" + +# colorOnNormally +msgid "Color" +msgstr "色" + +# colorOnNormally tooltip +msgid "Attachments color for completion reports." +msgstr "正常終了時に送信するメッセージのアタッチメントに指定する色" # withScreenshotOnNormally (same as withScreenshotOnError) -msgid "Take screenshot" +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 43870b1..8ba01c1 100644 --- a/Editor/UI/Reporters/SlackReporterEditor.cs +++ b/Editor/UI/Reporters/SlackReporterEditor.cs @@ -14,55 +14,103 @@ 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"); private static readonly string s_descriptionTooltip = L10n.Tr("Description about this Reporter instance"); private SerializedProperty _descriptionProp; 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_withScreenshotOnError = L10n.Tr("Take screenshot"); - private static readonly string s_withScreenshotOnErrorTooltip = L10n.Tr("Take a screenshot when posting an error terminated report"); + 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("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 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_withScreenshotOnNormally = L10n.Tr("Take screenshot"); - private static readonly string s_withScreenshotOnNormallyTooltip = L10n.Tr("Take a screenshot when posting a normally terminated report"); + 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("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 for completion reports."); 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,16 +126,42 @@ 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); + + _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); _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); + + _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() { @@ -98,18 +172,28 @@ 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); EditorGUI.EndDisabledGroup(); 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/README.md b/README.md index f8a0d9d..62022b2 100644 --- a/README.md +++ b/README.md @@ -435,11 +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
-
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
-
Take 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: @@ -450,6 +452,12 @@ The Slack Bot needs the following permissions: - chat:write - files:write +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 +- "{env.KEY}": Environment variables + ## Implementation of game-title-specific diff --git a/README_ja.md b/README_ja.md index bcb780b..65f2f1b 100644 --- a/README_ja.md +++ b/README_ja.md @@ -440,11 +440,13 @@ Slackにレポート送信するReporterです。
Slack Channels
通知を送るチャンネル(省略時は通知されない)。カンマ区切りで複数指定できます。 コマンドライン引数 -SLACK_CHANNELS で上書きできます。 チャンネルにはBotを招待しておく必要があります。
-
Mention Sub Team IDs
通知メッセージでメンションするチームのIDをカンマ区切りで指定します
-
Add Here In Slack Message
通知メッセージに@hereを付けます(デフォルト: off)
-
Take screenshot
エラー終了時にスクリーンショットを撮影します(デフォルト: on)
-
Normally terminated report
正常終了時にもレポートをポストします(デフォルト: off)
-
Take screenshot
正常終了時にスクリーンショットを撮影します(デフォルト: off)
+
Mention Sub Team IDs
エラー終了時に送信する通知をメンションするチームのIDをカンマ区切りで指定します
+
Add @here
エラー終了時に送信する通知に@hereを付けます
+
Lead Text
エラー終了時に送信する通知のリード文。OSの通知に使用されます。"{message}" のようなプレースホルダーを指定できます
+
Message
エラー終了時に送信するメッセージ本文のテンプレート。"{message}" のようなプレースホルダーを指定できます
+
Color
エラー終了時に送信するメッセージのアタッチメントに指定する色
+
Screenshot
エラー終了時にスクリーンショットを撮影します(デフォルト: on)
+
Normally terminated report
正常終了時にもレポートを送信します(デフォルト: off)
Slack Botは次のページで作成できます。 @@ -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 new file mode 100644 index 0000000..e7c4ab0 --- /dev/null +++ b/Runtime/Reporters/MessageBuilder.cs @@ -0,0 +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. + /// + public static class MessageBuilder + { + /// + /// Returns messages that replaced placeholders in the 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 variables + /// + /// Replace placeholder "{message}" in the template with this string + /// Messages that replaced placeholders in the template + public static string BuildWithTemplate(string template, string message = null) + { + 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); + }); + + 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/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/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 50% rename from Runtime/Reporters/SlackAPI.cs rename to Runtime/Reporters/Slack/SlackAPI.cs index 0556893..c2be8a2 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 text, 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, + text, + 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,57 @@ 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) + { +#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(); + #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 82% rename from Runtime/Reporters/SlackMessageSender.cs rename to Runtime/Reporters/Slack/SlackMessageSender.cs index 84006c7..d0fe80a 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,10 @@ 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 /// Cancellation token /// @@ -33,8 +35,10 @@ UniTask Send( string slackChannel, IEnumerable mentionSubTeamIDs, bool addHereInSlackMessage, - string logString, + string lead, + string message, string stackTrace, + Color color, bool withScreenshot, CancellationToken cancellationToken = default ); @@ -66,8 +70,10 @@ public async UniTask Send( string slackChannel, IEnumerable mentionSubTeamIDs, bool addHereInSlackMessage, - string logString, + string lead, + string message, string stackTrace, + Color color, bool withScreenshot, CancellationToken cancellationToken = default ) @@ -77,15 +83,16 @@ public async UniTask Send( return; } - var title = Title(logString, mentionSubTeamIDs, addHereInSlackMessage); + var text = CreateLead(lead, mentionSubTeamIDs, addHereInSlackMessage); await UniTask.SwitchToMainThread(); var postTitleTask = await _slackAPI.Post( slackToken, slackChannel, - title, - cancellationToken: cancellationToken + text, + message, + color ); if (!postTitleTask.Success) { @@ -107,8 +114,7 @@ public async UniTask Send( slackToken, slackChannel, withoutAlpha.EncodeToPNG(), - postTitleTask.Ts, - cancellationToken + postTitleTask.Ts ); if (!postScreenshotTask.Success) { @@ -116,15 +122,14 @@ public async UniTask Send( } } - if (stackTrace != null && stackTrace.Length > 0) + if (!string.IsNullOrEmpty(stackTrace)) { - var body = Body(logString, stackTrace); - await _slackAPI.Post(slackToken, slackChannel, body, postTitleTask.Ts, cancellationToken); + await _slackAPI.PostWithoutAttachments(slackToken, slackChannel, stackTrace, postTitleTask.Ts); } } - private static string Title(string logString, IEnumerable mentionSubTeamIDs, bool withHere) + private static string CreateLead(string lead, IEnumerable mentionSubTeamIDs, bool withHere) { var sb = new StringBuilder(); foreach (var s in mentionSubTeamIDs) @@ -141,14 +146,12 @@ private static string Title(string logString, IEnumerable mentionSubTeam sb.Append(" "); } - sb.Append(logString); - return sb.ToString(); - } - + if (!string.IsNullOrEmpty(lead)) + { + sb.Append(lead); + } - private static string Body(string logString, string stackTrace) - { - return $"{logString}\n\n```{stackTrace}```"; + return sb.ToString(); } 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 15f8c4e..b412362 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; @@ -15,41 +16,100 @@ 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; /// - /// With take a screenshot or not (on error terminates). + /// Lead text for error reports. It is used in OS notifications. + /// + [Multiline] + public string leadTextOnError = "{settings} occurred an error."; + + /// + /// Message body template for error reports. + /// + [Multiline] + public string messageBodyTemplateOnError = "{message}"; + + /// + /// Attachments color for error reports. + /// + public Color colorOnError = new Color(0.64f, 0.01f, 0f); + + /// + /// 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; /// - /// With take a screenshot or not (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}"; + + /// + /// Attachments color for completion reports. + /// + public Color colorOnNormally = new Color(0.24f, 0.65f, 0.34f); + + /// + /// Take a screenshot for completion reports. /// 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( @@ -64,11 +124,28 @@ public override async UniTask PostReportAsync( return; } - var withScreenshot = exitCode == ExitCode.Normally ? withScreenshotOnNormally : withScreenshotOnError; - // TODO: build message body with template and placeholders + 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, + message); + var color = (exitCode == ExitCode.Normally) ? colorOnNormally : colorOnError; + var withScreenshot = (exitCode == ExitCode.Normally) ? withScreenshotOnNormally : withScreenshotOnError; 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(',')) @@ -78,14 +155,19 @@ public override async UniTask PostReportAsync( return; } - await PostReportAsync(slackChannel, message, stackTrace, 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, bool withScreenshot, CancellationToken cancellationToken = default ) @@ -93,14 +175,16 @@ private async UniTask PostReportAsync( await _sender.Send( slackToken, slackChannel, - mentionSubTeamIDs.Split(','), - addHereInSlackMessage, + mention.Split(','), + here, + lead, message, stackTrace, + color, withScreenshot, cancellationToken ); - // TODO: can log slack post url? + Logger?.Log($"Slack message sent to {slackChannel}"); } private void OverwriteByCommandlineArguments() 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/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/MessageBuilderTest.cs b/Tests/Runtime/Reporters/MessageBuilderTest.cs new file mode 100644 index 0000000..4f8a8ba --- /dev/null +++ b/Tests/Runtime/Reporters/MessageBuilderTest.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2023-2024 DeNA Co., Ltd. +// This software is released under the MIT License. + +using DeNA.Anjin.Settings; +using NUnit.Framework; +using TestHelper.Attributes; +using UnityEngine; + +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() + { + 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.")); + + // teardown + AutopilotState.Instance.Reset(); + } + + [Test] + [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); + Assert.That(actual, Is.EqualTo("Environment variable STR_ENV: STRING_BY_ENVIRONMENT_VARIABLE.")); + } + } +} 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 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 70% rename from Tests/Runtime/Reporters/SlackMessageSenderTest.cs rename to Tests/Runtime/Reporters/Slack/SlackMessageSenderTest.cs index 6c95b01..7961cd4 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 @@ -26,8 +27,10 @@ await sender.Send( "CHANNEL", Array.Empty(), false, + "LEAD", "MESSAGE", "STACKTRACE", + Color.magenta, false ); @@ -36,19 +39,24 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", "MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "text", "LEAD" }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected), Format(actual)); } - + [Test] public async Task WithMention() { @@ -58,10 +66,12 @@ public async Task WithMention() await sender.Send( "TOKEN", "CHANNEL", - new []{"MENTION1", "MENTION2"}, + new[] { "MENTION1", "MENTION2" }, false, + "LEAD", "MESSAGE", "STACKTRACE", + Color.magenta, false ); @@ -70,19 +80,24 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", " MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "text", " LEAD" }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected), Format(actual)); } - + [Test] [FocusGameView] public async Task WithScreenshot() @@ -93,10 +108,12 @@ public async Task WithScreenshot() await sender.Send( "TOKEN", "CHANNEL", - new []{"MENTION1", "MENTION2"}, + new[] { "MENTION1", "MENTION2" }, false, + "LEAD", "MESSAGE", "STACKTRACE", + Color.magenta, true ); @@ -105,23 +122,33 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", " MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "text", " 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" }, + { "text", 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", "MESSAGE\n\n```STACKTRACE```" }, + { "text", "STACKTRACE" }, { "ts", "1" } }, }; Assert.That(actual, Is.EqualTo(expected)); } - + [Test] public async Task WithAtHere() { @@ -133,8 +160,10 @@ await sender.Send( "CHANNEL", Array.Empty(), true, + "LEAD", "MESSAGE", "STACKTRACE", + Color.magenta, false ); @@ -143,20 +172,24 @@ await sender.Send( { new Dictionary { - { "token", "TOKEN" }, { "channel", "CHANNEL" }, { "message", " MESSAGE" }, { "ts", null } + { "token", "TOKEN" }, + { "channel", "CHANNEL" }, + { "text", " LEAD" }, + { "message", "MESSAGE" }, + { "color", "RGBA(1.000, 0.000, 1.000, 1.000)" }, + { "ts", null } }, new Dictionary { - { "token", "TOKEN" }, + { "token", "TOKEN" }, // { "channel", "CHANNEL" }, - { "message", "MESSAGE\n\n```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 +208,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 new file mode 100644 index 0000000..cb0209b --- /dev/null +++ b/Tests/Runtime/Reporters/SlackReporterTest.cs @@ -0,0 +1,121 @@ +// 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 + var settings = ScriptableObject.CreateInstance(); + settings.LoggerAsset.loggerAssets.Add(ScriptableObject.CreateInstance()); + AutopilotState.Instance.settings = settings; + } + + [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.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); + + 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].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 + } + + [Test] + 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; + _sut.withScreenshotOnError = false; // dummy + 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 + } + + [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"; + 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/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"); diff --git a/Tests/Runtime/TestDoubles/SpySlackAPI.cs b/Tests/Runtime/TestDoubles/SpySlackAPI.cs index cceb745..62a352c 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 text, 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("text", text); + 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 new file mode 100644 index 0000000..c384bdb --- /dev/null +++ b/Tests/Runtime/TestDoubles/SpySlackMessageSender.cs @@ -0,0 +1,49 @@ +// 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.Slack; +using UnityEngine; + +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 Lead { 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 lead, string message, string stackTrace, Color color, + bool withScreenshot, CancellationToken cancellationToken = default) + { + var called = new CallArguments + { + SlackToken = slackToken, + SlackChannel = slackChannel, + MentionSubTeamIDs = mentionSubTeamIDs, + AddHereInSlackMessage = addHereInSlackMessage, + Lead = lead, + Message = message, + StackTrace = stackTrace, + Color = color, + 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; } } }