2026-04-13 12:38:20 +00:00
|
|
|
use crate::error::AppError;
|
|
|
|
|
use crate::models::credential::TuleapCredentials;
|
2026-04-15 15:17:23 +00:00
|
|
|
use crate::models::module::{ProjectModule, MODULE_TULEAP_AUTO_RESOLVE};
|
2026-04-16 15:44:40 +00:00
|
|
|
use crate::models::ticket::{ProcessedTicket, ProjectThroughputStats};
|
2026-04-13 12:38:20 +00:00
|
|
|
use crate::models::tracker::WatchedTracker;
|
|
|
|
|
use crate::services::tuleap_client::TuleapClient;
|
2026-04-15 15:17:23 +00:00
|
|
|
use crate::services::{crypto, filter_engine, notifier};
|
2026-04-13 12:38:20 +00:00
|
|
|
use crate::AppState;
|
2026-04-14 09:36:32 +00:00
|
|
|
use tauri::{Emitter, State};
|
2026-04-13 12:38:20 +00:00
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn manual_poll(
|
|
|
|
|
state: State<'_, AppState>,
|
2026-04-14 09:36:32 +00:00
|
|
|
app_handle: tauri::AppHandle,
|
2026-04-13 12:38:20 +00:00
|
|
|
tracker_id: String,
|
|
|
|
|
) -> Result<Vec<ProcessedTicket>, AppError> {
|
|
|
|
|
let (tracker, client) = {
|
|
|
|
|
let db = state
|
|
|
|
|
.db
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
|
|
|
|
|
|
let tracker = WatchedTracker::get_by_id(&db, &tracker_id)?;
|
2026-04-15 15:17:23 +00:00
|
|
|
let module_enabled =
|
|
|
|
|
ProjectModule::is_enabled(&db, &tracker.project_id, MODULE_TULEAP_AUTO_RESOLVE)?;
|
|
|
|
|
if !module_enabled {
|
|
|
|
|
return Err(AppError::from(
|
|
|
|
|
"Le module Polling Tuleap + auto-resolve est désactivé pour ce projet".to_string(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:23 +00:00
|
|
|
if tracker.status != "valid" {
|
|
|
|
|
return Err(AppError::from(
|
|
|
|
|
"Tracker is invalid. Reconfigure analyst/developer agents first.".to_string(),
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-04-13 12:38:20 +00:00
|
|
|
|
2026-04-16 15:58:48 +00:00
|
|
|
let cred = TuleapCredentials::get_for_project(&db, &tracker.project_id)?
|
2026-04-13 12:38:20 +00:00
|
|
|
.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 client = TuleapClient::new(
|
|
|
|
|
&state.http_client,
|
|
|
|
|
&cred.tuleap_url,
|
|
|
|
|
&cred.username,
|
|
|
|
|
&password,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
(tracker, client)
|
|
|
|
|
}; // lock dropped here
|
|
|
|
|
|
2026-04-14 09:36:32 +00:00
|
|
|
let _ = app_handle.emit(
|
|
|
|
|
"polling-started",
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"project_id": &tracker.project_id,
|
|
|
|
|
"tracker_id": &tracker.id,
|
|
|
|
|
"tracker_label": &tracker.tracker_label,
|
|
|
|
|
"source": "manual",
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let artifacts = match client.get_artifacts(tracker.tracker_id).await {
|
|
|
|
|
Ok(a) => a,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
let _ = app_handle.emit(
|
|
|
|
|
"polling-error",
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"project_id": &tracker.project_id,
|
|
|
|
|
"tracker_id": &tracker.id,
|
|
|
|
|
"tracker_label": &tracker.tracker_label,
|
|
|
|
|
"source": "manual",
|
|
|
|
|
"error": e,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
return Err(AppError::from(e));
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-13 12:38:20 +00:00
|
|
|
|
|
|
|
|
let filtered = filter_engine::apply_filters(&artifacts, &tracker.filters);
|
|
|
|
|
|
|
|
|
|
let mut newly_inserted: Vec<ProcessedTicket> = Vec::new();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let db = state
|
|
|
|
|
.db
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
|
|
|
|
|
|
for artifact in &filtered {
|
2026-04-15 15:17:23 +00:00
|
|
|
let artifact_id = artifact.get("id").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
|
2026-04-13 12:38:20 +00:00
|
|
|
|
|
|
|
|
let artifact_title = artifact
|
|
|
|
|
.get("title")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.unwrap_or("")
|
|
|
|
|
.to_string();
|
|
|
|
|
|
2026-04-15 15:17:23 +00:00
|
|
|
let artifact_data =
|
|
|
|
|
serde_json::to_string(artifact).unwrap_or_else(|_| "{}".to_string());
|
2026-04-13 12:38:20 +00:00
|
|
|
|
|
|
|
|
if let Some(ticket) = ProcessedTicket::insert_if_new(
|
|
|
|
|
&db,
|
2026-04-17 13:09:02 +00:00
|
|
|
&tracker.project_id,
|
2026-04-13 12:38:20 +00:00
|
|
|
&tracker.id,
|
|
|
|
|
artifact_id,
|
|
|
|
|
&artifact_title,
|
|
|
|
|
&artifact_data,
|
|
|
|
|
)? {
|
|
|
|
|
newly_inserted.push(ticket);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WatchedTracker::update_last_polled(&db, &tracker.id)?;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 09:36:32 +00:00
|
|
|
if !newly_inserted.is_empty() {
|
|
|
|
|
let _ = app_handle.emit(
|
|
|
|
|
"new-tickets-detected",
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"project_id": &tracker.project_id,
|
|
|
|
|
"tracker_id": &tracker.id,
|
|
|
|
|
"tracker_label": &tracker.tracker_label,
|
|
|
|
|
"count": newly_inserted.len(),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for ticket in &newly_inserted {
|
|
|
|
|
notifier::notify_new_ticket(
|
|
|
|
|
&state.db,
|
|
|
|
|
&app_handle,
|
|
|
|
|
&tracker.project_id,
|
|
|
|
|
&ticket.id,
|
|
|
|
|
ticket.artifact_id,
|
|
|
|
|
&ticket.artifact_title,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _ = app_handle.emit(
|
|
|
|
|
"polling-finished",
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"project_id": &tracker.project_id,
|
|
|
|
|
"tracker_id": &tracker.id,
|
|
|
|
|
"tracker_label": &tracker.tracker_label,
|
|
|
|
|
"source": "manual",
|
|
|
|
|
"new_tickets_count": newly_inserted.len(),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-13 12:38:20 +00:00
|
|
|
Ok(newly_inserted)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn get_queue_status(
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
project_id: String,
|
|
|
|
|
) -> Result<Vec<ProcessedTicket>, AppError> {
|
|
|
|
|
let db = state
|
|
|
|
|
.db
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
|
|
|
|
|
|
let tickets = ProcessedTicket::list_by_project(&db, &project_id)?;
|
|
|
|
|
Ok(tickets)
|
|
|
|
|
}
|
2026-04-16 15:44:40 +00:00
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn get_project_throughput(
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
project_id: String,
|
|
|
|
|
) -> Result<ProjectThroughputStats, AppError> {
|
|
|
|
|
let db = state
|
|
|
|
|
.db
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
|
|
|
|
|
|
let stats = ProcessedTicket::get_project_throughput_stats(&db, &project_id)?;
|
|
|
|
|
Ok(stats)
|
|
|
|
|
}
|