Fix new analytics backend bucketing and revenue (#6052)
* Fix analytics backend QA items * cargo prepare
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT\n WIDTH_BUCKET(\n EXTRACT(EPOCH FROM created)::bigint,\n EXTRACT(EPOCH FROM $1::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n $3::integer\n ) AS bucket,\n CASE WHEN $5 THEN mod_id ELSE 0 END AS mod_id,\n SUM(amount) amount_sum\n FROM payouts_values\n WHERE\n user_id = $4\n -- only project revenue is counted here\n -- for affiliate code revenue, see `affiliate_code_revenue``\n AND payouts_values.mod_id IS NOT NULL\n AND created BETWEEN $1 AND $2\n GROUP BY bucket, mod_id",
|
"query": "SELECT\n WIDTH_BUCKET(\n EXTRACT(EPOCH FROM created)::bigint,\n EXTRACT(EPOCH FROM $1::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n $3::integer\n ) AS bucket,\n mod_id,\n SUM(amount) amount_sum\n FROM payouts_values\n WHERE\n -- only project revenue is counted here\n -- for affiliate code revenue, see `affiliate_code_revenue`\n payouts_values.mod_id IS NOT NULL\n AND payouts_values.mod_id = ANY($4)\n AND created BETWEEN $1 AND $2\n GROUP BY bucket, mod_id",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -24,15 +24,14 @@
|
|||||||
"Timestamptz",
|
"Timestamptz",
|
||||||
"Timestamptz",
|
"Timestamptz",
|
||||||
"Int4",
|
"Int4",
|
||||||
"Int8",
|
"Int8Array"
|
||||||
"Bool"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
null,
|
null,
|
||||||
null,
|
true,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "b617ed1011341416c1c012c00e716a59873a8204e1b122c7c517a1c4437edfb4"
|
"hash": "8d38218e5a0c9297be7c6c77acf40a2339b12ff15f1f9e53a27a1c599a33e43b"
|
||||||
}
|
}
|
||||||
@@ -169,6 +169,8 @@ pub enum ProjectDownloadsField {
|
|||||||
Domain,
|
Domain,
|
||||||
/// Modrinth site path which was visited, e.g. `/mod/foo`.
|
/// Modrinth site path which was visited, e.g. `/mod/foo`.
|
||||||
SitePath,
|
SitePath,
|
||||||
|
/// Whether these downloads were monetized or not.
|
||||||
|
Monetized,
|
||||||
/// What country these downloads came from.
|
/// What country these downloads came from.
|
||||||
///
|
///
|
||||||
/// To anonymize the data, the country may be reported as `XX`.
|
/// To anonymize the data, the country may be reported as `XX`.
|
||||||
@@ -329,6 +331,9 @@ pub struct ProjectDownloads {
|
|||||||
/// [`ProjectDownloadsField::VersionId`].
|
/// [`ProjectDownloadsField::VersionId`].
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
version_id: Option<VersionId>,
|
version_id: Option<VersionId>,
|
||||||
|
/// [`ProjectDownloadsField::Monetized`].
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
monetized: Option<bool>,
|
||||||
/// [`ProjectDownloadsField::Country`].
|
/// [`ProjectDownloadsField::Country`].
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
country: Option<String>,
|
country: Option<String>,
|
||||||
@@ -473,6 +478,7 @@ mod query {
|
|||||||
pub domain: String,
|
pub domain: String,
|
||||||
pub site_path: String,
|
pub site_path: String,
|
||||||
pub version_id: DBVersionId,
|
pub version_id: DBVersionId,
|
||||||
|
pub monetized: i8,
|
||||||
pub country: String,
|
pub country: String,
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
pub game_version: String,
|
pub game_version: String,
|
||||||
@@ -485,6 +491,7 @@ mod query {
|
|||||||
const USE_DOMAIN: &str = "{use_domain: Bool}";
|
const USE_DOMAIN: &str = "{use_domain: Bool}";
|
||||||
const USE_SITE_PATH: &str = "{use_site_path: Bool}";
|
const USE_SITE_PATH: &str = "{use_site_path: Bool}";
|
||||||
const USE_VERSION_ID: &str = "{use_version_id: Bool}";
|
const USE_VERSION_ID: &str = "{use_version_id: Bool}";
|
||||||
|
const USE_MONETIZED: &str = "{use_monetized: Bool}";
|
||||||
const USE_COUNTRY: &str = "{use_country: Bool}";
|
const USE_COUNTRY: &str = "{use_country: Bool}";
|
||||||
const USE_REASON: &str = "{use_reason: Bool}";
|
const USE_REASON: &str = "{use_reason: Bool}";
|
||||||
const USE_GAME_VERSION: &str = "{use_game_version: Bool}";
|
const USE_GAME_VERSION: &str = "{use_game_version: Bool}";
|
||||||
@@ -497,6 +504,7 @@ mod query {
|
|||||||
if({USE_DOMAIN}, domain, '') AS domain,
|
if({USE_DOMAIN}, domain, '') AS domain,
|
||||||
if({USE_SITE_PATH}, site_path, '') AS site_path,
|
if({USE_SITE_PATH}, site_path, '') AS site_path,
|
||||||
if({USE_VERSION_ID}, version_id, 0) AS version_id,
|
if({USE_VERSION_ID}, version_id, 0) AS version_id,
|
||||||
|
if({USE_MONETIZED}, CAST(user_id != 0 AS Int8), -1) AS monetized,
|
||||||
if({USE_COUNTRY}, country, '') AS country,
|
if({USE_COUNTRY}, country, '') AS country,
|
||||||
if({USE_REASON}, reason, '') AS reason,
|
if({USE_REASON}, reason, '') AS reason,
|
||||||
if({USE_GAME_VERSION}, game_version, '') AS game_version,
|
if({USE_GAME_VERSION}, game_version, '') AS game_version,
|
||||||
@@ -509,7 +517,7 @@ mod query {
|
|||||||
-- not the possibly-zero one,
|
-- not the possibly-zero one,
|
||||||
-- by using `downloads.project_id` instead of `project_id`
|
-- by using `downloads.project_id` instead of `project_id`
|
||||||
AND downloads.project_id IN {PROJECT_IDS}
|
AND downloads.project_id IN {PROJECT_IDS}
|
||||||
GROUP BY bucket, project_id, domain, site_path, version_id, country, reason, game_version, loader"
|
GROUP BY bucket, project_id, domain, site_path, version_id, monetized, country, reason, game_version, loader"
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -731,6 +739,7 @@ pub async fn fetch_analytics(
|
|||||||
("use_domain", uses(F::Domain)),
|
("use_domain", uses(F::Domain)),
|
||||||
("use_site_path", uses(F::SitePath)),
|
("use_site_path", uses(F::SitePath)),
|
||||||
("use_version_id", uses(F::VersionId)),
|
("use_version_id", uses(F::VersionId)),
|
||||||
|
("use_monetized", uses(F::Monetized)),
|
||||||
("use_country", uses(F::Country)),
|
("use_country", uses(F::Country)),
|
||||||
("use_reason", uses(F::Reason)),
|
("use_reason", uses(F::Reason)),
|
||||||
("use_game_version", uses(F::GameVersion)),
|
("use_game_version", uses(F::GameVersion)),
|
||||||
@@ -749,6 +758,11 @@ pub async fn fetch_analytics(
|
|||||||
domain: none_if_empty(row.domain),
|
domain: none_if_empty(row.domain),
|
||||||
site_path: none_if_empty(row.site_path),
|
site_path: none_if_empty(row.site_path),
|
||||||
version_id: none_if_zero_version_id(row.version_id),
|
version_id: none_if_zero_version_id(row.version_id),
|
||||||
|
monetized: match row.monetized {
|
||||||
|
0 => Some(false),
|
||||||
|
1 => Some(true),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
country,
|
country,
|
||||||
reason: none_if_empty(row.reason)
|
reason: none_if_empty(row.reason)
|
||||||
.and_then(|s| s.parse().ok()),
|
.and_then(|s| s.parse().ok()),
|
||||||
@@ -821,11 +835,14 @@ pub async fn fetch_analytics(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(metrics) = &req.return_metrics.project_revenue {
|
if req.return_metrics.project_revenue.is_some() {
|
||||||
if !scopes.contains(Scopes::PAYOUTS_READ) {
|
if !scopes.contains(Scopes::PAYOUTS_READ) {
|
||||||
return Err(AuthenticationError::InvalidCredentials.into());
|
return Err(AuthenticationError::InvalidCredentials.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let project_id_values =
|
||||||
|
project_ids.iter().map(|id| id.0).collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut rows = sqlx::query!(
|
let mut rows = sqlx::query!(
|
||||||
"SELECT
|
"SELECT
|
||||||
WIDTH_BUCKET(
|
WIDTH_BUCKET(
|
||||||
@@ -834,21 +851,20 @@ pub async fn fetch_analytics(
|
|||||||
EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint,
|
EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint,
|
||||||
$3::integer
|
$3::integer
|
||||||
) AS bucket,
|
) AS bucket,
|
||||||
CASE WHEN $5 THEN mod_id ELSE 0 END AS mod_id,
|
mod_id,
|
||||||
SUM(amount) amount_sum
|
SUM(amount) amount_sum
|
||||||
FROM payouts_values
|
FROM payouts_values
|
||||||
WHERE
|
WHERE
|
||||||
user_id = $4
|
|
||||||
-- only project revenue is counted here
|
-- only project revenue is counted here
|
||||||
-- for affiliate code revenue, see `affiliate_code_revenue``
|
-- for affiliate code revenue, see `affiliate_code_revenue`
|
||||||
AND payouts_values.mod_id IS NOT NULL
|
payouts_values.mod_id IS NOT NULL
|
||||||
|
AND payouts_values.mod_id = ANY($4)
|
||||||
AND created BETWEEN $1 AND $2
|
AND created BETWEEN $1 AND $2
|
||||||
GROUP BY bucket, mod_id",
|
GROUP BY bucket, mod_id",
|
||||||
req.time_range.start,
|
req.time_range.start,
|
||||||
req.time_range.end,
|
req.time_range.end,
|
||||||
num_time_slices as i64,
|
num_time_slices as i64,
|
||||||
DBUserId::from(user.id) as DBUserId,
|
&project_id_values,
|
||||||
metrics.bucket_by.contains(&ProjectRevenueField::ProjectId),
|
|
||||||
)
|
)
|
||||||
.fetch(&**pool);
|
.fetch(&**pool);
|
||||||
while let Some(row) = rows.next().await.transpose()? {
|
while let Some(row) = rows.next().await.transpose()? {
|
||||||
|
|||||||
Reference in New Issue
Block a user