feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
use super::{ConnectionResult, TicketResult};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct ServiceNowConfig {
|
|
|
|
|
pub instance_url: String,
|
|
|
|
|
pub username: String,
|
|
|
|
|
pub password: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Incident {
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
pub sys_id: String,
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
pub number: String,
|
|
|
|
|
pub short_description: String,
|
|
|
|
|
pub description: String,
|
|
|
|
|
pub urgency: String,
|
|
|
|
|
pub impact: String,
|
|
|
|
|
pub state: String,
|
|
|
|
|
}
|
|
|
|
|
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
/// Test connection to ServiceNow by querying a single incident
|
|
|
|
|
pub async fn test_connection(config: &ServiceNowConfig) -> Result<ConnectionResult, String> {
|
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
let url = format!(
|
|
|
|
|
"{}/api/now/table/incident",
|
|
|
|
|
config.instance_url.trim_end_matches('/')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let resp = client
|
|
|
|
|
.get(&url)
|
|
|
|
|
.basic_auth(&config.username, Some(&config.password))
|
|
|
|
|
.query(&[("sysparm_limit", "1")])
|
|
|
|
|
.send()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Connection failed: {}", e))?;
|
|
|
|
|
|
|
|
|
|
if resp.status().is_success() {
|
|
|
|
|
Ok(ConnectionResult {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Successfully connected to ServiceNow".to_string(),
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
Ok(ConnectionResult {
|
|
|
|
|
success: false,
|
|
|
|
|
message: format!("Connection failed with status: {}", resp.status()),
|
|
|
|
|
})
|
|
|
|
|
}
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
}
|
|
|
|
|
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
/// Search for incidents by description or number
|
|
|
|
|
pub async fn search_incidents(
|
|
|
|
|
config: &ServiceNowConfig,
|
|
|
|
|
query: &str,
|
|
|
|
|
) -> Result<Vec<Incident>, String> {
|
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
let url = format!(
|
|
|
|
|
"{}/api/now/table/incident",
|
|
|
|
|
config.instance_url.trim_end_matches('/')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let sysparm_query = format!("short_descriptionLIKE{}", query);
|
|
|
|
|
|
|
|
|
|
let resp = client
|
|
|
|
|
.get(&url)
|
|
|
|
|
.basic_auth(&config.username, Some(&config.password))
|
|
|
|
|
.query(&[("sysparm_query", &sysparm_query), ("sysparm_limit", &"10".to_string())])
|
|
|
|
|
.send()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Search failed: {}", e))?;
|
|
|
|
|
|
|
|
|
|
if !resp.status().is_success() {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Search failed: {} - {}",
|
|
|
|
|
resp.status(),
|
|
|
|
|
resp.text().await.unwrap_or_default()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let body: serde_json::Value = resp
|
|
|
|
|
.json()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let incidents = body["result"]
|
|
|
|
|
.as_array()
|
|
|
|
|
.unwrap_or(&vec![])
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|i| {
|
|
|
|
|
Some(Incident {
|
|
|
|
|
sys_id: i["sys_id"].as_str()?.to_string(),
|
|
|
|
|
number: i["number"].as_str()?.to_string(),
|
|
|
|
|
short_description: i["short_description"].as_str()?.to_string(),
|
|
|
|
|
description: i["description"].as_str().unwrap_or("").to_string(),
|
|
|
|
|
urgency: i["urgency"].as_str().unwrap_or("3").to_string(),
|
|
|
|
|
impact: i["impact"].as_str().unwrap_or("3").to_string(),
|
|
|
|
|
state: i["state"].as_str().unwrap_or("1").to_string(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Ok(incidents)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a new incident in ServiceNow
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
pub async fn create_incident(
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
config: &ServiceNowConfig,
|
|
|
|
|
short_description: &str,
|
|
|
|
|
description: &str,
|
|
|
|
|
urgency: &str,
|
|
|
|
|
impact: &str,
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
) -> Result<TicketResult, String> {
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
let url = format!(
|
|
|
|
|
"{}/api/now/table/incident",
|
|
|
|
|
config.instance_url.trim_end_matches('/')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let body = serde_json::json!({
|
|
|
|
|
"short_description": short_description,
|
|
|
|
|
"description": description,
|
|
|
|
|
"urgency": urgency,
|
|
|
|
|
"impact": impact,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let resp = client
|
|
|
|
|
.post(&url)
|
|
|
|
|
.basic_auth(&config.username, Some(&config.password))
|
|
|
|
|
.header("Content-Type", "application/json")
|
|
|
|
|
.json(&body)
|
|
|
|
|
.send()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to create incident: {}", e))?;
|
|
|
|
|
|
|
|
|
|
if !resp.status().is_success() {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Failed to create incident: {} - {}",
|
|
|
|
|
resp.status(),
|
|
|
|
|
resp.text().await.unwrap_or_default()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let result: serde_json::Value = resp
|
|
|
|
|
.json()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let incident_number = result["result"]["number"].as_str().unwrap_or("");
|
|
|
|
|
let sys_id = result["result"]["sys_id"].as_str().unwrap_or("");
|
|
|
|
|
let incident_url = format!(
|
|
|
|
|
"{}/nav_to.do?uri=incident.do?sys_id={}",
|
|
|
|
|
config.instance_url.trim_end_matches('/'),
|
|
|
|
|
sys_id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(TicketResult {
|
|
|
|
|
id: sys_id.to_string(),
|
|
|
|
|
ticket_number: incident_number.to_string(),
|
|
|
|
|
url: incident_url,
|
|
|
|
|
})
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
}
|
|
|
|
|
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
/// Get an incident by sys_id or number
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
pub async fn get_incident(
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
config: &ServiceNowConfig,
|
|
|
|
|
incident_id: &str,
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
) -> Result<Incident, String> {
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
|
|
// Determine if incident_id is a sys_id or incident number
|
|
|
|
|
let (url, use_query) = if incident_id.starts_with("INC") {
|
|
|
|
|
// It's an incident number, use query parameter
|
|
|
|
|
(
|
|
|
|
|
format!(
|
|
|
|
|
"{}/api/now/table/incident",
|
|
|
|
|
config.instance_url.trim_end_matches('/')
|
|
|
|
|
),
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// It's a sys_id, use direct path
|
|
|
|
|
(
|
|
|
|
|
format!(
|
|
|
|
|
"{}/api/now/table/incident/{}",
|
|
|
|
|
config.instance_url.trim_end_matches('/'),
|
|
|
|
|
incident_id
|
|
|
|
|
),
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut request = client
|
|
|
|
|
.get(&url)
|
|
|
|
|
.basic_auth(&config.username, Some(&config.password));
|
|
|
|
|
|
|
|
|
|
if use_query {
|
|
|
|
|
request = request.query(&[("sysparm_query", &format!("number={}", incident_id))]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let resp = request
|
|
|
|
|
.send()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to get incident: {}", e))?;
|
|
|
|
|
|
|
|
|
|
if !resp.status().is_success() {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Failed to get incident: {} - {}",
|
|
|
|
|
resp.status(),
|
|
|
|
|
resp.text().await.unwrap_or_default()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let body: serde_json::Value = resp
|
|
|
|
|
.json()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let incident_data = if use_query {
|
|
|
|
|
// Query response has "result" array
|
|
|
|
|
body["result"]
|
|
|
|
|
.as_array()
|
|
|
|
|
.and_then(|arr| arr.first())
|
|
|
|
|
.ok_or_else(|| "Incident not found".to_string())?
|
|
|
|
|
} else {
|
|
|
|
|
// Direct sys_id response has "result" object
|
|
|
|
|
&body["result"]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Incident {
|
|
|
|
|
sys_id: incident_data["sys_id"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.ok_or_else(|| "Missing sys_id".to_string())?
|
|
|
|
|
.to_string(),
|
|
|
|
|
number: incident_data["number"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.ok_or_else(|| "Missing number".to_string())?
|
2026-03-15 17:43:46 +00:00
|
|
|
.to_string(),
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
short_description: incident_data["short_description"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.ok_or_else(|| "Missing short_description".to_string())?
|
|
|
|
|
.to_string(),
|
|
|
|
|
description: incident_data["description"].as_str().unwrap_or("").to_string(),
|
|
|
|
|
urgency: incident_data["urgency"].as_str().unwrap_or("3").to_string(),
|
|
|
|
|
impact: incident_data["impact"].as_str().unwrap_or("3").to_string(),
|
|
|
|
|
state: incident_data["state"].as_str().unwrap_or("1").to_string(),
|
|
|
|
|
})
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
}
|
|
|
|
|
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
/// Update an existing incident
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
pub async fn update_incident(
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
config: &ServiceNowConfig,
|
|
|
|
|
sys_id: &str,
|
|
|
|
|
updates: serde_json::Value,
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
) -> Result<TicketResult, String> {
|
feat: implement Confluence, ServiceNow, and Azure DevOps REST API clients
- Confluence: OAuth2 bearer auth, list_spaces, search_pages, publish_page, update_page
- ServiceNow: Basic auth, search_incidents, create_incident, get_incident, update_incident
- Azure DevOps: OAuth2 bearer auth, search_work_items, create_work_item, get_work_item, update_work_item
- Added TicketResult.id field to support both sys_id and ticket_number
- All implementations follow TDD with mockito HTTP mocking
- 19 tests passing across all three integrations
2026-04-03 20:43:37 +00:00
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
let url = format!(
|
|
|
|
|
"{}/api/now/table/incident/{}",
|
|
|
|
|
config.instance_url.trim_end_matches('/'),
|
|
|
|
|
sys_id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let resp = client
|
|
|
|
|
.patch(&url)
|
|
|
|
|
.basic_auth(&config.username, Some(&config.password))
|
|
|
|
|
.header("Content-Type", "application/json")
|
|
|
|
|
.json(&updates)
|
|
|
|
|
.send()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to update incident: {}", e))?;
|
|
|
|
|
|
|
|
|
|
if !resp.status().is_success() {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Failed to update incident: {} - {}",
|
|
|
|
|
resp.status(),
|
|
|
|
|
resp.text().await.unwrap_or_default()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let result: serde_json::Value = resp
|
|
|
|
|
.json()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let incident_number = result["result"]["number"].as_str().unwrap_or("");
|
|
|
|
|
let updated_sys_id = result["result"]["sys_id"].as_str().unwrap_or(sys_id);
|
|
|
|
|
let incident_url = format!(
|
|
|
|
|
"{}/nav_to.do?uri=incident.do?sys_id={}",
|
|
|
|
|
config.instance_url.trim_end_matches('/'),
|
|
|
|
|
updated_sys_id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(TicketResult {
|
|
|
|
|
id: updated_sys_id.to_string(),
|
|
|
|
|
ticket_number: incident_number.to_string(),
|
|
|
|
|
url: incident_url,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_connection_success() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("GET", "/api/now/table/incident")
|
|
|
|
|
.match_header("authorization", mockito::Matcher::Regex("Basic .+".into()))
|
|
|
|
|
.match_query(mockito::Matcher::AllOf(vec![
|
|
|
|
|
mockito::Matcher::UrlEncoded("sysparm_limit".into(), "1".into()),
|
|
|
|
|
]))
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(r#"{"result":[]}"#)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = test_connection(&config).await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let conn = result.unwrap();
|
|
|
|
|
assert!(conn.success);
|
|
|
|
|
assert!(conn.message.contains("Successfully connected"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_connection_failure() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("GET", "/api/now/table/incident")
|
|
|
|
|
.match_query(mockito::Matcher::AllOf(vec![
|
|
|
|
|
mockito::Matcher::UrlEncoded("sysparm_limit".into(), "1".into()),
|
|
|
|
|
]))
|
|
|
|
|
.with_status(401)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "wrong_password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = test_connection(&config).await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let conn = result.unwrap();
|
|
|
|
|
assert!(!conn.success);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_search_incidents() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("GET", "/api/now/table/incident")
|
|
|
|
|
.match_header("authorization", mockito::Matcher::Regex("Basic .+".into()))
|
|
|
|
|
.match_query(mockito::Matcher::AllOf(vec![
|
|
|
|
|
mockito::Matcher::UrlEncoded("sysparm_query".into(), "short_descriptionLIKElogin".into()),
|
|
|
|
|
mockito::Matcher::UrlEncoded("sysparm_limit".into(), "10".into()),
|
|
|
|
|
]))
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(
|
|
|
|
|
r#"{
|
|
|
|
|
"result": [
|
|
|
|
|
{
|
|
|
|
|
"sys_id": "abc123",
|
|
|
|
|
"number": "INC0010001",
|
|
|
|
|
"short_description": "Login issue",
|
|
|
|
|
"description": "Users cannot login",
|
|
|
|
|
"urgency": "2",
|
|
|
|
|
"impact": "2",
|
|
|
|
|
"state": "2"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}"#,
|
|
|
|
|
)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = search_incidents(&config, "login").await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let incidents = result.unwrap();
|
|
|
|
|
assert_eq!(incidents.len(), 1);
|
|
|
|
|
assert_eq!(incidents[0].number, "INC0010001");
|
|
|
|
|
assert_eq!(incidents[0].short_description, "Login issue");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_create_incident() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("POST", "/api/now/table/incident")
|
|
|
|
|
.match_header("authorization", mockito::Matcher::Regex("Basic .+".into()))
|
|
|
|
|
.match_header("content-type", "application/json")
|
|
|
|
|
.with_status(201)
|
|
|
|
|
.with_body(
|
|
|
|
|
r#"{
|
|
|
|
|
"result": {
|
|
|
|
|
"sys_id": "def456",
|
|
|
|
|
"number": "INC0010002"
|
|
|
|
|
}
|
|
|
|
|
}"#,
|
|
|
|
|
)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = create_incident(&config, "Test issue", "Description", "3", "3").await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let ticket = result.unwrap();
|
|
|
|
|
assert_eq!(ticket.ticket_number, "INC0010002");
|
|
|
|
|
assert_eq!(ticket.id, "def456");
|
|
|
|
|
assert!(ticket.url.contains("def456"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_get_incident_by_sys_id() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("GET", "/api/now/table/incident/abc123")
|
|
|
|
|
.match_header("authorization", mockito::Matcher::Regex("Basic .+".into()))
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(
|
|
|
|
|
r#"{
|
|
|
|
|
"result": {
|
|
|
|
|
"sys_id": "abc123",
|
|
|
|
|
"number": "INC0010001",
|
|
|
|
|
"short_description": "Login issue",
|
|
|
|
|
"description": "Users cannot login",
|
|
|
|
|
"urgency": "2",
|
|
|
|
|
"impact": "2",
|
|
|
|
|
"state": "2"
|
|
|
|
|
}
|
|
|
|
|
}"#,
|
|
|
|
|
)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = get_incident(&config, "abc123").await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let incident = result.unwrap();
|
|
|
|
|
assert_eq!(incident.sys_id, "abc123");
|
|
|
|
|
assert_eq!(incident.number, "INC0010001");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_get_incident_by_number() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("GET", "/api/now/table/incident")
|
|
|
|
|
.match_header("authorization", mockito::Matcher::Regex("Basic .+".into()))
|
|
|
|
|
.match_query(mockito::Matcher::AllOf(vec![
|
|
|
|
|
mockito::Matcher::UrlEncoded("sysparm_query".into(), "number=INC0010001".into()),
|
|
|
|
|
]))
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(
|
|
|
|
|
r#"{
|
|
|
|
|
"result": [{
|
|
|
|
|
"sys_id": "abc123",
|
|
|
|
|
"number": "INC0010001",
|
|
|
|
|
"short_description": "Login issue",
|
|
|
|
|
"description": "Users cannot login",
|
|
|
|
|
"urgency": "2",
|
|
|
|
|
"impact": "2",
|
|
|
|
|
"state": "2"
|
|
|
|
|
}]
|
|
|
|
|
}"#,
|
|
|
|
|
)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = get_incident(&config, "INC0010001").await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let incident = result.unwrap();
|
|
|
|
|
assert_eq!(incident.sys_id, "abc123");
|
|
|
|
|
assert_eq!(incident.number, "INC0010001");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_update_incident() {
|
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
|
let mock = server
|
|
|
|
|
.mock("PATCH", "/api/now/table/incident/abc123")
|
|
|
|
|
.match_header("authorization", mockito::Matcher::Regex("Basic .+".into()))
|
|
|
|
|
.match_header("content-type", "application/json")
|
|
|
|
|
.with_status(200)
|
|
|
|
|
.with_body(
|
|
|
|
|
r#"{
|
|
|
|
|
"result": {
|
|
|
|
|
"sys_id": "abc123",
|
|
|
|
|
"number": "INC0010001"
|
|
|
|
|
}
|
|
|
|
|
}"#,
|
|
|
|
|
)
|
|
|
|
|
.create_async()
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let config = ServiceNowConfig {
|
|
|
|
|
instance_url: server.url(),
|
|
|
|
|
username: "admin".to_string(),
|
|
|
|
|
password: "password".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let updates = serde_json::json!({
|
|
|
|
|
"state": "6",
|
|
|
|
|
"close_notes": "Issue resolved"
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let result = update_incident(&config, "abc123", updates).await;
|
|
|
|
|
mock.assert_async().await;
|
|
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
let ticket = result.unwrap();
|
|
|
|
|
assert_eq!(ticket.id, "abc123");
|
|
|
|
|
assert_eq!(ticket.ticket_number, "INC0010001");
|
|
|
|
|
}
|
feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.
Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)
Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)
DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload
Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
|
|
|
}
|