Class: RailsErrorDashboard::Services::BaselineAlertPayloadBuilder
- Inherits:
-
Object
- Object
- RailsErrorDashboard::Services::BaselineAlertPayloadBuilder
- Defined in:
- lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb
Overview
Pure algorithm: Build notification payloads for baseline anomaly alerts
No HTTP calls — accepts error + anomaly data, returns platform-specific Hashes.
Constant Summary collapse
- ANOMALY_EMOJIS =
{ critical: "🔴", high: "🟠", elevated: "🟡" }.freeze
- ANOMALY_COLORS =
{ critical: 15158332, # Red high: 16744192, # Orange elevated: 16776960 # Yellow }.freeze
- DEFAULT_EMOJI =
"⚪"- DEFAULT_COLOR =
Gray
9807270
Class Method Summary collapse
-
.anomaly_color(level) ⇒ Integer
Discord color integer.
-
.anomaly_emoji(level) ⇒ String
Emoji.
-
.anomaly_level(level, locale) ⇒ Object
Anomaly levels are their own vocabulary — an anomaly can be "elevated", an error cannot — so they do not reuse red.common.severity.
-
.baseline_field(label, value, locale) ⇒ Object
Slack's bold-label-newline-value shape, for a baseline-only label.
-
.baseline_label(label, locale) ⇒ Object
A label from red.notifications.baseline_alert.labels.
-
.discord_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) ⇒ Hash
Build Discord embed payload for baseline anomaly.
-
.slack_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) ⇒ Hash
Build Slack Block Kit payload for baseline anomaly As in SlackPayloadBuilder, the Block Kit "type" values are Slack's vocabulary and stay English; only the labels and button text move.
- .std_devs_text(anomaly_data, locale) ⇒ Object
-
.threshold_text(anomaly_data, locale) ⇒ Object
"N errors" was an English binary plural; it is now a real plural key.
-
.webhook_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) ⇒ Hash
Build generic webhook payload for baseline anomaly Machine-readable.
Class Method Details
.anomaly_color(level) ⇒ Integer
Returns Discord color integer.
229 230 231 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 229 def self.anomaly_color(level) ANOMALY_COLORS[level] || DEFAULT_COLOR end |
.anomaly_emoji(level) ⇒ String
Returns Emoji.
223 224 225 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 223 def self.anomaly_emoji(level) ANOMALY_EMOJIS[level] || DEFAULT_EMOJI end |
.anomaly_level(level, locale) ⇒ Object
Anomaly levels are their own vocabulary — an anomaly can be "elevated", an error cannot — so they do not reuse red.common.severity. Previously rendered with .upcase on the raw symbol, which applies English morphology to a machine value.
191 192 193 194 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 191 def self.anomaly_level(level, locale) key = ANOMALY_EMOJIS.key?(level) ? level : :unknown NotificationHelpers.t("red.notifications.baseline_alert.level.#{key}", locale) end |
.baseline_field(label, value, locale) ⇒ Object
Slack's bold-label-newline-value shape, for a baseline-only label.
183 184 185 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 183 def self.baseline_field(label, value, locale) "*#{baseline_label(label, locale)}:*\n#{value}" end |
.baseline_label(label, locale) ⇒ Object
A label from red.notifications.baseline_alert.labels. Separate from NotificationHelpers.label because these are anomaly-specific and do not appear on an ordinary error alert.
178 179 180 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 178 def self.baseline_label(label, locale) NotificationHelpers.t("red.notifications.baseline_alert.labels.#{label}", locale) end |
.discord_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) ⇒ Hash
Build Discord embed payload for baseline anomaly
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 120 def self.discord_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) { embeds: [ { title: NotificationHelpers.t("red.notifications.baseline_alert.heading", locale), color: anomaly_color(anomaly_data[:level]), fields: [ { name: NotificationHelpers.label(:application, locale), value: NotificationHelpers.app_name(error_log), inline: true }, { name: NotificationHelpers.label(:error_type, locale), value: error_log.error_type, inline: true }, { name: NotificationHelpers.label(:platform, locale), value: error_log.platform, inline: true }, { name: baseline_label(:severity, locale), value: anomaly_level(anomaly_data[:level], locale), inline: true }, { name: baseline_label(:std_devs, locale), value: std_devs_text(anomaly_data, locale), inline: true }, { name: baseline_label(:threshold, locale), value: threshold_text(anomaly_data, locale), inline: true }, { name: baseline_label(:baseline_type, locale), value: anomaly_data[:baseline_type] || NotificationHelpers.not_available(locale), inline: true }, { name: NotificationHelpers.label(:message, locale), value: "```#{NotificationHelpers.(error_log., 200)}```", inline: false } ], url: NotificationHelpers.dashboard_url(error_log), timestamp: Time.current.iso8601 } ] } end |
.slack_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) ⇒ Hash
Build Slack Block Kit payload for baseline anomaly As in SlackPayloadBuilder, the Block Kit "type" values are Slack's vocabulary and stay English; only the labels and button text move.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 37 def self.slack_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) { text: NotificationHelpers.t("red.notifications.baseline_alert.fallback", locale), blocks: [ { type: "header", text: { type: "plain_text", text: NotificationHelpers.t("red.notifications.baseline_alert.heading", locale) } }, { type: "section", fields: [ { type: "mrkdwn", text: NotificationHelpers.field(:application, NotificationHelpers.app_name(error_log), locale) }, { type: "mrkdwn", text: NotificationHelpers.field(:error_type, error_log.error_type, locale) }, { type: "mrkdwn", text: NotificationHelpers.field(:platform, error_log.platform, locale) }, { type: "mrkdwn", text: baseline_field( :severity, "#{anomaly_emoji(anomaly_data[:level])} #{anomaly_level(anomaly_data[:level], locale)}", locale ) }, { type: "mrkdwn", text: baseline_field(:std_devs, std_devs_text(anomaly_data, locale), locale) } ] }, { type: "section", text: { type: "mrkdwn", # Message content is diagnostic output — never translated. text: NotificationHelpers.field( :message, "```#{NotificationHelpers.(error_log., 200)}```", locale ) } }, { type: "section", text: { type: "mrkdwn", text: baseline_field( :baseline_info, "#{baseline_label(:threshold, locale)}: #{threshold_text(anomaly_data, locale)}\n" \ "#{baseline_label(:baseline_type, locale)}: #{anomaly_data[:baseline_type]}", locale ) } }, { type: "actions", elements: [ { type: "button", text: { type: "plain_text", text: NotificationHelpers.t("red.notifications.baseline_alert.view_in_dashboard", locale) }, url: NotificationHelpers.dashboard_url(error_log) } ] } ] } end |
.std_devs_text(anomaly_data, locale) ⇒ Object
196 197 198 199 200 201 202 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 196 def self.std_devs_text(anomaly_data, locale) NotificationHelpers.t( "red.notifications.baseline_alert.std_devs_above", locale, value: anomaly_data[:std_devs_above]&.round(1) ) end |
.threshold_text(anomaly_data, locale) ⇒ Object
"N errors" was an English binary plural; it is now a real plural key.
The threshold is a rounded float (e.g. 12.5) but plural selection needs an Integer, so :count carries the integer and :value the number that is actually displayed. A locale whose plural rules differ at 1 vs 1.5 is not a case CLDR expresses, so rounding for selection is correct.
210 211 212 213 214 215 216 217 218 219 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 210 def self.threshold_text(anomaly_data, locale) rounded = anomaly_data[:threshold]&.round(1) NotificationHelpers.t( "red.notifications.baseline_alert.threshold_errors", locale, count: rounded.to_i, value: rounded ) end |
.webhook_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) ⇒ Hash
Build generic webhook payload for baseline anomaly Machine-readable. Every key AND value here is consumed by whatever the operator has wired up downstream, so nothing is localized — the locale is accepted only so the signature matches its siblings (REQ-2, REQ-6).
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb', line 150 def self.webhook_payload(error_log, anomaly_data, locale: I18nStore::DEFAULT_LOCALE) _locale = locale { event: "baseline_anomaly", timestamp: Time.current.iso8601, error: { id: error_log.id, application: NotificationHelpers.app_name(error_log), type: error_log.error_type, message: error_log., platform: error_log.platform, severity: error_log.severity.to_s, occurred_at: error_log.occurred_at.iso8601 }, anomaly: { level: anomaly_data[:level].to_s, std_devs_above: anomaly_data[:std_devs_above], threshold: anomaly_data[:threshold], baseline_type: anomaly_data[:baseline_type] }, dashboard_url: NotificationHelpers.dashboard_url(error_log) } end |