Skip to main content

systemprompt_cli/commands/core/content/analytics/
journey.rs

1//! `core content analytics journey` command.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use crate::cli_settings::CliConfig;
7use crate::commands::core::content::types::{JourneyNode, JourneyOutput};
8use crate::context::CommandContext;
9use crate::shared::CommandOutput;
10use anyhow::Result;
11use clap::Args;
12use systemprompt_content::{ContentRepositories, LinkAnalyticsService};
13use systemprompt_database::DbPool;
14
15#[derive(Debug, Clone, Copy, Args)]
16pub struct JourneyArgs {
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: JourneyArgs, ctx: &CommandContext) -> Result<CommandOutput> {
25    execute_with_pool(args, &ctx.db_pool().await?, &ctx.cli).await
26}
27
28pub async fn execute_with_pool(
29    args: JourneyArgs,
30    pool: &DbPool,
31    _config: &CliConfig,
32) -> Result<CommandOutput> {
33    let repositories = ContentRepositories::new(pool)?;
34    let service = LinkAnalyticsService::new(repositories.link, repositories.link_analytics);
35
36    let nodes = service
37        .get_content_journey_map(Some(args.limit), Some(args.offset))
38        .await?;
39
40    let journey_nodes: Vec<JourneyNode> = nodes
41        .into_iter()
42        .map(|node| JourneyNode {
43            source_content_id: node.source_content_id,
44            target_url: node.target_url,
45            click_count: node.click_count,
46        })
47        .collect();
48
49    let output = JourneyOutput {
50        nodes: journey_nodes,
51    };
52
53    Ok(CommandOutput::table_of(
54        vec!["source_content_id", "target_url", "click_count"],
55        &output.nodes,
56    )
57    .with_title("Content Journey"))
58}