mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
add session duration
This commit is contained in:
1 parent
7b89ad7c2f
commit
e4f37900e2
7 files changed
+59
-14
No files matched your search
@@ -13,10 +13,16 @@ CREATE TABLE analytics_app_active_users (
|
||||
-- Who sent the event.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- App version is updated on every write.
|
||||
app_version VARCHAR(255) NOT NULL,
|
||||
-- Updated on every write.
|
||||
-- The "user agent" or app version string of the client.
|
||||
app_version VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- IP address is updated on every write.
|
||||
-- Updated on every write.
|
||||
-- How long the current user's session has been open
|
||||
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The user's last known IP address.
|
||||
ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- Incrementing count of events.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE analytics_totals (
|
||||
CREATE TABLE analytics_app_active_users (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
@@ -13,17 +13,34 @@ CREATE TABLE analytics_totals (
|
||||
-- Who sent the event.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The "user agent" or app version string of the client.
|
||||
app_version VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- How long the current user's session has been open
|
||||
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The user's last known IP address.
|
||||
ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- Incrementing count of events.
|
||||
event_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
|
||||
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
|
||||
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== RECORD TIMESTAMPS ==========
|
||||
|
||||
first_event_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_event_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
-- Written at create only.
|
||||
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Updated on every ping.
|
||||
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (app_namespace, user_token), -- For now
|
||||
INDEX idx_last_event_at (last_event_at)
|
||||
INDEX idx_last_active_at (last_active_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -14,6 +14,9 @@ pub struct LogAppActiveUserRequest {
|
||||
/// If set together with `maybe_app_name`, the two will be
|
||||
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
|
||||
pub maybe_app_version: Option<String>,
|
||||
|
||||
/// How long the user has been active in the app, in seconds.
|
||||
pub maybe_session_duration_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
|
||||
@@ -11,6 +11,8 @@ pub async fn log_active_user(
|
||||
request: LogAppActiveUserRequest,
|
||||
) -> Result<LogAppActiveUserResponse, StorytellerError> {
|
||||
|
||||
// TODO(bt): This is gross and doesn't automatically handle field changes.
|
||||
|
||||
let mut query = HashMap::new();
|
||||
|
||||
if let Some(app_name) = request.maybe_app_name {
|
||||
@@ -20,6 +22,10 @@ pub async fn log_active_user(
|
||||
if let Some(app_version) = request.maybe_app_version {
|
||||
query.insert("maybe_app_version".to_string(), app_version);
|
||||
}
|
||||
|
||||
if let Some(maybe_session_duration_seconds) = request.maybe_session_duration_seconds {
|
||||
query.insert("maybe_session_duration_seconds".to_string(), maybe_session_duration_seconds.to_string());
|
||||
}
|
||||
|
||||
Ok(basic_query_string_post_request(
|
||||
api_host,
|
||||
|
||||
+8
-1
@@ -16,6 +16,7 @@ use log::{error, info};
|
||||
use reqwest::Url;
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs, Task};
|
||||
use sqlite_tasks::queries::update_task_status::{update_task_status, UpdateTaskArgs};
|
||||
use std::time::Instant;
|
||||
use storyteller_client::analytics::log_active_user::log_active_user;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
@@ -34,10 +35,12 @@ pub async fn storyteller_activity_thread(
|
||||
app_env_configs: AppEnvConfigs,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
let startup = Instant::now();
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_env_configs,
|
||||
&storyteller_creds_manager,
|
||||
startup,
|
||||
).await;
|
||||
if let Err(err) = res {
|
||||
error!("An error occurred: {:?}", err);
|
||||
@@ -50,6 +53,7 @@ pub async fn storyteller_activity_thread(
|
||||
async fn polling_loop(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
startup: Instant,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
let creds = storyteller_creds_manager.get_credentials()?;
|
||||
@@ -68,13 +72,16 @@ async fn polling_loop(
|
||||
},
|
||||
};
|
||||
|
||||
let time_since_startup = Instant::now().duration_since(startup);
|
||||
|
||||
let request = LogAppActiveUserRequest {
|
||||
maybe_app_name: Some(CLIENT_NAME.to_string()),
|
||||
maybe_app_version: Some(CLIENT_VERSION.to_string()),
|
||||
maybe_session_duration_seconds: Some(time_since_startup.as_secs()),
|
||||
};
|
||||
|
||||
info!("Logging active user with storyteller.");
|
||||
|
||||
|
||||
let result = log_active_user(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
|
||||
+8
-3
@@ -8,11 +8,12 @@ use sqlx::MySql;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct UpsertAnalyticsAppActiveUser<'a> {
|
||||
// TODO: Swap PaymentsNamespace (artcraft,fakeyou) for an AnalyticsNamespace enum.
|
||||
// TODO: Swap PaymentsNamespace (artcraft,fakeyou,etc.) for an AnalyticsNamespace enum.
|
||||
pub namespace: PaymentsNamespace,
|
||||
pub user_token: &'a UserToken,
|
||||
pub ip_address: &'a str,
|
||||
pub app_version: &'a str,
|
||||
pub app_version: Option<&'a str>,
|
||||
pub session_duration_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
impl <'a> UpsertAnalyticsAppActiveUser<'a> {
|
||||
@@ -24,12 +25,14 @@ SET
|
||||
app_namespace = ?,
|
||||
user_token = ?,
|
||||
app_version = ?,
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
first_active_at = NOW(),
|
||||
last_active_at = NOW()
|
||||
ON DUPLICATE KEY UPDATE
|
||||
app_version = ?,
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
last_active_at = NOW()
|
||||
@@ -38,15 +41,17 @@ ON DUPLICATE KEY UPDATE
|
||||
self.namespace.to_str(),
|
||||
self.user_token.as_str(),
|
||||
self.app_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
// Update case
|
||||
self.app_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn upsert_with_connection(
|
||||
&self,
|
||||
&self,
|
||||
mysql_connection: &mut PoolConnection<MySql>
|
||||
) -> Result<(), MysqlError<UpsertError>> {
|
||||
let _query_result = self.query()
|
||||
|
||||
+3
-2
@@ -86,14 +86,15 @@ pub async fn log_app_active_user_handler(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
maybe_recorded_version.unwrap_or_else(|| "unknown".to_string())
|
||||
maybe_recorded_version
|
||||
};
|
||||
|
||||
let upsert = UpsertAnalyticsAppActiveUser {
|
||||
namespace: PaymentsNamespace::Artcraft,
|
||||
user_token: &user_token,
|
||||
ip_address: &ip_address,
|
||||
app_version: &app_version,
|
||||
app_version: app_version.as_deref(),
|
||||
session_duration_seconds: request.maybe_session_duration_seconds,
|
||||
};
|
||||
|
||||
upsert.upsert_with_connection(&mut mysql_connection).await?;
|
||||
|
||||
Reference in new issue
Block a user