Skip to content

Commit 69f4acf

Browse files
committed
sentry - db - analytics testing:
- Use primitives::sentry::EventType in structs - Use primitives::analytics::FetchedMetric - primitives::UnifiedNum - add Mul impls for u64 - sentry - db - analytics - finish query & testing
1 parent 04fe52b commit 69f4acf

File tree

9 files changed

+1048
-393
lines changed

9 files changed

+1048
-393
lines changed

primitives/src/analytics.rs

+33-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{Address, CampaignId, ValidatorId, IPFS};
1+
use crate::{
2+
sentry::{EventType, IMPRESSION},
3+
Address, CampaignId, ValidatorId, IPFS,
4+
};
25
use parse_display::Display;
36
use serde::{Deserialize, Serialize};
47

@@ -16,13 +19,23 @@ pub mod postgres {
1619
impl AnalyticsQuery {
1720
pub fn get_key(&self, key: AllowedKey) -> Option<Box<dyn ToSql + Sync + Send>> {
1821
match key {
19-
AllowedKey::CampaignId => self.campaign_id.map(|campaign_id| Box::new(campaign_id) as _),
22+
AllowedKey::CampaignId => self
23+
.campaign_id
24+
.map(|campaign_id| Box::new(campaign_id) as _),
2025
AllowedKey::AdUnit => self.ad_unit.map(|ad_unit| Box::new(ad_unit) as _),
2126
AllowedKey::AdSlot => self.ad_slot.map(|ad_slot| Box::new(ad_slot) as _),
22-
AllowedKey::AdSlotType => self.ad_slot_type.clone().map(|ad_slot_type| Box::new(ad_slot_type) as _),
23-
AllowedKey::Advertiser => self.advertiser.map(|advertiser| Box::new(advertiser) as _),
27+
AllowedKey::AdSlotType => self
28+
.ad_slot_type
29+
.clone()
30+
.map(|ad_slot_type| Box::new(ad_slot_type) as _),
31+
AllowedKey::Advertiser => {
32+
self.advertiser.map(|advertiser| Box::new(advertiser) as _)
33+
}
2434
AllowedKey::Publisher => self.publisher.map(|publisher| Box::new(publisher) as _),
25-
AllowedKey::Hostname => self.hostname.clone().map(|hostname| Box::new(hostname) as _),
35+
AllowedKey::Hostname => self
36+
.hostname
37+
.clone()
38+
.map(|hostname| Box::new(hostname) as _),
2639
AllowedKey::Country => self.country.clone().map(|country| Box::new(country) as _),
2740
AllowedKey::OsName => self.os_name.clone().map(|os_name| Box::new(os_name) as _),
2841
}
@@ -89,7 +102,7 @@ pub struct AnalyticsQuery {
89102
#[serde(default = "default_limit")]
90103
pub limit: u32,
91104
#[serde(default = "default_event_type")]
92-
pub event_type: String,
105+
pub event_type: EventType,
93106
#[serde(default = "default_metric")]
94107
pub metric: Metric,
95108
pub segment_by: Option<AllowedKey>,
@@ -106,16 +119,6 @@ pub struct AnalyticsQuery {
106119
pub os_name: Option<OperatingSystem>,
107120
}
108121

109-
#[derive(Debug, Serialize, Deserialize)]
110-
#[serde(untagged, rename_all = "camelCase")]
111-
pub enum AnalyticsQueryKey {
112-
CampaignId(CampaignId),
113-
IPFS(IPFS),
114-
String(String),
115-
Address(Address),
116-
OperatingSystem(OperatingSystem),
117-
}
118-
119122
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Display, Hash, Eq)]
120123
#[serde(untagged, into = "String", from = "String")]
121124
pub enum OperatingSystem {
@@ -128,9 +131,15 @@ pub enum OperatingSystem {
128131
#[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq, Eq)]
129132
#[serde(rename_all = "camelCase")]
130133
pub enum Timeframe {
134+
/// [`Timeframe::Year`] returns analytics grouped by month.
131135
Year,
136+
/// [`Timeframe::Month`] returns analytics grouped by day.
132137
Month,
138+
/// [`Timeframe::Week`] returns analytics grouped by hour.
139+
/// Same as [`Timeframe::Day`].
133140
Week,
141+
/// [`Timeframe::Day`] returns analytics grouped by hour.
142+
/// Same as [`Timeframe::Week`].
134143
Day,
135144
}
136145

@@ -151,10 +160,13 @@ pub enum AuthenticateAs {
151160

152161
impl Metric {
153162
#[cfg(feature = "postgres")]
154-
pub fn column_name(self) -> String {
163+
/// Returns the query column name of the [`Metric`].
164+
///
165+
/// Available only when the `postgres` feature is enabled.
166+
pub fn column_name(self) -> &'static str {
155167
match self {
156-
Metric::Count => "payout_count".to_string(),
157-
Metric::Paid => "payout_amount".to_string(),
168+
Metric::Count => "payout_count",
169+
Metric::Paid => "payout_amount",
158170
}
159171
}
160172
}
@@ -257,8 +269,8 @@ fn default_limit() -> u32 {
257269
100
258270
}
259271

260-
fn default_event_type() -> String {
261-
"IMPRESSION".into()
272+
fn default_event_type() -> EventType {
273+
IMPRESSION
262274
}
263275

264276
fn default_metric() -> Metric {

primitives/src/analytics/query.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ pub enum AllowedKey {
2727
OsName,
2828
}
2929

30+
impl AllowedKey {
31+
#[allow(non_snake_case)]
32+
/// Helper function to get the [`AllowedKey`] as `camelCase`.
33+
pub fn to_camelCase(&self) -> String {
34+
serde_json::to_value(self)
35+
.expect("AllowedKey should always be serializable!")
36+
.as_str()
37+
.expect("Serialized AllowedKey should be a string!")
38+
.to_string()
39+
}
40+
}
41+
3042
/// All [`AllowedKey`]s should be present in this static variable.
3143
pub static ALLOWED_KEYS: Lazy<HashSet<AllowedKey>> = Lazy::new(|| {
3244
vec![
@@ -89,7 +101,7 @@ pub struct Time {
89101
/// And it should not contain **minutes**, **seconds** or **nanoseconds**
90102
// TODO: Either Timestamp (number) or DateTime (string) de/serialization
91103
pub start: DateHour<Utc>,
92-
/// End DateHour should be after Start DateHour!
104+
/// The End [`DateHour`] which will fetch `analytics_time <= end` and should be after Start [`DateHour`]!
93105
pub end: Option<DateHour<Utc>>,
94106
// we can use `chrono_tz` to support more Timezones when needed.
95107
// #[serde(default = "default_timezone_utc")]
@@ -160,9 +172,9 @@ mod de {
160172
let timeframe = timeframe.unwrap_or(Timeframe::Day);
161173
let start = start.unwrap_or_else(|| DateHour::now() - &timeframe);
162174

163-
// if there is an End time passed, check if End is > Start
175+
// if there is an End DateHour passed, check if End is > Start
164176
match end {
165-
Some(end) if start > end => {
177+
Some(end) if start >= end => {
166178
return Err(de::Error::custom(
167179
"End time should be larger than the Start time",
168180
));
@@ -178,7 +190,7 @@ mod de {
178190
}
179191
}
180192

181-
const FIELDS: &'static [&'static str] = &["timeframe", "start", "end"];
193+
const FIELDS: &[&str] = &["timeframe", "start", "end"];
182194
deserializer.deserialize_struct("Time", FIELDS, TimeVisitor)
183195
}
184196
}

primitives/src/sentry.rs

+68-17
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,22 @@ mod event {
145145
static IMPRESSION_STRING: Lazy<String> = Lazy::new(|| EventType::Impression.to_string());
146146
static CLICK_STRING: Lazy<String> = Lazy::new(|| EventType::Click.to_string());
147147

148-
#[derive(Debug, Display, FromStr, Serialize, Deserialize, Hash, Ord, Eq, PartialEq, PartialOrd, Clone, Copy)]
148+
#[derive(
149+
Debug,
150+
Display,
151+
FromStr,
152+
Serialize,
153+
Deserialize,
154+
Hash,
155+
Ord,
156+
Eq,
157+
PartialEq,
158+
PartialOrd,
159+
Clone,
160+
Copy,
161+
)]
149162
#[display(style = "SNAKE_CASE")]
150-
#[serde(rename_all="SCREAMING_SNAKE_CASE")]
163+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
151164
pub enum EventType {
152165
Impression,
153166
Click,
@@ -300,7 +313,7 @@ pub struct FetchedAnalytics {
300313
}
301314

302315
/// The value of the requested analytics [`Metric`].
303-
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
316+
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
304317
#[serde(untagged)]
305318
pub enum FetchedMetric {
306319
Count(u32),
@@ -355,7 +368,11 @@ impl<Tz: TimeZone, Tz2: TimeZone> PartialEq<DateHour<Tz2>> for DateHour<Tz> {
355368

356369
impl<Tz: TimeZone> Ord for DateHour<Tz> {
357370
fn cmp(&self, other: &DateHour<Tz>) -> Ordering {
358-
self.date.cmp(&other.date)
371+
match self.date.cmp(&other.date) {
372+
// Only if the two dates are equal, compare the hours too!
373+
Ordering::Equal => self.hour.cmp(&other.hour),
374+
ordering => ordering,
375+
}
359376
}
360377
}
361378

@@ -365,11 +382,14 @@ impl<Tz: TimeZone, Tz2: TimeZone> PartialOrd<DateHour<Tz2>> for DateHour<Tz> {
365382
/// See [`DateTime`] implementation of `PartialOrd<DateTime<Tz2>>` for more details.
366383
fn partial_cmp(&self, other: &DateHour<Tz2>) -> Option<Ordering> {
367384
if self.date == other.date {
368-
if self.hour > other.hour {
369-
Some(Ordering::Greater)
370-
} else {
371-
Some(Ordering::Less)
372-
}
385+
self.hour.partial_cmp(&other.hour)
386+
// if self.hour > other.hour {
387+
// Some(Ordering::Greater)
388+
// } else if self.hour == other.hour {
389+
// Some(Ordering::Equal)
390+
// } else {
391+
// Some(Ordering::Less)
392+
// }
373393
} else {
374394
self.date.naive_utc().partial_cmp(&other.date.naive_utc())
375395
}
@@ -922,7 +942,8 @@ pub mod campaign_create {
922942
#[cfg(feature = "postgres")]
923943
mod postgres {
924944
use super::{
925-
Analytics, DateHour, FetchedAnalytics, FetchedMetric, MessageResponse, ValidatorMessage, EventType,
945+
Analytics, DateHour, EventType, FetchedAnalytics, FetchedMetric, MessageResponse,
946+
ValidatorMessage,
926947
};
927948
use crate::{
928949
analytics::{AnalyticsQuery, Metric},
@@ -1072,7 +1093,7 @@ mod postgres {
10721093
raw: &'a [u8],
10731094
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
10741095
let event_string = <&str as FromSql>::from_sql(ty, raw)?;
1075-
1096+
10761097
Ok(event_string.parse()?)
10771098
}
10781099
accepts!(VARCHAR, TEXT);
@@ -1129,7 +1150,7 @@ mod test {
11291150
use serde_json::{json, Value};
11301151

11311152
#[test]
1132-
pub fn de_serialize_events() {
1153+
pub fn test_de_serialize_events() {
11331154
let click = Event::Click {
11341155
publisher: ADDRESSES["publisher"],
11351156
ad_unit: Some(DUMMY_IPFS[0]),
@@ -1152,7 +1173,7 @@ mod test {
11521173
}
11531174

11541175
#[test]
1155-
fn datehour_subtract_timeframe() {
1176+
fn test_datehour_subtract_timeframe() {
11561177
// test with End of year
11571178
{
11581179
let datehour = DateHour::from_ymdh(2021, 12, 31, 22);
@@ -1175,7 +1196,7 @@ mod test {
11751196
}
11761197

11771198
#[test]
1178-
fn datehour_de_serialize_partial_ord_and_eq() {
1199+
fn test_datehour_de_serialize_partial_ord_and_eq() {
11791200
let earlier = DateHour::from_ymdh(2021, 12, 1, 16);
11801201
let later = DateHour::from_ymdh(2021, 12, 31, 16);
11811202

@@ -1189,16 +1210,46 @@ mod test {
11891210
assert_eq!(Some(Ordering::Greater), later.partial_cmp(&earlier));
11901211

11911212
// Serialize & deserialize
1192-
let json_datetime = Value::String("2021-12-1T16:00:00+02:00".into());
1213+
let json_datetime = Value::String("2021-12-01T16:00:00+02:00".into());
11931214
let datehour: DateHour<Utc> =
11941215
serde_json::from_value(json_datetime.clone()).expect("Should deserialize");
11951216
assert_eq!(
1196-
DateHour::from_ymdh(2021, 12, 1, 12),
1217+
DateHour::from_ymdh(2021, 12, 1, 14),
11971218
datehour,
11981219
"Deserialized DateHour should be 2 hours earlier to match UTC +0"
11991220
);
12001221

12011222
let serialized_value = serde_json::to_value(datehour).expect("Should serialize DateHour");
1202-
assert_eq!(json_datetime, serialized_value);
1223+
assert_eq!(
1224+
Value::String("2021-12-01T14:00:00Z".into()),
1225+
serialized_value,
1226+
"Json value should always be serialized with Z (UTC+0) timezone"
1227+
);
1228+
}
1229+
1230+
#[test]
1231+
fn test_partial_eq_with_same_datehour_but_different_zones() {
1232+
// Eq & PartialEq with different timezones
1233+
let json = json!({
1234+
"UTC+0": "2021-12-16T14:00:00+00:00",
1235+
"UTC+2": "2021-12-16T16:00:00+02:00",
1236+
});
1237+
1238+
let map: HashMap<String, DateHour<Utc>> =
1239+
serde_json::from_value(json).expect("Should deserialize");
1240+
1241+
assert_eq!(
1242+
Some(Ordering::Equal),
1243+
map["UTC+0"].partial_cmp(&map["UTC+2"])
1244+
);
1245+
1246+
assert_eq!(
1247+
map["UTC+0"], map["UTC+2"],
1248+
"DateHour should be the same after the second one is made into UTC+0"
1249+
);
1250+
assert!(
1251+
map["UTC+0"] >= map["UTC+2"],
1252+
"UTC+0 value should be equal to UTC+2"
1253+
);
12031254
}
12041255
}

primitives/src/test_util.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub static CREATOR: Lazy<Address> = Lazy::new(|| *ADDRESS_3);
1212
pub static ADVERTISER: Lazy<Address> = Lazy::new(|| *ADDRESS_4);
1313
pub static PUBLISHER: Lazy<Address> = Lazy::new(|| *ADDRESS_5);
1414
pub static GUARDIAN_2: Lazy<Address> = Lazy::new(|| *ADDRESS_6);
15+
pub static PUBLISHER_2: Lazy<Address> = Lazy::new(|| *ADDRESS_7);
1516

1617
/// passphrase: ganache0
1718
pub static ADDRESS_0: Lazy<Address> = Lazy::new(|| {

primitives/src/unified_num.rs

+39
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,52 @@ impl Mul<&UnifiedNum> for UnifiedNum {
290290
}
291291
}
292292

293+
impl Mul<u64> for &UnifiedNum {
294+
type Output = UnifiedNum;
295+
296+
fn mul(self, rhs: u64) -> Self::Output {
297+
UnifiedNum(self.0 * rhs)
298+
}
299+
}
300+
301+
impl Mul<u64> for UnifiedNum {
302+
type Output = UnifiedNum;
303+
304+
fn mul(self, rhs: u64) -> Self::Output {
305+
UnifiedNum(self.0 * rhs)
306+
}
307+
}
308+
309+
impl Mul<UnifiedNum> for u64 {
310+
type Output = UnifiedNum;
311+
312+
fn mul(self, rhs: UnifiedNum) -> Self::Output {
313+
UnifiedNum(self * rhs.0)
314+
}
315+
}
316+
317+
impl Mul<&UnifiedNum> for u64 {
318+
type Output = UnifiedNum;
319+
320+
fn mul(self, rhs: &UnifiedNum) -> Self::Output {
321+
UnifiedNum(self * rhs.0)
322+
}
323+
}
324+
293325
impl<'a> Sum<&'a UnifiedNum> for Option<UnifiedNum> {
294326
fn sum<I: Iterator<Item = &'a UnifiedNum>>(mut iter: I) -> Self {
295327
iter.try_fold(0_u64, |acc, unified| acc.checked_add(unified.0))
296328
.map(UnifiedNum)
297329
}
298330
}
299331

332+
impl<'a> Sum<UnifiedNum> for Option<UnifiedNum> {
333+
fn sum<I: Iterator<Item = UnifiedNum>>(mut iter: I) -> Self {
334+
iter.try_fold(0_u64, |acc, unified| acc.checked_add(unified.0))
335+
.map(UnifiedNum)
336+
}
337+
}
338+
300339
impl CheckedAdd for UnifiedNum {
301340
fn checked_add(&self, v: &Self) -> Option<Self> {
302341
self.0.checked_add(v.0).map(Self)

0 commit comments

Comments
 (0)