core_lib/notifications/
sender.rs1use anyhow::Result;
2use chrono::Utc;
3use reqwest::Client;
4use serde_json::json;
5
6use crate::{
7 core::watcher::WatchContext,
8 exec::metrics::ExecMetrics,
9 notifications::{DiscordEmbed, DiscordField, DiscordFooter, DiscordImage},
10};
11
12pub async fn discord_sender(url: &str, embed: &DiscordEmbed) -> Result<()> {
13 let payload = json!({
14 "embeds": [embed]
15 });
16
17 let client = Client::new();
18 let resp = client
20 .post(url)
21 .json(&payload)
22 .timeout(std::time::Duration::from_secs(10))
23 .send()
24 .await?;
25
26 if resp.status().is_success() {
27 Ok(())
28 } else {
29 Err(anyhow::anyhow!(
30 "Discord error: {} - {:?}",
31 resp.status(),
32 resp.text().await?
33 ))
34 }
35}
36
37pub async fn discord_send_succes(ctx: &WatchContext, m: &ExecMetrics) -> Result<()> {
38 if ctx.config.pipeline.notifications.is_none() {
39 return Ok(());
40 }
41
42 let notification_config = ctx.config.pipeline.notifications.as_ref().unwrap();
43
44 let embed = DiscordEmbed {
45 title: "✅Pipeline finish".into(),
46 description: format!("Pipeline **{}** executed successfully", ctx.repo.name),
47 color: 0x2ECC71,
48 thumbnail: DiscordImage::load(notification_config.thumbnail.clone()),
49 fields: vec![
50 DiscordField {
51 name: "Service name".into(),
52 value: format!("`{}`", ctx.repo.name.clone()),
53 inline: false,
54 },
55 DiscordField {
56 name: "Duration".into(),
57 value: format!("`{:.2}s`", (m.duration_ms.unwrap_or(1) as f64) / 1000.0),
58 inline: true,
59 },
60 DiscordField {
61 name: "CPU".into(),
62 value: format!("`{:.2}%`", m.cpu_usage),
63 inline: true,
64 },
65 DiscordField {
66 name: "Mem (%)".into(),
67 value: format!("`{:.2}%`", m.mem_usage),
68 inline: true,
69 },
70 DiscordField {
71 name: "Mem (Mb)".into(),
72 value: format!("`{}Mb`", m.mem_usage_kb / (1024 * 1024)),
73 inline: true,
74 },
75 ],
76 footer: Some(DiscordFooter {
77 text: "Fleet CI/CD Pipeline".into(),
78 }),
79 timestamp: Some(m.finished_at.unwrap()),
80 };
81
82 for c in notification_config.channels.iter() {
83 if c.service == "discord" {
84 discord_sender(&c.url, &embed).await?;
85 }
86 }
87 Ok(())
88}
89
90pub async fn discord_send_failure(ctx: &WatchContext, msg: &str, m: &ExecMetrics) -> Result<()> {
93 if ctx.config.pipeline.notifications.is_none() {
94 return Ok(());
95 }
96
97 let notification_config = ctx.config.pipeline.notifications.as_ref().unwrap();
98
99 let embed = DiscordEmbed {
100 title: "❌ Pipeline failed".into(),
101 description: String::from(msg),
102 color: 0xE74C3C,
103 thumbnail: DiscordImage::load(notification_config.thumbnail.clone()),
104 fields: vec![
105 DiscordField {
106 name: "Service name".into(),
107 value: format!("`{}`", ctx.repo.name.clone()),
108 inline: false,
109 },
110 DiscordField {
111 name: "Duration".into(),
112 value: format!("`{:.2}s`", (m.duration_ms.unwrap_or(1) as f64) / 1000.0),
113 inline: true,
114 },
115 DiscordField {
116 name: "CPU".into(),
117 value: format!("`{:.2}%`", m.cpu_usage),
118 inline: true,
119 },
120 DiscordField {
121 name: "Mem (%)".into(),
122 value: format!("`{:.2}%`", m.mem_usage),
123 inline: true,
124 },
125 DiscordField {
126 name: "Mem (Mb)".into(),
127 value: format!("`{}Mb`", m.mem_usage_kb / (1024 * 1024)),
128 inline: true,
129 },
130 ],
131 footer: Some(DiscordFooter {
132 text: "Fleet CI/CD Pipeline".into(),
133 }),
134 timestamp: Some(m.finished_at.unwrap_or(Utc::now())),
135 };
136 for c in notification_config.channels.iter() {
137 if c.service == "discord" {
138 discord_sender(&c.url, &embed).await?;
139 }
140 }
141 Ok(())
142}