systemprompt_cli/commands/core/content/analytics/
clicks.rs1use crate::cli_settings::CliConfig;
2use crate::commands::core::content::types::{ClickRow, ClicksOutput};
3use crate::shared::CommandResult;
4use anyhow::Result;
5use clap::Args;
6use systemprompt_content::LinkAnalyticsService;
7use systemprompt_database::DbPool;
8use systemprompt_identifiers::LinkId;
9use systemprompt_runtime::AppContext;
10
11#[derive(Debug, Args)]
12pub struct ClicksArgs {
13 #[arg(help = "Link ID")]
15 pub link_id: String,
16
17 #[arg(long, default_value = "20")]
18 pub limit: i64,
19
20 #[arg(long, default_value = "0")]
21 pub offset: i64,
22}
23
24pub async fn execute(args: ClicksArgs, config: &CliConfig) -> Result<CommandResult<ClicksOutput>> {
25 let ctx = AppContext::new().await?;
26 execute_with_pool(args, ctx.db_pool(), config).await
27}
28
29pub async fn execute_with_pool(
30 args: ClicksArgs,
31 pool: &DbPool,
32 _config: &CliConfig,
33) -> Result<CommandResult<ClicksOutput>> {
34 let service = LinkAnalyticsService::new(pool)?;
35
36 let link_id = LinkId::new(args.link_id.clone());
37 let clicks = service
38 .get_link_clicks(&link_id, Some(args.limit), Some(args.offset))
39 .await?;
40
41 let total = clicks.len() as i64;
42 let click_rows: Vec<ClickRow> = clicks
43 .into_iter()
44 .filter_map(|click| {
45 Some(ClickRow {
46 click_id: click.id.to_string(),
47 session_id: click.session_id,
48 user_id: click.user_id,
49 clicked_at: click.clicked_at?,
50 referrer_page: click.referrer_page,
51 device_type: click.device_type,
52 country: click.country,
53 is_conversion: click.is_conversion.unwrap_or(false),
54 })
55 })
56 .collect();
57
58 let output = ClicksOutput {
59 link_id,
60 clicks: click_rows,
61 total,
62 };
63
64 Ok(CommandResult::table(output)
65 .with_title("Link Clicks")
66 .with_columns(vec![
67 "click_id".to_string(),
68 "session_id".to_string(),
69 "clicked_at".to_string(),
70 "device_type".to_string(),
71 "country".to_string(),
72 ]))
73}