fix(clippy): reduce tracker insert/add argument count

This commit is contained in:
thibaud-lclr 2026-04-14 17:23:30 +02:00
parent 7fc7a674f0
commit 2d87ad5344
7 changed files with 278 additions and 165 deletions

View file

@ -2,10 +2,11 @@ use crate::error::AppError;
use crate::models::agent::{Agent, AgentRole};
use crate::models::credential::TuleapCredentials;
use crate::models::ticket::ProcessedTicket;
use crate::models::tracker::{FilterGroup, TrackerUpdate, WatchedTracker};
use crate::models::tracker::{FilterGroup, NewWatchedTracker, TrackerUpdate, WatchedTracker};
use crate::services::crypto;
use crate::services::tuleap_client::TuleapClient;
use crate::AppState;
use serde::Deserialize;
use tauri::State;
fn build_tuleap_client(state: &State<AppState>) -> Result<TuleapClient, AppError> {
@ -17,8 +18,8 @@ fn build_tuleap_client(state: &State<AppState>) -> Result<TuleapClient, AppError
let cred = TuleapCredentials::get(&db)?
.ok_or_else(|| AppError::from("No Tuleap credentials configured".to_string()))?;
let password = crypto::decrypt(&state.encryption_key, &cred.password_encrypted)
.map_err(AppError::from)?;
let password =
crypto::decrypt(&state.encryption_key, &cred.password_encrypted).map_err(AppError::from)?;
Ok(TuleapClient::new(
&state.http_client,
@ -28,7 +29,11 @@ fn build_tuleap_client(state: &State<AppState>) -> Result<TuleapClient, AppError
))
}
fn ensure_agent_role(db: &rusqlite::Connection, agent_id: &str, expected: AgentRole) -> Result<(), AppError> {
fn ensure_agent_role(
db: &rusqlite::Connection,
agent_id: &str,
expected: AgentRole,
) -> Result<(), AppError> {
let agent = Agent::get_by_id(db, agent_id)?;
if agent.role != expected {
return Err(AppError::from(format!(
@ -41,34 +46,42 @@ fn ensure_agent_role(db: &rusqlite::Connection, agent_id: &str, expected: AgentR
Ok(())
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddTrackerPayload {
pub project_id: String,
pub tracker_id: i32,
pub tracker_label: String,
pub polling_interval: i32,
pub analyst_agent_id: String,
pub developer_agent_id: String,
pub filters: Vec<FilterGroup>,
}
#[tauri::command]
pub fn add_tracker(
state: State<'_, AppState>,
project_id: String,
tracker_id: i32,
tracker_label: String,
polling_interval: i32,
analyst_agent_id: String,
developer_agent_id: String,
filters: Vec<FilterGroup>,
payload: AddTrackerPayload,
) -> Result<WatchedTracker, AppError> {
let db = state
.db
.lock()
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
ensure_agent_role(&db, &analyst_agent_id, AgentRole::Analyst)?;
ensure_agent_role(&db, &developer_agent_id, AgentRole::Developer)?;
ensure_agent_role(&db, &payload.analyst_agent_id, AgentRole::Analyst)?;
ensure_agent_role(&db, &payload.developer_agent_id, AgentRole::Developer)?;
let tracker = WatchedTracker::insert(
&db,
&project_id,
tracker_id,
&tracker_label,
polling_interval,
&analyst_agent_id,
&developer_agent_id,
filters,
NewWatchedTracker {
project_id: payload.project_id,
tracker_id: payload.tracker_id,
tracker_label: payload.tracker_label,
polling_interval: payload.polling_interval,
analyst_agent_id: payload.analyst_agent_id,
developer_agent_id: payload.developer_agent_id,
filters: payload.filters,
},
)?;
Ok(tracker)

View file

@ -247,7 +247,7 @@ mod tests {
use super::*;
use crate::db;
use crate::models::project::Project;
use crate::models::tracker::WatchedTracker;
use crate::models::tracker::{NewWatchedTracker, WatchedTracker};
fn setup() -> Connection {
db::init_in_memory().expect("db init should succeed")
@ -302,10 +302,9 @@ mod tests {
"new script",
)
.unwrap_err();
assert!(
err.to_string()
.contains("Default agents cannot change name, role, or tool")
);
assert!(err
.to_string()
.contains("Default agents cannot change name, role, or tool"));
Agent::update(
&conn,
@ -338,18 +337,20 @@ mod tests {
let analyst_default = Agent::get_default_by_role(&conn, AgentRole::Analyst).unwrap();
let developer_default = Agent::get_default_by_role(&conn, AgentRole::Developer).unwrap();
let analyst = Agent::insert(&conn, "Analyst", AgentRole::Analyst, AgentTool::Codex, "")
.unwrap();
let analyst =
Agent::insert(&conn, "Analyst", AgentRole::Analyst, AgentTool::Codex, "").unwrap();
let tracker = WatchedTracker::insert(
&conn,
&project.id,
100,
"Bugs",
10,
&analyst.id,
&developer_default.id,
vec![],
NewWatchedTracker {
project_id: project.id.clone(),
tracker_id: 100,
tracker_label: "Bugs".to_string(),
polling_interval: 10,
analyst_agent_id: analyst.id.clone(),
developer_agent_id: developer_default.id.clone(),
filters: vec![],
},
)
.unwrap();

View file

@ -60,7 +60,14 @@ impl ProcessedTicket {
"INSERT INTO processed_tickets \
(id, tracker_id, artifact_id, artifact_title, artifact_data, status, detected_at) \
VALUES (?1, ?2, ?3, ?4, ?5, 'Pending', ?6)",
params![id, tracker_id, artifact_id, artifact_title, artifact_data, now],
params![
id,
tracker_id,
artifact_id,
artifact_title,
artifact_data,
now
],
)?;
let ticket = ProcessedTicket {
@ -183,7 +190,7 @@ mod tests {
use crate::db;
use crate::models::agent::{Agent, AgentRole, AgentTool};
use crate::models::project::Project;
use crate::models::tracker::WatchedTracker;
use crate::models::tracker::{NewWatchedTracker, WatchedTracker};
fn setup() -> (Connection, String) {
let conn = db::init_in_memory().expect("db init should succeed");
@ -193,13 +200,15 @@ mod tests {
Agent::insert(&conn, "D", AgentRole::Developer, AgentTool::ClaudeCode, "").unwrap();
let tracker = WatchedTracker::insert(
&conn,
&project.id,
456,
"Bugs",
10,
&analyst.id,
&developer.id,
vec![],
NewWatchedTracker {
project_id: project.id.clone(),
tracker_id: 456,
tracker_label: "Bugs".to_string(),
polling_interval: 10,
analyst_agent_id: analyst.id.clone(),
developer_agent_id: developer.id.clone(),
filters: vec![],
},
)
.unwrap();
(conn, tracker.id)
@ -257,8 +266,8 @@ mod tests {
fn test_exists() {
let (conn, tracker_id) = setup();
let before = ProcessedTicket::exists(&conn, &tracker_id, 303)
.expect("exists check should succeed");
let before =
ProcessedTicket::exists(&conn, &tracker_id, 303).expect("exists check should succeed");
assert!(!before);
ProcessedTicket::insert_if_new(&conn, &tracker_id, 303, "Some ticket", "{}")
@ -285,10 +294,15 @@ mod tests {
fn test_get_by_id() {
let (conn, tracker_id) = setup();
let inserted =
ProcessedTicket::insert_if_new(&conn, &tracker_id, 404, "Not Found Bug", "{\"id\": 404}")
.expect("insert should succeed")
.expect("should be Some");
let inserted = ProcessedTicket::insert_if_new(
&conn,
&tracker_id,
404,
"Not Found Bug",
"{\"id\": 404}",
)
.expect("insert should succeed")
.expect("should be Some");
let found =
ProcessedTicket::get_by_id(&conn, &inserted.id).expect("get_by_id should succeed");

View file

@ -41,6 +41,17 @@ pub struct TrackerUpdate {
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewWatchedTracker {
pub project_id: String,
pub tracker_id: i32,
pub tracker_label: String,
pub polling_interval: i32,
pub analyst_agent_id: String,
pub developer_agent_id: String,
pub filters: Vec<FilterGroup>,
}
fn normalize_agent_id(agent_id: &str) -> Option<String> {
let trimmed = agent_id.trim();
if trimmed.is_empty() {
@ -50,7 +61,10 @@ fn normalize_agent_id(agent_id: &str) -> Option<String> {
}
}
fn compute_status(analyst_agent_id: &Option<String>, developer_agent_id: &Option<String>) -> String {
fn compute_status(
analyst_agent_id: &Option<String>,
developer_agent_id: &Option<String>,
) -> String {
if analyst_agent_id.is_some() && developer_agent_id.is_some() {
"valid".to_string()
} else {
@ -62,8 +76,9 @@ fn from_row(row: &rusqlite::Row) -> rusqlite::Result<WatchedTracker> {
let filters_json: String = row.get(7)?;
let enabled_int: i32 = row.get(8)?;
let filters: Vec<FilterGroup> = serde_json::from_str(&filters_json)
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(7, rusqlite::types::Type::Text, Box::new(e)))?;
let filters: Vec<FilterGroup> = serde_json::from_str(&filters_json).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(7, rusqlite::types::Type::Text, Box::new(e))
})?;
Ok(WatchedTracker {
id: row.get(0)?,
@ -82,49 +97,50 @@ fn from_row(row: &rusqlite::Row) -> rusqlite::Result<WatchedTracker> {
}
impl WatchedTracker {
pub fn insert(
conn: &Connection,
project_id: &str,
tracker_id: i32,
tracker_label: &str,
polling_interval: i32,
analyst_agent_id: &str,
developer_agent_id: &str,
filters: Vec<FilterGroup>,
) -> Result<WatchedTracker> {
pub fn insert(conn: &Connection, new_tracker: NewWatchedTracker) -> Result<WatchedTracker> {
let NewWatchedTracker {
project_id,
tracker_id,
tracker_label,
polling_interval,
analyst_agent_id,
developer_agent_id,
filters,
} = new_tracker;
let id = Uuid::new_v4().to_string();
let now = chrono::Utc::now().to_rfc3339();
let filters_json = serde_json::to_string(&filters)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
let analyst_agent_id = normalize_agent_id(analyst_agent_id);
let developer_agent_id = normalize_agent_id(developer_agent_id);
let analyst_agent_id = normalize_agent_id(&analyst_agent_id);
let developer_agent_id = normalize_agent_id(&developer_agent_id);
let status = compute_status(&analyst_agent_id, &developer_agent_id);
conn.execute(
"INSERT INTO watched_trackers (id, project_id, tracker_id, tracker_label, polling_interval, agent_config_json, filters_json, analyst_agent_id, developer_agent_id, status, created_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
params![
id,
project_id,
&id,
&project_id,
tracker_id,
tracker_label,
&tracker_label,
polling_interval,
"{}",
filters_json,
analyst_agent_id,
developer_agent_id,
status,
now,
analyst_agent_id.as_deref(),
developer_agent_id.as_deref(),
&status,
&now,
],
)?;
Ok(WatchedTracker {
id,
project_id: project_id.to_string(),
project_id,
tracker_id,
tracker_label: tracker_label.to_string(),
tracker_label,
polling_interval,
analyst_agent_id,
developer_agent_id,
@ -232,14 +248,8 @@ mod tests {
}
fn create_agents(conn: &Connection) -> (String, String) {
let analyst = Agent::insert(
conn,
"Analyst",
AgentRole::Analyst,
AgentTool::Codex,
"",
)
.unwrap();
let analyst =
Agent::insert(conn, "Analyst", AgentRole::Analyst, AgentTool::Codex, "").unwrap();
let developer = Agent::insert(
conn,
@ -271,13 +281,15 @@ mod tests {
let tracker = WatchedTracker::insert(
&conn,
&pid,
42,
"Bug Tracker",
15,
&analyst_id,
&developer_id,
sample_filters(),
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 42,
tracker_label: "Bug Tracker".to_string(),
polling_interval: 15,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: sample_filters(),
},
)
.expect("insert should succeed");
@ -288,8 +300,14 @@ mod tests {
assert_eq!(tracker.polling_interval, 15);
assert!(tracker.enabled);
assert_eq!(tracker.status, "valid");
assert_eq!(tracker.analyst_agent_id.as_deref(), Some(analyst_id.as_str()));
assert_eq!(tracker.developer_agent_id.as_deref(), Some(developer_id.as_str()));
assert_eq!(
tracker.analyst_agent_id.as_deref(),
Some(analyst_id.as_str())
);
assert_eq!(
tracker.developer_agent_id.as_deref(),
Some(developer_id.as_str())
);
assert!(tracker.last_polled_at.is_none());
assert!(!tracker.created_at.is_empty());
assert_eq!(tracker.filters.len(), 1);
@ -301,8 +319,32 @@ mod tests {
let pid = project_id(&conn);
let (analyst_id, developer_id) = create_agents(&conn);
WatchedTracker::insert(&conn, &pid, 1, "Tracker A", 10, &analyst_id, &developer_id, vec![]).unwrap();
WatchedTracker::insert(&conn, &pid, 2, "Tracker B", 20, &analyst_id, &developer_id, vec![]).unwrap();
WatchedTracker::insert(
&conn,
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 1,
tracker_label: "Tracker A".to_string(),
polling_interval: 10,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: vec![],
},
)
.unwrap();
WatchedTracker::insert(
&conn,
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 2,
tracker_label: "Tracker B".to_string(),
polling_interval: 20,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: vec![],
},
)
.unwrap();
let trackers = WatchedTracker::list_by_project(&conn, &pid).expect("list should succeed");
assert_eq!(trackers.len(), 2);
@ -314,10 +356,35 @@ mod tests {
let pid = project_id(&conn);
let (analyst_id, developer_id) = create_agents(&conn);
let valid = WatchedTracker::insert(&conn, &pid, 1, "Valid", 10, &analyst_id, &developer_id, vec![]).unwrap();
WatchedTracker::insert(&conn, &pid, 2, "Invalid", 10, "", &developer_id, vec![]).unwrap();
let valid = WatchedTracker::insert(
&conn,
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 1,
tracker_label: "Valid".to_string(),
polling_interval: 10,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: vec![],
},
)
.unwrap();
WatchedTracker::insert(
&conn,
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 2,
tracker_label: "Invalid".to_string(),
polling_interval: 10,
analyst_agent_id: "".to_string(),
developer_agent_id: developer_id.clone(),
filters: vec![],
},
)
.unwrap();
let enabled = WatchedTracker::list_all_enabled(&conn).expect("list_all_enabled should succeed");
let enabled =
WatchedTracker::list_all_enabled(&conn).expect("list_all_enabled should succeed");
assert_eq!(enabled.len(), 1);
assert_eq!(enabled[0].id, valid.id);
}
@ -330,17 +397,20 @@ mod tests {
let created = WatchedTracker::insert(
&conn,
&pid,
99,
"My Tracker",
30,
&analyst_id,
&developer_id,
sample_filters(),
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 99,
tracker_label: "My Tracker".to_string(),
polling_interval: 30,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: sample_filters(),
},
)
.unwrap();
let found = WatchedTracker::get_by_id(&conn, &created.id).expect("get_by_id should succeed");
let found =
WatchedTracker::get_by_id(&conn, &created.id).expect("get_by_id should succeed");
assert_eq!(found.id, created.id);
assert_eq!(found.tracker_id, 99);
assert_eq!(found.tracker_label, "My Tracker");
@ -356,13 +426,15 @@ mod tests {
let created = WatchedTracker::insert(
&conn,
&pid,
10,
"Original",
5,
&analyst_id,
&developer_id,
sample_filters(),
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 10,
tracker_label: "Original".to_string(),
polling_interval: 5,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: sample_filters(),
},
)
.unwrap();
@ -406,19 +478,22 @@ mod tests {
let created = WatchedTracker::insert(
&conn,
&pid,
5,
"Poller",
10,
&analyst_id,
&developer_id,
vec![],
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 5,
tracker_label: "Poller".to_string(),
polling_interval: 10,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: vec![],
},
)
.unwrap();
assert!(created.last_polled_at.is_none());
WatchedTracker::update_last_polled(&conn, &created.id).expect("update_last_polled should succeed");
WatchedTracker::update_last_polled(&conn, &created.id)
.expect("update_last_polled should succeed");
let updated = WatchedTracker::get_by_id(&conn, &created.id).unwrap();
assert!(updated.last_polled_at.is_some());
@ -432,13 +507,15 @@ mod tests {
let created = WatchedTracker::insert(
&conn,
&pid,
7,
"ToDelete",
10,
&analyst_id,
&developer_id,
vec![],
NewWatchedTracker {
project_id: pid.clone(),
tracker_id: 7,
tracker_label: "ToDelete".to_string(),
polling_interval: 10,
analyst_agent_id: analyst_id.clone(),
developer_agent_id: developer_id.clone(),
filters: vec![],
},
)
.unwrap();

View file

@ -31,7 +31,12 @@ const SELECT_ALL_COLS: &str = "SELECT id, ticket_id, path, branch_name, status,
created_at, merged_at, merged_into FROM worktrees";
impl Worktree {
pub fn insert(conn: &Connection, ticket_id: &str, path: &str, branch_name: &str) -> Result<Worktree> {
pub fn insert(
conn: &Connection,
ticket_id: &str,
path: &str,
branch_name: &str,
) -> Result<Worktree> {
let id = Uuid::new_v4().to_string();
let now = chrono::Utc::now().to_rfc3339();
@ -103,7 +108,7 @@ mod tests {
use crate::models::agent::{Agent, AgentRole, AgentTool};
use crate::models::project::Project;
use crate::models::ticket::ProcessedTicket;
use crate::models::tracker::WatchedTracker;
use crate::models::tracker::{NewWatchedTracker, WatchedTracker};
fn setup() -> (Connection, String) {
let conn = db::init_in_memory().expect("db init");
@ -113,13 +118,15 @@ mod tests {
Agent::insert(&conn, "D", AgentRole::Developer, AgentTool::ClaudeCode, "").unwrap();
let tracker = WatchedTracker::insert(
&conn,
&project.id,
100,
"Bugs",
10,
&analyst.id,
&developer.id,
vec![],
NewWatchedTracker {
project_id: project.id.clone(),
tracker_id: 100,
tracker_label: "Bugs".to_string(),
polling_interval: 10,
analyst_agent_id: analyst.id.clone(),
developer_agent_id: developer.id.clone(),
filters: vec![],
},
)
.unwrap();
let ticket = ProcessedTicket::insert_if_new(&conn, &tracker.id, 42, "Bug 42", "{}")
@ -165,13 +172,15 @@ mod tests {
Agent::insert(&conn, "D", AgentRole::Developer, AgentTool::ClaudeCode, "").unwrap();
let tracker = WatchedTracker::insert(
&conn,
&project.id,
100,
"Bugs",
10,
&analyst.id,
&developer.id,
vec![],
NewWatchedTracker {
project_id: project.id.clone(),
tracker_id: 100,
tracker_label: "Bugs".to_string(),
polling_interval: 10,
analyst_agent_id: analyst.id.clone(),
developer_agent_id: developer.id.clone(),
filters: vec![],
},
)
.unwrap();
let t1 = ProcessedTicket::insert_if_new(&conn, &tracker.id, 1, "T1", "{}")

View file

@ -142,7 +142,7 @@ mod tests {
use crate::models::agent::{Agent, AgentRole, AgentTool};
use crate::models::project::Project;
use crate::models::ticket::ProcessedTicket;
use crate::models::tracker::WatchedTracker;
use crate::models::tracker::{NewWatchedTracker, WatchedTracker};
fn setup() -> (Arc<Mutex<Connection>>, String) {
let conn = db::init_in_memory().expect("db init should succeed");
@ -159,25 +159,22 @@ mod tests {
let tracker = WatchedTracker::insert(
&conn,
project_id,
101,
"Bugs",
10,
&analyst.id,
&developer.id,
vec![],
NewWatchedTracker {
project_id: project_id.to_string(),
tracker_id: 101,
tracker_label: "Bugs".to_string(),
polling_interval: 10,
analyst_agent_id: analyst.id.clone(),
developer_agent_id: developer.id.clone(),
filters: vec![],
},
)
.expect("tracker insert should succeed");
let ticket = ProcessedTicket::insert_if_new(
&conn,
&tracker.id,
1,
"Ticket 1",
"{\"id\":1}",
)
.expect("ticket insert should succeed")
.expect("ticket should be inserted");
let ticket =
ProcessedTicket::insert_if_new(&conn, &tracker.id, 1, "Ticket 1", "{\"id\":1}")
.expect("ticket insert should succeed")
.expect("ticket should be inserted");
ticket.id
}

View file

@ -99,13 +99,15 @@ export async function addTracker(
filters: FilterGroup[]
): Promise<WatchedTracker> {
return invoke("add_tracker", {
projectId,
trackerId,
trackerLabel,
pollingInterval,
analystAgentId,
developerAgentId,
filters,
payload: {
projectId,
trackerId,
trackerLabel,
pollingInterval,
analystAgentId,
developerAgentId,
filters,
},
});
}
export async function listTrackers(projectId: string): Promise<WatchedTracker[]> {