1use chrono::{DateTime, Utc};
2use scouter_types::psi::DistributionData;
3use scouter_types::{
4 alert::Alert,
5 custom::{BinnedCustomMetric, BinnedCustomMetricStats},
6 get_utc_datetime,
7 psi::FeatureBinProportionResult,
8 RecordType,
9};
10use serde::{Deserialize, Serialize};
11use sqlx::{postgres::PgRow, Error, FromRow, Row};
12use std::collections::BTreeMap;
13use std::collections::HashMap;
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct DriftRecord {
17 pub created_at: DateTime<Utc>,
18 pub name: String,
19 pub space: String,
20 pub version: String,
21 pub feature: String,
22 pub value: f64,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct SpcFeatureResult {
27 pub feature: String,
28 pub created_at: Vec<DateTime<Utc>>,
29 pub values: Vec<f64>,
30}
31
32impl<'r> FromRow<'r, PgRow> for SpcFeatureResult {
33 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
34 Ok(SpcFeatureResult {
35 feature: row.try_get("feature")?,
36 created_at: row.try_get("created_at")?,
37 values: row.try_get("values")?,
38 })
39 }
40}
41
42#[derive(Debug)]
43pub struct FeatureDistributionWrapper(pub String, pub DistributionData);
44
45impl<'r> FromRow<'r, PgRow> for FeatureDistributionWrapper {
46 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
47 let feature: String = row.try_get("feature")?;
48 let sample_size: i64 = row.try_get("sample_size")?;
49 let bins_json: serde_json::Value = row.try_get("bins")?;
50 let bins: BTreeMap<usize, f64> =
51 serde_json::from_value(bins_json).map_err(|e| Error::Decode(e.into()))?;
52
53 Ok(FeatureDistributionWrapper(
54 feature,
55 DistributionData {
56 sample_size: sample_size as u64,
57 bins,
58 },
59 ))
60 }
61}
62
63pub struct BinnedCustomMetricWrapper(pub BinnedCustomMetric);
64
65impl<'r> FromRow<'r, PgRow> for BinnedCustomMetricWrapper {
66 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
67 let stats_json: Vec<serde_json::Value> = row.try_get("stats")?;
68
69 let stats: Vec<BinnedCustomMetricStats> = stats_json
70 .into_iter()
71 .map(|value| serde_json::from_value(value).unwrap_or_default())
72 .collect();
73
74 Ok(BinnedCustomMetricWrapper(BinnedCustomMetric {
75 metric: row.try_get("metric")?,
76 created_at: row.try_get("created_at")?,
77 stats,
78 }))
79 }
80}
81
82pub struct AlertWrapper(pub Alert);
83
84impl<'r> FromRow<'r, PgRow> for AlertWrapper {
85 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
86 let alert_value: serde_json::Value = row.try_get("alert")?;
87 let alert: BTreeMap<String, String> =
88 serde_json::from_value(alert_value).unwrap_or_default();
89
90 Ok(AlertWrapper(Alert {
91 created_at: row.try_get("created_at")?,
92 name: row.try_get("name")?,
93 space: row.try_get("space")?,
94 version: row.try_get("version")?,
95 alert,
96 entity_name: row.try_get("entity_name")?,
97 id: row.try_get("id")?,
98 drift_type: row.try_get("drift_type")?,
99 active: row.try_get("active")?,
100 }))
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct TaskRequest {
106 pub name: String,
107 pub space: String,
108 pub version: String,
109 pub profile: String,
110 pub drift_type: String,
111 pub previous_run: DateTime<Utc>,
112 pub schedule: String,
113 pub uid: String,
114}
115
116impl<'r> FromRow<'r, PgRow> for TaskRequest {
117 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
118 let profile: serde_json::Value = row.try_get("profile")?;
119
120 Ok(TaskRequest {
121 name: row.try_get("name")?,
122 space: row.try_get("space")?,
123 version: row.try_get("version")?,
124 profile: profile.to_string(),
125 drift_type: row.try_get("drift_type")?,
126 previous_run: row.try_get("previous_run")?,
127 schedule: row.try_get("schedule")?,
128 uid: row.try_get("uid")?,
129 })
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ObservabilityResult {
135 pub route_name: String,
136 pub created_at: Vec<DateTime<Utc>>,
137 pub p5: Vec<f64>,
138 pub p25: Vec<f64>,
139 pub p50: Vec<f64>,
140 pub p95: Vec<f64>,
141 pub p99: Vec<f64>,
142 pub total_request_count: Vec<i64>,
143 pub total_error_count: Vec<i64>,
144 pub error_latency: Vec<f64>,
145 pub status_counts: Vec<HashMap<String, i64>>,
146}
147
148impl<'r> FromRow<'r, PgRow> for ObservabilityResult {
149 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
150 let status_counts: Vec<serde_json::Value> = row.try_get("status_counts")?;
152
153 let status_counts: Vec<HashMap<String, i64>> = status_counts
155 .into_iter()
156 .map(|value| serde_json::from_value(value).unwrap_or_default())
157 .collect();
158
159 Ok(ObservabilityResult {
160 route_name: row.try_get("route_name")?,
161 created_at: row.try_get("created_at")?,
162 p5: row.try_get("p5")?,
163 p25: row.try_get("p25")?,
164 p50: row.try_get("p50")?,
165 p95: row.try_get("p95")?,
166 p99: row.try_get("p99")?,
167 total_request_count: row.try_get("total_request_count")?,
168 total_error_count: row.try_get("total_error_count")?,
169 error_latency: row.try_get("error_latency")?,
170 status_counts,
171 })
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct BinProportion {
177 pub bin_id: usize,
178 pub proportion: f64,
179}
180
181#[derive(Debug)]
182pub struct FeatureBinProportionResultWrapper(pub FeatureBinProportionResult);
183
184impl<'r> FromRow<'r, PgRow> for FeatureBinProportionResultWrapper {
185 fn from_row(row: &'r PgRow) -> Result<Self, Error> {
186 let bin_proportions_json: Vec<serde_json::Value> = row.try_get("bin_proportions")?;
188
189 let bin_proportions: Vec<BTreeMap<usize, f64>> = bin_proportions_json
191 .into_iter()
192 .map(|json| serde_json::from_value(json).unwrap_or_default())
193 .collect();
194
195 let overall_proportions_json: serde_json::Value = row.try_get("overall_proportions")?;
196 let overall_proportions: BTreeMap<usize, f64> =
197 serde_json::from_value(overall_proportions_json).unwrap_or_default();
198
199 Ok(FeatureBinProportionResultWrapper(
200 FeatureBinProportionResult {
201 feature: row.try_get("feature")?,
202 created_at: row.try_get("created_at")?,
203 bin_proportions,
204 overall_proportions,
205 },
206 ))
207 }
208}
209#[derive(Debug, Clone, FromRow)]
210pub struct Entity {
211 pub space: String,
212 pub name: String,
213 pub version: String,
214 pub begin_timestamp: DateTime<Utc>,
215 pub end_timestamp: DateTime<Utc>,
216}
217
218impl Entity {
219 pub fn get_write_path(&self, record_type: &RecordType) -> String {
220 format!(
221 "{}/{}/{}/{}",
222 self.space, self.name, self.version, record_type
223 )
224 }
225}
226
227#[derive(Debug, Serialize, Deserialize, Clone)]
228pub struct User {
229 pub id: Option<i32>,
230 pub created_at: DateTime<Utc>,
231 pub active: bool,
232 pub username: String,
233 pub password_hash: String,
234 pub hashed_recovery_codes: Vec<String>,
235 pub permissions: Vec<String>,
236 pub group_permissions: Vec<String>,
237 pub role: String,
238 pub favorite_spaces: Vec<String>,
239 pub refresh_token: Option<String>,
240 pub email: String,
241 pub updated_at: DateTime<Utc>,
242}
243
244impl User {
245 #[allow(clippy::too_many_arguments)]
246 pub fn new(
247 username: String,
248 password_hash: String,
249 email: String,
250 hashed_recovery_codes: Vec<String>,
251 permissions: Option<Vec<String>>,
252 group_permissions: Option<Vec<String>>,
253 role: Option<String>,
254 favorite_spaces: Option<Vec<String>>,
255 ) -> Self {
256 let created_at = get_utc_datetime();
257
258 User {
259 id: None,
260 created_at,
261 active: true,
262 username,
263 password_hash,
264 hashed_recovery_codes,
265 permissions: permissions.unwrap_or(vec!["read:all".to_string()]),
266 group_permissions: group_permissions.unwrap_or(vec!["user".to_string()]),
267 favorite_spaces: favorite_spaces.unwrap_or_default(),
268 role: role.unwrap_or("user".to_string()),
269 refresh_token: None,
270 email,
271 updated_at: created_at,
272 }
273 }
274}
275
276impl FromRow<'_, PgRow> for User {
277 fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
278 let id = row.try_get("id")?;
279 let created_at = row.try_get("created_at")?;
280 let updated_at = row.try_get("updated_at")?;
281 let active = row.try_get("active")?;
282 let username = row.try_get("username")?;
283 let password_hash = row.try_get("password_hash")?;
284 let email = row.try_get("email")?;
285 let role = row.try_get("role")?;
286 let refresh_token = row.try_get("refresh_token")?;
287
288 let group_permissions: Vec<String> =
289 serde_json::from_value(row.try_get("group_permissions")?).unwrap_or_default();
290
291 let permissions: Vec<String> =
292 serde_json::from_value(row.try_get("permissions")?).unwrap_or_default();
293
294 let hashed_recovery_codes: Vec<String> =
295 serde_json::from_value(row.try_get("hashed_recovery_codes")?).unwrap_or_default();
296
297 let favorite_spaces: Vec<String> =
298 serde_json::from_value(row.try_get("favorite_spaces")?).unwrap_or_default();
299
300 Ok(User {
301 id,
302 created_at,
303 updated_at,
304 active,
305 username,
306 password_hash,
307 email,
308 role,
309 refresh_token,
310 hashed_recovery_codes,
311 permissions,
312 group_permissions,
313 favorite_spaces,
314 })
315 }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
319pub struct UpdateAlertResult {
320 pub id: i32,
321 pub active: bool,
322 pub updated_at: DateTime<Utc>,
323}