core_lib/notifications/
sender.rs

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