fix(backend): trim heavy payloads in ticket and task lists
This commit is contained in:
parent
aea8d0b73b
commit
7f7e066e7c
2 changed files with 70 additions and 4 deletions
|
|
@ -6,6 +6,30 @@ use crate::models::project::Project;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
|
const TASK_LIST_RESULT_PREVIEW_MAX_BYTES: usize = 20_000;
|
||||||
|
const TASK_LIST_ERROR_PREVIEW_MAX_BYTES: usize = 8_000;
|
||||||
|
const TASK_LIST_TRUNCATION_NOTICE: &str =
|
||||||
|
"\n\n[... contenu tronque pour preserver la fluidite de l'interface ...]";
|
||||||
|
|
||||||
|
fn truncate_for_task_list(value: Option<String>, max_bytes: usize) -> Option<String> {
|
||||||
|
let Some(content) = value else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if content.len() <= max_bytes {
|
||||||
|
return Some(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut boundary = max_bytes;
|
||||||
|
while boundary > 0 && !content.is_char_boundary(boundary) {
|
||||||
|
boundary -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut truncated = content[..boundary].to_string();
|
||||||
|
truncated.push_str(TASK_LIST_TRUNCATION_NOTICE);
|
||||||
|
Some(truncated)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn create_agent_task(
|
pub fn create_agent_task(
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
|
|
@ -55,7 +79,14 @@ pub fn list_agent_tasks(
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
||||||
|
|
||||||
let tasks = AgentTask::list_by_project(&db, &project_id)?;
|
let tasks = AgentTask::list_by_project(&db, &project_id)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut task| {
|
||||||
|
task.result = truncate_for_task_list(task.result, TASK_LIST_RESULT_PREVIEW_MAX_BYTES);
|
||||||
|
task.error = truncate_for_task_list(task.error, TASK_LIST_ERROR_PREVIEW_MAX_BYTES);
|
||||||
|
task
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,3 +153,31 @@ pub async fn cancel_agent_task(
|
||||||
AgentTask::cancel(&db, &task_id)?;
|
AgentTask::cancel(&db, &task_id)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::truncate_for_task_list;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate_for_task_list_keeps_short_content() {
|
||||||
|
let input = Some("short".to_string());
|
||||||
|
let output = truncate_for_task_list(input, 10);
|
||||||
|
assert_eq!(output.as_deref(), Some("short"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate_for_task_list_truncates_long_content() {
|
||||||
|
let input = Some("x".repeat(20));
|
||||||
|
let output = truncate_for_task_list(input, 10).expect("content should exist");
|
||||||
|
assert!(output.starts_with("xxxxxxxxxx"));
|
||||||
|
assert!(output.contains("contenu tronque"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate_for_task_list_respects_utf8_boundaries() {
|
||||||
|
let input = Some("éééé".to_string());
|
||||||
|
let output = truncate_for_task_list(input, 3).expect("content should exist");
|
||||||
|
assert!(output.starts_with("é"));
|
||||||
|
assert!(!output.starts_with("éé"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ const SELECT_ALL_COLS: &str = "SELECT id, tracker_id, project_id, source, source
|
||||||
artifact_id, artifact_title, artifact_data, status, analyst_report, developer_report, \
|
artifact_id, artifact_title, artifact_data, status, analyst_report, developer_report, \
|
||||||
worktree_path, branch_name, detected_at, processed_at FROM processed_tickets";
|
worktree_path, branch_name, detected_at, processed_at FROM processed_tickets";
|
||||||
const SELECT_SUMMARY_COLS: &str = "SELECT id, tracker_id, project_id, source, source_ref, \
|
const SELECT_SUMMARY_COLS: &str = "SELECT id, tracker_id, project_id, source, source_ref, \
|
||||||
artifact_id, artifact_title, '' AS artifact_data, status, analyst_report, developer_report, \
|
artifact_id, artifact_title, '' AS artifact_data, status, NULL AS analyst_report, NULL AS developer_report, \
|
||||||
worktree_path, branch_name, detected_at, processed_at FROM processed_tickets";
|
worktree_path, branch_name, detected_at, processed_at FROM processed_tickets";
|
||||||
|
|
||||||
impl ProcessedTicket {
|
impl ProcessedTicket {
|
||||||
|
|
@ -823,7 +823,7 @@ mod tests {
|
||||||
fn test_list_by_project_summary_omits_artifact_payload() {
|
fn test_list_by_project_summary_omits_artifact_payload() {
|
||||||
let (conn, project_id, tracker_id) = setup();
|
let (conn, project_id, tracker_id) = setup();
|
||||||
|
|
||||||
ProcessedTicket::insert_if_new(
|
let ticket = ProcessedTicket::insert_if_new(
|
||||||
&conn,
|
&conn,
|
||||||
&project_id,
|
&project_id,
|
||||||
&tracker_id,
|
&tracker_id,
|
||||||
|
|
@ -831,12 +831,19 @@ mod tests {
|
||||||
"Large payload ticket",
|
"Large payload ticket",
|
||||||
&"x".repeat(10_000),
|
&"x".repeat(10_000),
|
||||||
)
|
)
|
||||||
.expect("insert should succeed");
|
.expect("insert should succeed")
|
||||||
|
.expect("ticket should be inserted");
|
||||||
|
ProcessedTicket::set_analyst_report(&conn, &ticket.id, &"A".repeat(10_000))
|
||||||
|
.expect("analyst report should be stored");
|
||||||
|
ProcessedTicket::set_developer_report(&conn, &ticket.id, &"D".repeat(10_000))
|
||||||
|
.expect("developer report should be stored");
|
||||||
|
|
||||||
let tickets = ProcessedTicket::list_by_project_summary(&conn, &project_id)
|
let tickets = ProcessedTicket::list_by_project_summary(&conn, &project_id)
|
||||||
.expect("summary list should succeed");
|
.expect("summary list should succeed");
|
||||||
|
|
||||||
assert!(!tickets.is_empty());
|
assert!(!tickets.is_empty());
|
||||||
assert!(tickets.iter().all(|ticket| ticket.artifact_data.is_empty()));
|
assert!(tickets.iter().all(|ticket| ticket.artifact_data.is_empty()));
|
||||||
|
assert!(tickets.iter().all(|ticket| ticket.analyst_report.is_none()));
|
||||||
|
assert!(tickets.iter().all(|ticket| ticket.developer_report.is_none()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue