Skip to content

Commit a6bc1d8

Browse files
authored
Merge pull request #496 from AmbireTech/analytics-multichain-support
Analytics multichain support
2 parents 194cb33 + 98172e1 commit a6bc1d8

File tree

12 files changed

+299
-39
lines changed

12 files changed

+299
-39
lines changed

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

primitives/src/analytics.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
sentry::{EventType, IMPRESSION},
3-
Address, CampaignId, ValidatorId, IPFS,
3+
Address, CampaignId, ChainId, ValidatorId, IPFS,
44
};
55
use parse_display::Display;
66
use serde::{Deserialize, Serialize};
@@ -115,6 +115,8 @@ pub struct AnalyticsQuery {
115115
pub hostname: Option<String>,
116116
pub country: Option<String>,
117117
pub os_name: Option<OperatingSystem>,
118+
#[serde(default)]
119+
pub chains: Vec<ChainId>,
118120
}
119121

120122
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Display, Hash, Eq)]

primitives/src/chain.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ use serde::{Deserialize, Serialize};
22
use std::fmt;
33

44
use crate::{config::TokenInfo, util::ApiUrl, Address, Campaign, Channel};
5+
use parse_display::Display;
56

67
/// The Id of the chain
78
///
89
/// # Ethereum Virtual Machine
910
///
1011
/// For all the EVM-compatible Chain IDs visit <https://chainid.network>
11-
#[derive(Serialize, Deserialize, Hash, Clone, Copy, Eq, PartialEq)]
12+
#[derive(Serialize, Deserialize, Hash, Clone, Copy, Eq, PartialEq, Display)]
1213
#[serde(transparent)]
1314
pub struct ChainId(u32);
1415

@@ -21,6 +22,10 @@ impl ChainId {
2122

2223
Self(id)
2324
}
25+
26+
pub fn to_u32(self) -> u32 {
27+
self.0
28+
}
2429
}
2530

2631
impl From<u32> for ChainId {
@@ -110,3 +115,34 @@ impl ChainOf<Campaign> {
110115
}
111116
}
112117
}
118+
119+
#[cfg(feature = "postgres")]
120+
pub mod postgres {
121+
use super::ChainId;
122+
use bytes::BytesMut;
123+
use std::error::Error;
124+
use tokio_postgres::types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type};
125+
126+
impl<'a> FromSql<'a> for ChainId {
127+
fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<ChainId, Box<dyn Error + Sync + Send>> {
128+
let value = <i32 as FromSql>::from_sql(ty, raw)?;
129+
130+
Ok(ChainId(u32::try_from(value)?))
131+
}
132+
accepts!(INT4);
133+
}
134+
135+
impl ToSql for ChainId {
136+
fn to_sql(
137+
&self,
138+
ty: &Type,
139+
w: &mut BytesMut,
140+
) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
141+
<i32 as ToSql>::to_sql(&self.0.try_into()?, ty, w)
142+
}
143+
144+
accepts!(INT4);
145+
146+
to_sql_checked!();
147+
}
148+
}

primitives/src/sentry.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
balances::BalancesState,
44
spender::Spender,
55
validator::{ApproveState, Heartbeat, MessageTypes, NewState, Type as MessageType},
6-
Address, Balances, CampaignId, UnifiedMap, UnifiedNum, ValidatorId, IPFS,
6+
Address, Balances, CampaignId, ChainId, UnifiedMap, UnifiedNum, ValidatorId, IPFS,
77
};
88
use chrono::{
99
serde::ts_milliseconds, Date, DateTime, Datelike, Duration, NaiveDate, TimeZone, Timelike, Utc,
@@ -284,6 +284,7 @@ pub struct UpdateAnalytics {
284284
pub hostname: Option<String>,
285285
pub country: Option<String>,
286286
pub os_name: OperatingSystem,
287+
pub chain_id: ChainId,
287288
pub event_type: EventType,
288289
pub amount_to_add: UnifiedNum,
289290
pub count_to_add: i32,

sentry/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ once_cell = "1.5.2"
6161
woothee = "0.13"
6262
# Making requests to the platform
6363
reqwest = { version = "0.11", features = ["json", "cookies"] }
64+
serde_qs = "0.9.2"
6465

6566
[dev-dependencies]
6667
pretty_assertions = "1"

sentry/migrations/20190806011140_initial-tables/up.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ CREATE TABLE analytics (
9393
hostname varchar(255) NOT NULL,
9494
country varchar(255) NOT NULL,
9595
os_name varchar(255) NOT NULL,
96+
chain_id integer NOT NULL,
9697
event_type varchar(255) NOT NULL,
9798
payout_amount bigint NOT NULL DEFAULT 0,
9899
payout_count integer NOT NULL DEFAULT 0,
99100
-- Do not rename the Primary key constraint (`analytics_pkey`)!
100-
PRIMARY KEY (campaign_id, "time", ad_unit, ad_slot, ad_slot_type, advertiser, publisher, hostname, country, os_name, event_type)
101+
PRIMARY KEY (campaign_id, "time", ad_unit, ad_slot, ad_slot_type, advertiser, publisher, hostname, country, os_name, chain_id, event_type)
101102
);

sentry/src/analytics.rs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use crate::{
55
use primitives::{
66
analytics::OperatingSystem,
77
sentry::{DateHour, Event, UpdateAnalytics},
8-
Address, Campaign, UnifiedNum,
8+
Address, Campaign, ChainOf, UnifiedNum,
99
};
1010
use std::collections::HashMap;
1111

1212
/// Validator fees will not be included in analytics
1313
pub async fn record(
1414
pool: &DbPool,
15-
campaign: &Campaign,
15+
campaign_context: &ChainOf<Campaign>,
1616
session: &Session,
1717
events_with_payouts: Vec<(Event, Address, UnifiedNum)>,
1818
) -> Result<(), PoolError> {
@@ -43,7 +43,8 @@ pub async fn record(
4343
ad_slot,
4444
} => (*publisher, *ad_unit, referrer.clone(), *ad_slot),
4545
};
46-
let ad_unit = campaign
46+
let ad_unit = campaign_context
47+
.context
4748
.ad_units
4849
.iter()
4950
.find(|ad_unit| ad_unit.ipfs == event_ad_unit);
@@ -70,16 +71,17 @@ pub async fn record(
7071
analytics.count_to_add += 1;
7172
})
7273
.or_insert_with(|| UpdateAnalytics {
73-
campaign_id: campaign.id,
74+
campaign_id: campaign_context.context.id,
7475
time: datehour,
7576
ad_unit,
7677
ad_slot,
7778
ad_slot_type,
78-
advertiser: campaign.creator,
79+
advertiser: campaign_context.context.creator,
7980
publisher,
8081
hostname,
8182
country: session.country.to_owned(),
8283
os_name: os_name.clone(),
84+
chain_id: campaign_context.chain.chain_id,
8385
event_type,
8486
amount_to_add: payout_amount,
8587
count_to_add: 1,
@@ -95,6 +97,7 @@ pub async fn record(
9597
#[cfg(test)]
9698
mod test {
9799
use super::*;
100+
use crate::test_util::setup_dummy_app;
98101
use primitives::{
99102
sentry::{Analytics, CLICK, IMPRESSION},
100103
test_util::{DUMMY_CAMPAIGN, DUMMY_IPFS, PUBLISHER},
@@ -164,6 +167,8 @@ mod test {
164167

165168
#[tokio::test]
166169
async fn test_analytics_recording_with_empty_events() {
170+
let app = setup_dummy_app().await;
171+
167172
let test_events = get_test_events();
168173
let database = DATABASE_POOL.get().await.expect("Should get a DB pool");
169174

@@ -186,9 +191,22 @@ mod test {
186191
test_events["impression"].clone(),
187192
];
188193

189-
record(&database.clone(), &campaign, &session, input_events.clone())
190-
.await
191-
.expect("should record");
194+
let dummy_channel = DUMMY_CAMPAIGN.channel;
195+
let channel_chain = app
196+
.config
197+
.find_chain_of(dummy_channel.token)
198+
.expect("Channel token should be whitelisted in config!");
199+
let channel_context = channel_chain.with_channel(dummy_channel);
200+
let campaign_context = channel_context.clone().with(campaign);
201+
202+
record(
203+
&database.clone(),
204+
&campaign_context,
205+
&session,
206+
input_events.clone(),
207+
)
208+
.await
209+
.expect("should record");
192210

193211
let analytics = get_all_analytics(&database.pool)
194212
.await
@@ -218,6 +236,8 @@ mod test {
218236

219237
#[tokio::test]
220238
async fn test_recording_with_session() {
239+
let app = setup_dummy_app().await;
240+
221241
let database = DATABASE_POOL.get().await.expect("Should get a DB pool");
222242

223243
setup_test_migrations(database.pool.clone())
@@ -247,9 +267,21 @@ mod test {
247267
test_events["impression"].clone(),
248268
];
249269

250-
record(&database.clone(), &campaign, &session, input_events.clone())
251-
.await
252-
.expect("should record");
270+
let dummy_channel = DUMMY_CAMPAIGN.channel;
271+
let channel_chain = app
272+
.config
273+
.find_chain_of(dummy_channel.token)
274+
.expect("Channel token should be whitelisted in config!");
275+
let channel_context = channel_chain.with_channel(dummy_channel);
276+
let campaign_context = channel_context.clone().with(campaign);
277+
record(
278+
&database.clone(),
279+
&campaign_context,
280+
&session,
281+
input_events.clone(),
282+
)
283+
.await
284+
.expect("should record");
253285

254286
let analytics = get_all_analytics(&database.pool)
255287
.await

0 commit comments

Comments
 (0)