Jobs: list / create / edit + attach to changelist

- Tools -> Jobs: browse/filter jobs, create/edit job spec
- Pending changelist menu -> Attach job... (p4 fix)
- Backend p4 jobs, fix, job -o/-i

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-08 11:59:34 +03:00
parent 7092c9d1dc
commit 2069f0fed4
4 changed files with 141 additions and 1 deletions

View File

@ -1867,6 +1867,59 @@ async fn p4_latest_change(state: State<'_, AppState>, scope: String) -> Result<V
Ok(v.into_iter().next().unwrap_or(Value::Null))
}
/// List jobs on the server (defect/task tracking).
#[tauri::command]
async fn p4_jobs(state: State<'_, AppState>) -> Result<Vec<Value>, String> {
let conn = current(&state)?;
run_json(&conn, &["jobs", "-m", "300"])
}
/// Link a job to a pending changelist (`p4 fix -c <change> <job>`).
#[tauri::command]
async fn p4_fix(state: State<'_, AppState>, job: String, change: String) -> Result<String, String> {
let conn = current(&state)?;
let job = job.trim();
let change = change.trim();
if job.is_empty() || change.is_empty() {
return Err("Job and changelist are required".into());
}
run_text(&conn, &["fix", "-c", change, job])
}
/// Get a job spec as editable text (unknown name → new job template).
#[tauri::command]
async fn p4_job_spec(state: State<'_, AppState>, job: String) -> Result<String, String> {
let conn = current(&state)?;
let j = job.trim();
if j.is_empty() {
run_text(&conn, &["job", "-o"]) // template for a new job
} else {
run_text(&conn, &["job", "-o", j])
}
}
/// Save a job spec (`p4 job -i`), creating it if new.
#[tauri::command]
async fn p4_job_save(state: State<'_, AppState>, spec: String) -> Result<String, String> {
let conn = current(&state)?;
let mut child = p4_cmd(&conn, false)
.args(["job", "-i"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start p4: {e}"))?;
if let Some(si) = child.stdin.as_mut() {
si.write_all(spec.as_bytes()).map_err(|e| e.to_string())?;
}
let out = child.wait_with_output().map_err(|e| e.to_string())?;
if !out.status.success() {
let err = decode(&out.stderr);
return Err(if err.trim().is_empty() { decode(&out.stdout).trim().to_string() } else { err.trim().to_string() });
}
Ok(decode(&out.stdout).trim().to_string())
}
/// List streams on the server.
#[tauri::command]
async fn p4_streams(state: State<'_, AppState>) -> Result<Vec<Value>, String> {
@ -2156,6 +2209,10 @@ pub fn run() {
p4_streams,
p4_switch_stream,
p4_stream_integ,
p4_jobs,
p4_fix,
p4_job_spec,
p4_job_save,
p4_client_spec,
p4_client_save,
uasset_thumbnail,