style: apply cargo fmt formatting

This commit is contained in:
Shaun Arman 2026-03-15 12:43:46 -05:00
parent 1011fc1db4
commit c8a717adee
16 changed files with 159 additions and 96 deletions

View File

@ -106,10 +106,7 @@ impl Provider for AnthropicProvider {
}) })
}); });
let model = json["model"] let model = json["model"].as_str().unwrap_or(&config.model).to_string();
.as_str()
.unwrap_or(&config.model)
.to_string();
Ok(ChatResponse { Ok(ChatResponse {
content, content,

View File

@ -30,10 +30,7 @@ impl Provider for OpenAiProvider {
config: &ProviderConfig, config: &ProviderConfig,
) -> anyhow::Result<ChatResponse> { ) -> anyhow::Result<ChatResponse> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let url = format!( let url = format!("{}/chat/completions", config.api_url.trim_end_matches('/'));
"{}/chat/completions",
config.api_url.trim_end_matches('/')
);
let body = serde_json::json!({ let body = serde_json::json!({
"model": config.model, "model": config.model,

View File

@ -54,8 +54,14 @@ mod tests {
#[test] #[test]
fn test_write_audit_event_inserts_row() { fn test_write_audit_event_inserts_row() {
let conn = setup_test_db(); let conn = setup_test_db();
write_audit_event(&conn, "test_action", "issue", "issue-123", r#"{"key":"val"}"#) write_audit_event(
.expect("should insert"); &conn,
"test_action",
"issue",
"issue-123",
r#"{"key":"val"}"#,
)
.expect("should insert");
let count: i64 = conn let count: i64 = conn
.prepare("SELECT COUNT(*) FROM audit_log") .prepare("SELECT COUNT(*) FROM audit_log")

View File

@ -48,10 +48,7 @@ pub async fn analyze_logs(
}, },
Message { Message {
role: "user".into(), role: "user".into(),
content: format!( content: format!("Analyze logs for issue {}:\n\n{}", issue_id, log_contents),
"Analyze logs for issue {}:\n\n{}",
issue_id, log_contents
),
}, },
]; ];
@ -61,8 +58,13 @@ pub async fn analyze_logs(
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let content = &response.content; let content = &response.content;
let summary = extract_section(content, "SUMMARY:") let summary = extract_section(content, "SUMMARY:").unwrap_or_else(|| {
.unwrap_or_else(|| content.lines().next().unwrap_or("Analysis complete").to_string()); content
.lines()
.next()
.unwrap_or("Analysis complete")
.to_string()
});
let key_findings = extract_list(content, "KEY_FINDINGS:"); let key_findings = extract_list(content, "KEY_FINDINGS:");
let suggested_why1 = extract_section(content, "FIRST_WHY:") let suggested_why1 = extract_section(content, "FIRST_WHY:")
.unwrap_or_else(|| "Why did this issue occur?".to_string()); .unwrap_or_else(|| "Why did this issue occur?".to_string());
@ -76,7 +78,8 @@ pub async fn analyze_logs(
"ai_analyze_logs".to_string(), "ai_analyze_logs".to_string(),
"issue".to_string(), "issue".to_string(),
issue_id.clone(), issue_id.clone(),
serde_json::json!({ "log_file_ids": log_file_ids, "provider": provider_config.name }).to_string(), serde_json::json!({ "log_file_ids": log_file_ids, "provider": provider_config.name })
.to_string(),
); );
db.execute( db.execute(
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \ "INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
@ -275,10 +278,7 @@ pub async fn list_providers() -> Result<Vec<ProviderInfo>, String> {
ProviderInfo { ProviderInfo {
name: "gemini".to_string(), name: "gemini".to_string(),
supports_streaming: false, supports_streaming: false,
models: vec![ models: vec!["gemini-1.5-pro".to_string(), "gemini-1.5-flash".to_string()],
"gemini-1.5-pro".to_string(),
"gemini-1.5-flash".to_string(),
],
}, },
ProviderInfo { ProviderInfo {
name: "mistral".to_string(), name: "mistral".to_string(),

View File

@ -64,8 +64,13 @@ pub async fn upload_log_file(
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \ "INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
rusqlite::params![ rusqlite::params![
entry.id, entry.timestamp, entry.action, entry.id,
entry.entity_type, entry.entity_id, entry.user_id, entry.details entry.timestamp,
entry.action,
entry.entity_type,
entry.entity_id,
entry.user_id,
entry.details
], ],
); );
@ -163,14 +168,16 @@ pub async fn apply_redactions(
.unwrap_or_default(); .unwrap_or_default();
drop(db); drop(db);
raw.into_iter() raw.into_iter()
.map(|(id, pii_type, start, end, original, replacement)| pii::PiiSpan { .map(
id, |(id, pii_type, start, end, original, replacement)| pii::PiiSpan {
pii_type, id,
start: start as usize, pii_type,
end: end as usize, start: start as usize,
original, end: end as usize,
replacement, original,
}) replacement,
},
)
.filter(|span| approved_span_ids.contains(&span.id)) .filter(|span| approved_span_ids.contains(&span.id))
.collect() .collect()
}; };

View File

@ -247,23 +247,29 @@ pub async fn update_issue(
} }
#[tauri::command] #[tauri::command]
pub async fn delete_issue( pub async fn delete_issue(issue_id: String, state: State<'_, AppState>) -> Result<(), String> {
issue_id: String,
state: State<'_, AppState>,
) -> Result<(), String> {
let db = state.db.lock().map_err(|e| e.to_string())?; let db = state.db.lock().map_err(|e| e.to_string())?;
// Delete related records (CASCADE should handle this, but be explicit) // Delete related records (CASCADE should handle this, but be explicit)
db.execute("DELETE FROM ai_messages WHERE conversation_id IN (SELECT id FROM ai_conversations WHERE issue_id = ?1)", [&issue_id]) db.execute("DELETE FROM ai_messages WHERE conversation_id IN (SELECT id FROM ai_conversations WHERE issue_id = ?1)", [&issue_id])
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
db.execute("DELETE FROM ai_conversations WHERE issue_id = ?1", [&issue_id]) db.execute(
.map_err(|e| e.to_string())?; "DELETE FROM ai_conversations WHERE issue_id = ?1",
db.execute("DELETE FROM pii_spans WHERE log_file_id IN (SELECT id FROM log_files WHERE issue_id = ?1)", [&issue_id]) [&issue_id],
.map_err(|e| e.to_string())?; )
.map_err(|e| e.to_string())?;
db.execute(
"DELETE FROM pii_spans WHERE log_file_id IN (SELECT id FROM log_files WHERE issue_id = ?1)",
[&issue_id],
)
.map_err(|e| e.to_string())?;
db.execute("DELETE FROM log_files WHERE issue_id = ?1", [&issue_id]) db.execute("DELETE FROM log_files WHERE issue_id = ?1", [&issue_id])
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
db.execute("DELETE FROM resolution_steps WHERE issue_id = ?1", [&issue_id]) db.execute(
.map_err(|e| e.to_string())?; "DELETE FROM resolution_steps WHERE issue_id = ?1",
[&issue_id],
)
.map_err(|e| e.to_string())?;
db.execute("DELETE FROM issues WHERE id = ?1", [&issue_id]) db.execute("DELETE FROM issues WHERE id = ?1", [&issue_id])
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
@ -364,13 +370,7 @@ pub async fn add_five_why(
evidence: String, evidence: String,
state: State<'_, AppState>, state: State<'_, AppState>,
) -> Result<ResolutionStep, String> { ) -> Result<ResolutionStep, String> {
let step = ResolutionStep::new( let step = ResolutionStep::new(issue_id.clone(), step_order, why_question, answer, evidence);
issue_id.clone(),
step_order,
why_question,
answer,
evidence,
);
let db = state.db.lock().map_err(|e| e.to_string())?; let db = state.db.lock().map_err(|e| e.to_string())?;
db.execute( db.execute(
@ -444,8 +444,13 @@ pub async fn add_timeline_event(
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \ "INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
rusqlite::params![ rusqlite::params![
entry.id, entry.timestamp, entry.action, entry.id,
entry.entity_type, entry.entity_id, entry.user_id, entry.details entry.timestamp,
entry.action,
entry.entity_type,
entry.entity_id,
entry.user_id,
entry.details
], ],
) )
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;

View File

@ -52,8 +52,13 @@ pub async fn generate_rca(
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \ "INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
rusqlite::params![ rusqlite::params![
entry.id, entry.timestamp, entry.action, entry.id,
entry.entity_type, entry.entity_id, entry.user_id, entry.details entry.timestamp,
entry.action,
entry.entity_type,
entry.entity_id,
entry.user_id,
entry.details
], ],
); );
@ -94,8 +99,13 @@ pub async fn generate_postmortem(
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \ "INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
rusqlite::params![ rusqlite::params![
entry.id, entry.timestamp, entry.action, entry.id,
entry.entity_type, entry.entity_id, entry.user_id, entry.details entry.timestamp,
entry.action,
entry.entity_type,
entry.entity_id,
entry.user_id,
entry.details
], ],
); );
@ -103,10 +113,7 @@ pub async fn generate_postmortem(
} }
#[tauri::command] #[tauri::command]
pub async fn update_document( pub async fn update_document(doc_id: String, content_md: String) -> Result<(), String> {
doc_id: String,
content_md: String,
) -> Result<(), String> {
// Documents are generated on-demand and held in memory / frontend state. // Documents are generated on-demand and held in memory / frontend state.
// This is a no-op placeholder. In a future version with a documents table, // This is a no-op placeholder. In a future version with a documents table,
// this would persist updates. // this would persist updates.

View File

@ -9,9 +9,7 @@ use crate::state::{AppSettings, AppState};
#[tauri::command] #[tauri::command]
pub async fn check_ollama_installed() -> Result<OllamaStatus, String> { pub async fn check_ollama_installed() -> Result<OllamaStatus, String> {
installer::check_ollama() installer::check_ollama().await.map_err(|e| e.to_string())
.await
.map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]
@ -55,9 +53,7 @@ pub async fn recommend_models() -> Result<Vec<ModelRecommendation>, String> {
// --- Settings commands --- // --- Settings commands ---
#[tauri::command] #[tauri::command]
pub async fn get_settings( pub async fn get_settings(state: tauri::State<'_, AppState>) -> Result<AppSettings, String> {
state: tauri::State<'_, AppState>,
) -> Result<AppSettings, String> {
state state
.settings .settings
.lock() .lock()

View File

@ -35,8 +35,7 @@ pub fn export_pdf(content_md: &str, title: &str, output_path: &str) -> anyhow::R
for line_info in &lines { for line_info in &lines {
if line_count >= max_lines_per_page { if line_count >= max_lines_per_page {
let (new_page, new_layer) = let (new_page, new_layer) = doc.add_page(Mm(210.0), Mm(297.0), "Layer 1");
doc.add_page(Mm(210.0), Mm(297.0), "Layer 1");
current_layer = doc.get_page(new_page).get_layer(new_layer); current_layer = doc.get_page(new_page).get_layer(new_layer);
y_pos = margin_top; y_pos = margin_top;
line_count = 0; line_count = 0;
@ -185,13 +184,18 @@ mod tests {
#[test] #[test]
fn test_markdown_to_lines_title() { fn test_markdown_to_lines_title() {
let lines = markdown_to_lines("# My Title\n\nSome text"); let lines = markdown_to_lines("# My Title\n\nSome text");
assert!(lines.iter().any(|l| l.text == "My Title" && matches!(l.style, LineStyle::Title))); assert!(lines
.iter()
.any(|l| l.text == "My Title" && matches!(l.style, LineStyle::Title)));
} }
#[test] #[test]
fn test_markdown_to_lines_heading() { fn test_markdown_to_lines_heading() {
let lines = markdown_to_lines("## Section\n### Subsection"); let lines = markdown_to_lines("## Section\n### Subsection");
let headings: Vec<_> = lines.iter().filter(|l| matches!(l.style, LineStyle::Heading)).collect(); let headings: Vec<_> = lines
.iter()
.filter(|l| matches!(l.style, LineStyle::Heading))
.collect();
assert_eq!(headings.len(), 2); assert_eq!(headings.len(), 2);
} }
@ -204,7 +208,9 @@ mod tests {
#[test] #[test]
fn test_markdown_to_lines_table_row() { fn test_markdown_to_lines_table_row() {
let lines = markdown_to_lines("| Col1 | Col2 |\n|------|------|\n| A | B |"); let lines = markdown_to_lines("| Col1 | Col2 |\n|------|------|\n| A | B |");
assert!(lines.iter().any(|l| l.text.contains("Col1") && l.text.contains("Col2"))); assert!(lines
.iter()
.any(|l| l.text.contains("Col1") && l.text.contains("Col2")));
} }
#[test] #[test]

View File

@ -5,10 +5,7 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
let mut md = String::new(); let mut md = String::new();
md.push_str(&format!( md.push_str(&format!("# Blameless Post-Mortem: {}\n\n", issue.title));
"# Blameless Post-Mortem: {}\n\n",
issue.title
));
// Header metadata // Header metadata
md.push_str("## Metadata\n\n"); md.push_str("## Metadata\n\n");
@ -19,7 +16,11 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
md.push_str(&format!("- **Last Updated:** {}\n", issue.updated_at)); md.push_str(&format!("- **Last Updated:** {}\n", issue.updated_at));
md.push_str(&format!( md.push_str(&format!(
"- **Assigned To:** {}\n", "- **Assigned To:** {}\n",
if issue.assigned_to.is_empty() { "_Unassigned_" } else { &issue.assigned_to } if issue.assigned_to.is_empty() {
"_Unassigned_"
} else {
&issue.assigned_to
}
)); ));
md.push_str("- **Authors:** _[Add authors]_\n"); md.push_str("- **Authors:** _[Add authors]_\n");
md.push_str("- **Reviewers:** _[Add reviewers]_\n\n"); md.push_str("- **Reviewers:** _[Add reviewers]_\n\n");

View File

@ -15,7 +15,14 @@ pub fn generate_rca_markdown(detail: &IssueDetail) -> String {
md.push_str(&format!("| **Status** | {} |\n", issue.status)); md.push_str(&format!("| **Status** | {} |\n", issue.status));
md.push_str(&format!("| **Severity** | {} |\n", issue.severity)); md.push_str(&format!("| **Severity** | {} |\n", issue.severity));
md.push_str(&format!("| **Source** | {} |\n", issue.source)); md.push_str(&format!("| **Source** | {} |\n", issue.source));
md.push_str(&format!("| **Assigned To** | {} |\n", if issue.assigned_to.is_empty() { "Unassigned" } else { &issue.assigned_to })); md.push_str(&format!(
"| **Assigned To** | {} |\n",
if issue.assigned_to.is_empty() {
"Unassigned"
} else {
&issue.assigned_to
}
));
md.push_str(&format!("| **Created** | {} |\n", issue.created_at)); md.push_str(&format!("| **Created** | {} |\n", issue.created_at));
md.push_str(&format!("| **Last Updated** | {} |\n", issue.updated_at)); md.push_str(&format!("| **Last Updated** | {} |\n", issue.updated_at));
if let Some(ref resolved) = issue.resolved_at { if let Some(ref resolved) = issue.resolved_at {

View File

@ -19,7 +19,10 @@ pub struct WorkItem {
} }
pub async fn test_connection(_config: &AzureDevOpsConfig) -> Result<ConnectionResult, String> { pub async fn test_connection(_config: &AzureDevOpsConfig) -> Result<ConnectionResult, String> {
Err("Azure DevOps integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Azure DevOps integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn create_work_item( pub async fn create_work_item(
@ -29,14 +32,20 @@ pub async fn create_work_item(
_work_item_type: &str, _work_item_type: &str,
_severity: &str, _severity: &str,
) -> Result<TicketResult, String> { ) -> Result<TicketResult, String> {
Err("Azure DevOps integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Azure DevOps integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn get_work_item( pub async fn get_work_item(
_config: &AzureDevOpsConfig, _config: &AzureDevOpsConfig,
_work_item_id: i64, _work_item_id: i64,
) -> Result<WorkItem, String> { ) -> Result<WorkItem, String> {
Err("Azure DevOps integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Azure DevOps integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn update_work_item( pub async fn update_work_item(
@ -44,5 +53,8 @@ pub async fn update_work_item(
_work_item_id: i64, _work_item_id: i64,
_updates: serde_json::Value, _updates: serde_json::Value,
) -> Result<TicketResult, String> { ) -> Result<TicketResult, String> {
Err("Azure DevOps integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Azure DevOps integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }

View File

@ -24,11 +24,17 @@ pub struct Page {
} }
pub async fn test_connection(_config: &ConfluenceConfig) -> Result<ConnectionResult, String> { pub async fn test_connection(_config: &ConfluenceConfig) -> Result<ConnectionResult, String> {
Err("Confluence integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Confluence integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn list_spaces(_config: &ConfluenceConfig) -> Result<Vec<Space>, String> { pub async fn list_spaces(_config: &ConfluenceConfig) -> Result<Vec<Space>, String> {
Err("Confluence integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Confluence integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn publish_page( pub async fn publish_page(
@ -38,7 +44,10 @@ pub async fn publish_page(
_content_html: &str, _content_html: &str,
_parent_page_id: Option<&str>, _parent_page_id: Option<&str>,
) -> Result<PublishResult, String> { ) -> Result<PublishResult, String> {
Err("Confluence integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Confluence integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn update_page( pub async fn update_page(
@ -48,5 +57,8 @@ pub async fn update_page(
_content_html: &str, _content_html: &str,
_version: i32, _version: i32,
) -> Result<PublishResult, String> { ) -> Result<PublishResult, String> {
Err("Confluence integration available in v0.2. Please update to the latest version.".to_string()) Err(
"Confluence integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }

View File

@ -20,7 +20,10 @@ pub struct Incident {
} }
pub async fn test_connection(_config: &ServiceNowConfig) -> Result<ConnectionResult, String> { pub async fn test_connection(_config: &ServiceNowConfig) -> Result<ConnectionResult, String> {
Err("ServiceNow integration available in v0.2. Please update to the latest version.".to_string()) Err(
"ServiceNow integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn create_incident( pub async fn create_incident(
@ -30,14 +33,20 @@ pub async fn create_incident(
_urgency: &str, _urgency: &str,
_impact: &str, _impact: &str,
) -> Result<TicketResult, String> { ) -> Result<TicketResult, String> {
Err("ServiceNow integration available in v0.2. Please update to the latest version.".to_string()) Err(
"ServiceNow integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn get_incident( pub async fn get_incident(
_config: &ServiceNowConfig, _config: &ServiceNowConfig,
_incident_number: &str, _incident_number: &str,
) -> Result<Incident, String> { ) -> Result<Incident, String> {
Err("ServiceNow integration available in v0.2. Please update to the latest version.".to_string()) Err(
"ServiceNow integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }
pub async fn update_incident( pub async fn update_incident(
@ -45,5 +54,8 @@ pub async fn update_incident(
_incident_number: &str, _incident_number: &str,
_updates: serde_json::Value, _updates: serde_json::Value,
) -> Result<TicketResult, String> { ) -> Result<TicketResult, String> {
Err("ServiceNow integration available in v0.2. Please update to the latest version.".to_string()) Err(
"ServiceNow integration available in v0.2. Please update to the latest version."
.to_string(),
)
} }

View File

@ -8,13 +8,9 @@ pub async fn check_ollama() -> anyhow::Result<OllamaStatus> {
"which" "which"
}; };
let which_result = std::process::Command::new(which_cmd) let which_result = std::process::Command::new(which_cmd).arg("ollama").output();
.arg("ollama")
.output();
let installed = which_result let installed = which_result.map(|o| o.status.success()).unwrap_or(false);
.map(|o| o.status.success())
.unwrap_or(false);
let version = if installed { let version = if installed {
std::process::Command::new("ollama") std::process::Command::new("ollama")
@ -104,7 +100,10 @@ mod tests {
fn test_macos_install_guide() { fn test_macos_install_guide() {
let guide = get_install_instructions("macos"); let guide = get_install_instructions("macos");
assert_eq!(guide.platform, "macOS"); assert_eq!(guide.platform, "macOS");
assert!(guide.steps.iter().any(|s| s.contains("dmg") || s.contains("Applications"))); assert!(guide
.steps
.iter()
.any(|s| s.contains("dmg") || s.contains("Applications")));
} }
#[test] #[test]

View File

@ -46,8 +46,7 @@ pub fn recommend_models(hw: &HardwareInfo) -> Vec<ModelRecommendation> {
size: "40 GB".to_string(), size: "40 GB".to_string(),
min_ram_gb: 48.0, min_ram_gb: 48.0,
description: "Full Llama 3.1 70B. Best quality, requires significant RAM.".to_string(), description: "Full Llama 3.1 70B. Best quality, requires significant RAM.".to_string(),
recommended: ram >= 48.0 recommended: ram >= 48.0 || (has_gpu && hw.gpu_vram_gb.unwrap_or(0.0) >= 40.0),
|| (has_gpu && hw.gpu_vram_gb.unwrap_or(0.0) >= 40.0),
}, },
]; ];