refactor: extract who result types to core::who_types for TUI reuse
Move the 14 result structs and enums (WhoResult, ExpertResult, Expert, ScoreComponents, ExpertMrDetail, WorkloadResult, WorkloadIssue, WorkloadMr, WorkloadDiscussion, ReviewsResult, ReviewCategory, ActiveResult, ActiveDiscussion, OverlapResult, OverlapUser) from cli::commands::who into a new core::who_types module. The TUI Who screen needs these types to render results, but importing from the CLI layer would create a circular dependency (TUI -> CLI -> core). By placing them in core, both the CLI and TUI can depend on them cleanly. The CLI module re-exports all types via `pub use crate::core::who_types::*` so existing consumers are unaffected.
This commit is contained in:
@@ -36,7 +36,7 @@ pub use list::{
|
||||
ListFilters, MrListFilters, NoteListFilters, open_issue_in_browser, open_mr_in_browser,
|
||||
print_list_issues, print_list_issues_json, print_list_mrs, print_list_mrs_json,
|
||||
print_list_notes, print_list_notes_csv, print_list_notes_json, print_list_notes_jsonl,
|
||||
query_notes, run_list_issues, run_list_mrs,
|
||||
query_issues, query_mrs, query_notes, run_list_issues, run_list_mrs,
|
||||
};
|
||||
pub use search::{
|
||||
SearchCliFilters, SearchResponse, print_search_results, print_search_results_json, run_search,
|
||||
@@ -50,4 +50,7 @@ pub use sync::{SyncOptions, SyncResult, print_sync, print_sync_json, run_sync};
|
||||
pub use sync_status::{print_sync_status, print_sync_status_json, run_sync_status};
|
||||
pub use timeline::{TimelineParams, print_timeline, print_timeline_json_with_meta, run_timeline};
|
||||
pub use trace::{parse_trace_path, print_trace, print_trace_json};
|
||||
pub use who::{WhoRun, print_who_human, print_who_json, run_who};
|
||||
pub use who::{
|
||||
WhoRun, half_life_decay, print_who_human, print_who_json, query_active, query_expert,
|
||||
query_overlap, query_reviews, query_workload, run_who,
|
||||
};
|
||||
|
||||
@@ -79,6 +79,15 @@ fn resolve_mode<'a>(args: &'a WhoArgs) -> Result<WhoMode<'a>> {
|
||||
}
|
||||
|
||||
// ─── Result Types ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Shared result types live in core::who_types. Re-exported here for
|
||||
// backwards-compatible use within the CLI.
|
||||
|
||||
pub use crate::core::who_types::{
|
||||
ActiveDiscussion, ActiveResult, Expert, ExpertMrDetail, ExpertResult, OverlapResult,
|
||||
OverlapUser, ReviewCategory, ReviewsResult, ScoreComponents, WhoResult, WorkloadDiscussion,
|
||||
WorkloadIssue, WorkloadMr, WorkloadResult,
|
||||
};
|
||||
|
||||
/// Top-level run result: carries resolved inputs + the mode-specific result.
|
||||
pub struct WhoRun {
|
||||
@@ -98,166 +107,6 @@ pub struct WhoResolvedInput {
|
||||
pub limit: u16,
|
||||
}
|
||||
|
||||
/// Top-level result enum -- one variant per mode.
|
||||
pub enum WhoResult {
|
||||
Expert(ExpertResult),
|
||||
Workload(WorkloadResult),
|
||||
Reviews(ReviewsResult),
|
||||
Active(ActiveResult),
|
||||
Overlap(OverlapResult),
|
||||
}
|
||||
|
||||
// --- Expert ---
|
||||
|
||||
pub struct ExpertResult {
|
||||
pub path_query: String,
|
||||
/// "exact" or "prefix" -- how the path was matched in SQL.
|
||||
pub path_match: String,
|
||||
pub experts: Vec<Expert>,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
pub struct Expert {
|
||||
pub username: String,
|
||||
pub score: i64,
|
||||
/// Unrounded f64 score (only populated when explain_score is set).
|
||||
pub score_raw: Option<f64>,
|
||||
/// Per-component score breakdown (only populated when explain_score is set).
|
||||
pub components: Option<ScoreComponents>,
|
||||
pub review_mr_count: u32,
|
||||
pub review_note_count: u32,
|
||||
pub author_mr_count: u32,
|
||||
pub last_seen_ms: i64,
|
||||
/// Stable MR references like "group/project!123"
|
||||
pub mr_refs: Vec<String>,
|
||||
pub mr_refs_total: u32,
|
||||
pub mr_refs_truncated: bool,
|
||||
/// Per-MR detail breakdown (only populated when --detail is set)
|
||||
pub details: Option<Vec<ExpertMrDetail>>,
|
||||
}
|
||||
|
||||
/// Per-component score breakdown for explain mode.
|
||||
pub struct ScoreComponents {
|
||||
pub author: f64,
|
||||
pub reviewer_participated: f64,
|
||||
pub reviewer_assigned: f64,
|
||||
pub notes: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExpertMrDetail {
|
||||
pub mr_ref: String,
|
||||
pub title: String,
|
||||
/// "R", "A", or "A+R"
|
||||
pub role: String,
|
||||
pub note_count: u32,
|
||||
pub last_activity_ms: i64,
|
||||
}
|
||||
|
||||
// --- Workload ---
|
||||
|
||||
pub struct WorkloadResult {
|
||||
pub username: String,
|
||||
pub assigned_issues: Vec<WorkloadIssue>,
|
||||
pub authored_mrs: Vec<WorkloadMr>,
|
||||
pub reviewing_mrs: Vec<WorkloadMr>,
|
||||
pub unresolved_discussions: Vec<WorkloadDiscussion>,
|
||||
pub assigned_issues_truncated: bool,
|
||||
pub authored_mrs_truncated: bool,
|
||||
pub reviewing_mrs_truncated: bool,
|
||||
pub unresolved_discussions_truncated: bool,
|
||||
}
|
||||
|
||||
pub struct WorkloadIssue {
|
||||
pub iid: i64,
|
||||
/// Canonical reference: `group/project#iid`
|
||||
pub ref_: String,
|
||||
pub title: String,
|
||||
pub project_path: String,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
pub struct WorkloadMr {
|
||||
pub iid: i64,
|
||||
/// Canonical reference: `group/project!iid`
|
||||
pub ref_: String,
|
||||
pub title: String,
|
||||
pub draft: bool,
|
||||
pub project_path: String,
|
||||
pub author_username: Option<String>,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
pub struct WorkloadDiscussion {
|
||||
pub entity_type: String,
|
||||
pub entity_iid: i64,
|
||||
/// Canonical reference: `group/project!iid` or `group/project#iid`
|
||||
pub ref_: String,
|
||||
pub entity_title: String,
|
||||
pub project_path: String,
|
||||
pub last_note_at: i64,
|
||||
}
|
||||
|
||||
// --- Reviews ---
|
||||
|
||||
pub struct ReviewsResult {
|
||||
pub username: String,
|
||||
pub total_diffnotes: u32,
|
||||
pub categorized_count: u32,
|
||||
pub mrs_reviewed: u32,
|
||||
pub categories: Vec<ReviewCategory>,
|
||||
}
|
||||
|
||||
pub struct ReviewCategory {
|
||||
pub name: String,
|
||||
pub count: u32,
|
||||
pub percentage: f64,
|
||||
}
|
||||
|
||||
// --- Active ---
|
||||
|
||||
pub struct ActiveResult {
|
||||
pub discussions: Vec<ActiveDiscussion>,
|
||||
/// Count of unresolved discussions *within the time window*, not total across all time.
|
||||
pub total_unresolved_in_window: u32,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
pub struct ActiveDiscussion {
|
||||
pub discussion_id: i64,
|
||||
pub entity_type: String,
|
||||
pub entity_iid: i64,
|
||||
pub entity_title: String,
|
||||
pub project_path: String,
|
||||
pub last_note_at: i64,
|
||||
pub note_count: u32,
|
||||
pub participants: Vec<String>,
|
||||
pub participants_total: u32,
|
||||
pub participants_truncated: bool,
|
||||
}
|
||||
|
||||
// --- Overlap ---
|
||||
|
||||
pub struct OverlapResult {
|
||||
pub path_query: String,
|
||||
/// "exact" or "prefix" -- how the path was matched in SQL.
|
||||
pub path_match: String,
|
||||
pub users: Vec<OverlapUser>,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
pub struct OverlapUser {
|
||||
pub username: String,
|
||||
pub author_touch_count: u32,
|
||||
pub review_touch_count: u32,
|
||||
pub touch_count: u32,
|
||||
pub last_seen_at: i64,
|
||||
/// Stable MR references like "group/project!123"
|
||||
pub mr_refs: Vec<String>,
|
||||
pub mr_refs_total: u32,
|
||||
pub mr_refs_truncated: bool,
|
||||
}
|
||||
|
||||
/// Maximum MR references to retain per user in output (shared across modes).
|
||||
const MAX_MR_REFS_PER_USER: usize = 50;
|
||||
|
||||
@@ -483,7 +332,7 @@ fn resolve_since_required(input: &str) -> Result<i64> {
|
||||
///
|
||||
/// Returns `0.0` when `half_life_days` is zero (prevents division by zero).
|
||||
/// Negative elapsed values are clamped to zero (future events retain full weight).
|
||||
fn half_life_decay(elapsed_ms: i64, half_life_days: u32) -> f64 {
|
||||
pub fn half_life_decay(elapsed_ms: i64, half_life_days: u32) -> f64 {
|
||||
let days = (elapsed_ms as f64 / 86_400_000.0).max(0.0);
|
||||
let hl = f64::from(half_life_days);
|
||||
if hl <= 0.0 {
|
||||
@@ -495,7 +344,7 @@ fn half_life_decay(elapsed_ms: i64, half_life_days: u32) -> f64 {
|
||||
// ─── Query: Expert Mode ─────────────────────────────────────────────────────
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn query_expert(
|
||||
pub fn query_expert(
|
||||
conn: &Connection,
|
||||
path: &str,
|
||||
project_id: Option<i64>,
|
||||
@@ -1150,7 +999,7 @@ fn query_expert_details(
|
||||
|
||||
// ─── Query: Workload Mode ───────────────────────────────────────────────────
|
||||
|
||||
fn query_workload(
|
||||
pub fn query_workload(
|
||||
conn: &Connection,
|
||||
username: &str,
|
||||
project_id: Option<i64>,
|
||||
@@ -1336,7 +1185,7 @@ fn query_workload(
|
||||
|
||||
// ─── Query: Reviews Mode ────────────────────────────────────────────────────
|
||||
|
||||
fn query_reviews(
|
||||
pub fn query_reviews(
|
||||
conn: &Connection,
|
||||
username: &str,
|
||||
project_id: Option<i64>,
|
||||
@@ -1432,7 +1281,7 @@ fn query_reviews(
|
||||
})
|
||||
.collect();
|
||||
|
||||
categories.sort_by(|a, b| b.count.cmp(&a.count));
|
||||
categories.sort_by_key(|cat| std::cmp::Reverse(cat.count));
|
||||
|
||||
Ok(ReviewsResult {
|
||||
username: username.to_string(),
|
||||
@@ -1463,7 +1312,7 @@ fn normalize_review_prefix(raw: &str) -> String {
|
||||
|
||||
// ─── Query: Active Mode ─────────────────────────────────────────────────────
|
||||
|
||||
fn query_active(
|
||||
pub fn query_active(
|
||||
conn: &Connection,
|
||||
project_id: Option<i64>,
|
||||
since_ms: i64,
|
||||
@@ -1686,7 +1535,7 @@ fn query_active(
|
||||
|
||||
// ─── Query: Overlap Mode ────────────────────────────────────────────────────
|
||||
|
||||
fn query_overlap(
|
||||
pub fn query_overlap(
|
||||
conn: &Connection,
|
||||
path: &str,
|
||||
project_id: Option<i64>,
|
||||
|
||||
@@ -22,6 +22,7 @@ pub mod timeline_collect;
|
||||
pub mod timeline_expand;
|
||||
pub mod timeline_seed;
|
||||
pub mod trace;
|
||||
pub mod who_types;
|
||||
|
||||
pub use config::Config;
|
||||
pub use error::{LoreError, Result};
|
||||
|
||||
180
src/core/who_types.rs
Normal file
180
src/core/who_types.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
//! Shared types for the `who` people-intelligence queries.
|
||||
//!
|
||||
//! These types are the data contract consumed by both the CLI renderer
|
||||
//! and the TUI view layer. They contain no CLI or rendering logic.
|
||||
|
||||
// ─── Top-Level Result ────────────────────────────────────────────────────────
|
||||
|
||||
/// Top-level result enum -- one variant per mode.
|
||||
#[derive(Debug)]
|
||||
pub enum WhoResult {
|
||||
Expert(ExpertResult),
|
||||
Workload(WorkloadResult),
|
||||
Reviews(ReviewsResult),
|
||||
Active(ActiveResult),
|
||||
Overlap(OverlapResult),
|
||||
}
|
||||
|
||||
// ─── Expert ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExpertResult {
|
||||
pub path_query: String,
|
||||
/// "exact" or "prefix" -- how the path was matched in SQL.
|
||||
pub path_match: String,
|
||||
pub experts: Vec<Expert>,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expert {
|
||||
pub username: String,
|
||||
pub score: i64,
|
||||
/// Unrounded f64 score (only populated when explain_score is set).
|
||||
pub score_raw: Option<f64>,
|
||||
/// Per-component score breakdown (only populated when explain_score is set).
|
||||
pub components: Option<ScoreComponents>,
|
||||
pub review_mr_count: u32,
|
||||
pub review_note_count: u32,
|
||||
pub author_mr_count: u32,
|
||||
pub last_seen_ms: i64,
|
||||
/// Stable MR references like "group/project!123"
|
||||
pub mr_refs: Vec<String>,
|
||||
pub mr_refs_total: u32,
|
||||
pub mr_refs_truncated: bool,
|
||||
/// Per-MR detail breakdown (only populated when --detail is set)
|
||||
pub details: Option<Vec<ExpertMrDetail>>,
|
||||
}
|
||||
|
||||
/// Per-component score breakdown for explain mode.
|
||||
#[derive(Debug)]
|
||||
pub struct ScoreComponents {
|
||||
pub author: f64,
|
||||
pub reviewer_participated: f64,
|
||||
pub reviewer_assigned: f64,
|
||||
pub notes: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExpertMrDetail {
|
||||
pub mr_ref: String,
|
||||
pub title: String,
|
||||
/// "R", "A", or "A+R"
|
||||
pub role: String,
|
||||
pub note_count: u32,
|
||||
pub last_activity_ms: i64,
|
||||
}
|
||||
|
||||
// ─── Workload ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorkloadResult {
|
||||
pub username: String,
|
||||
pub assigned_issues: Vec<WorkloadIssue>,
|
||||
pub authored_mrs: Vec<WorkloadMr>,
|
||||
pub reviewing_mrs: Vec<WorkloadMr>,
|
||||
pub unresolved_discussions: Vec<WorkloadDiscussion>,
|
||||
pub assigned_issues_truncated: bool,
|
||||
pub authored_mrs_truncated: bool,
|
||||
pub reviewing_mrs_truncated: bool,
|
||||
pub unresolved_discussions_truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorkloadIssue {
|
||||
pub iid: i64,
|
||||
/// Canonical reference: `group/project#iid`
|
||||
pub ref_: String,
|
||||
pub title: String,
|
||||
pub project_path: String,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorkloadMr {
|
||||
pub iid: i64,
|
||||
/// Canonical reference: `group/project!iid`
|
||||
pub ref_: String,
|
||||
pub title: String,
|
||||
pub draft: bool,
|
||||
pub project_path: String,
|
||||
pub author_username: Option<String>,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorkloadDiscussion {
|
||||
pub entity_type: String,
|
||||
pub entity_iid: i64,
|
||||
/// Canonical reference: `group/project!iid` or `group/project#iid`
|
||||
pub ref_: String,
|
||||
pub entity_title: String,
|
||||
pub project_path: String,
|
||||
pub last_note_at: i64,
|
||||
}
|
||||
|
||||
// ─── Reviews ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReviewsResult {
|
||||
pub username: String,
|
||||
pub total_diffnotes: u32,
|
||||
pub categorized_count: u32,
|
||||
pub mrs_reviewed: u32,
|
||||
pub categories: Vec<ReviewCategory>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReviewCategory {
|
||||
pub name: String,
|
||||
pub count: u32,
|
||||
pub percentage: f64,
|
||||
}
|
||||
|
||||
// ─── Active ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveResult {
|
||||
pub discussions: Vec<ActiveDiscussion>,
|
||||
/// Count of unresolved discussions *within the time window*, not total across all time.
|
||||
pub total_unresolved_in_window: u32,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveDiscussion {
|
||||
pub discussion_id: i64,
|
||||
pub entity_type: String,
|
||||
pub entity_iid: i64,
|
||||
pub entity_title: String,
|
||||
pub project_path: String,
|
||||
pub last_note_at: i64,
|
||||
pub note_count: u32,
|
||||
pub participants: Vec<String>,
|
||||
pub participants_total: u32,
|
||||
pub participants_truncated: bool,
|
||||
}
|
||||
|
||||
// ─── Overlap ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OverlapResult {
|
||||
pub path_query: String,
|
||||
/// "exact" or "prefix" -- how the path was matched in SQL.
|
||||
pub path_match: String,
|
||||
pub users: Vec<OverlapUser>,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OverlapUser {
|
||||
pub username: String,
|
||||
pub author_touch_count: u32,
|
||||
pub review_touch_count: u32,
|
||||
pub touch_count: u32,
|
||||
pub last_seen_at: i64,
|
||||
/// Stable MR references like "group/project!123"
|
||||
pub mr_refs: Vec<String>,
|
||||
pub mr_refs_total: u32,
|
||||
pub mr_refs_truncated: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user