mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 08:03:37 +00:00
* test(repro): demonstrate #7732 GitLab pipeline job details never load in Checks panel Co-authored-by: Orca <help@stably.ai> * fix(gitlab): load pipeline job traces in the Checks side panel (#7732) Expanding a GitLab pipeline job in the Checks panel always showed "No inline details are available for this check.": the mapper dropped the numeric job id, `PRCheckDetail` had nowhere to carry it, and every consumer called the GitHub check-runs API, which returns null for a GitLab job. - carry `gitlabJobId` on `PRCheckDetail` and add the `gitlab-job:` branch to all three identity ladders (panel rows, editor tabs, fix-prompt keys) so same-stage jobs with no web_url stop colliding - add a runtime-routed trace client so SSH/remote workspaces work, not just local IPC, and thread the MR's `projectRef` for fork pipelines - bound the trace in main via the existing `sliceCheckLogTail` (now shared, not GitHub-only) so a multi-megabyte CI log never crosses the 1 MB transport frame cap; strip ANSI/section markers up to the CR only, which keeps each section's visible header and command echo - render the excerpt inline instead of "Log tail available in full details." - feed GitLab traces to "Fix with AI", which previously sent bare check names - skip the fetch for jobs that cannot have a trace (created/manual/skipped) so GitLab's 404 does not replace the benign empty state, and re-arm a failed load when the job's state changes since the panel has no retry Co-authored-by: Orca <help@stably.ai> * fix(gitlab): treat a missing job log as an empty log, not an error (#7732) Round-1 review follow-up. - a job canceled before it started (or whose log was erased/expired) is `completed`/`cancelled`, so the panel fetched its trace, GitLab answered 404, and `classifyGlabError`'s issue-edit copy ("Issue not found — it may have been deleted.") landed verbatim on the auto-expanded check row; main now maps that 404 to an empty trace so the row keeps its benign empty state - keep a missing project a real error (GitLab masks unauthorized projects as 404) and add `classifyJobLogError` so 403/unknown failures stop borrowing issue-edit wording on a job-log read - broaden the empty-log copy in all five catalogs: it now covers erased and expired logs, not only jobs that never ran - e2e: derive the repro screenshot dir from `process.cwd()` (or an env override) instead of a hardcoded POSIX path to a throwaway worktree - bound the raw trace before the ANSI/section passes so a multi-megabyte log is not scanned in full on the main-process event loop - drop the redundant `if (repo)` in `handleFixChecksWithAI` and the now-dead "Log tail available in full details." catalog entry Co-authored-by: Orca <help@stably.ai> * fix(gitlab): address review — project ref on reload, retry re-arm, IPC timeout - Carry the MR's GitLab project ref on the check-details tab so reloading a fork/cross-project job tab fetches the trace from the pipeline's own project. - Re-arm the sidebar retry when a details load resolves to null, not only when it throws; a detail-less row otherwise never retried after the job moved on. - Bound the local `gl.jobTrace` IPC call with the same 30s timeout the runtime RPC path uses — glab runs without a subprocess timeout in main. - Document that the trace 404 -> empty-log mapping is deliberately broad. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
173 lines
5.3 KiB
TypeScript
173 lines
5.3 KiB
TypeScript
/* GitLab preload bindings — split out of `src/preload/index.ts` so
|
|
adding or changing a `gl.*` channel doesn't surface as a merge
|
|
conflict on every upstream sync of the much larger central preload
|
|
file. Composed back into `api.gl` from `index.ts`. */
|
|
import { ipcRenderer } from 'electron'
|
|
import type { TaskSourceContext } from '../shared/task-source-context'
|
|
|
|
type GitLabRepoSelectorArgs = {
|
|
repoPath: string
|
|
repoId?: string | null
|
|
sourceContext?: TaskSourceContext | null
|
|
}
|
|
|
|
export const glApi = {
|
|
viewer: (): Promise<unknown> => ipcRenderer.invoke('gitlab:viewer'),
|
|
diagnoseAuth: (): Promise<unknown> => ipcRenderer.invoke('gitlab:diagnoseAuth'),
|
|
rateLimit: (args?: { force?: boolean; host?: string | null }): Promise<unknown> =>
|
|
ipcRenderer.invoke('gitlab:rateLimit', args),
|
|
|
|
projectSlug: (args: GitLabRepoSelectorArgs): Promise<unknown> =>
|
|
ipcRenderer.invoke('gitlab:projectSlug', args),
|
|
|
|
mrForBranch: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
branch: string
|
|
linkedMRIid?: number | null
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:mrForBranch', args),
|
|
|
|
mr: (args: GitLabRepoSelectorArgs & { iid: number }): Promise<unknown> =>
|
|
ipcRenderer.invoke('gitlab:mr', args),
|
|
|
|
listMRs: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
state?: 'opened' | 'merged' | 'closed' | 'all'
|
|
page?: number
|
|
perPage?: number
|
|
query?: string
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:listMRs', args),
|
|
|
|
listWorkItems: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
state?: 'opened' | 'merged' | 'closed' | 'all'
|
|
page?: number
|
|
perPage?: number
|
|
query?: string
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:listWorkItems', args),
|
|
|
|
issue: (args: GitLabRepoSelectorArgs & { number: number }): Promise<unknown> =>
|
|
ipcRenderer.invoke('gitlab:issue', args),
|
|
|
|
listIssues: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
state?: 'opened' | 'closed' | 'all'
|
|
assignee?: string
|
|
limit?: number
|
|
}
|
|
): Promise<{ items: unknown[]; error?: unknown }> =>
|
|
ipcRenderer.invoke('gitlab:listIssues', args),
|
|
|
|
createIssue: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
title: string
|
|
body: string
|
|
}
|
|
): Promise<{ ok: true; number: number; url: string } | { ok: false; error: string }> =>
|
|
ipcRenderer.invoke('gitlab:createIssue', args),
|
|
|
|
updateIssue: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
number: number
|
|
updates: unknown
|
|
}
|
|
): Promise<{ ok: true } | { ok: false; error: string }> =>
|
|
ipcRenderer.invoke('gitlab:updateIssue', args),
|
|
|
|
addIssueComment: (
|
|
args: GitLabRepoSelectorArgs & { number: number; body: string }
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:addIssueComment', args),
|
|
|
|
listLabels: (args: GitLabRepoSelectorArgs): Promise<string[]> =>
|
|
ipcRenderer.invoke('gitlab:listLabels', args),
|
|
|
|
listAssignableUsers: (args: GitLabRepoSelectorArgs): Promise<unknown[]> =>
|
|
ipcRenderer.invoke('gitlab:listAssignableUsers', args),
|
|
|
|
todos: (args: GitLabRepoSelectorArgs): Promise<unknown[]> =>
|
|
ipcRenderer.invoke('gitlab:todos', args),
|
|
|
|
workItemDetails: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
type: 'issue' | 'mr'
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:workItemDetails', args),
|
|
|
|
closeMR: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
}
|
|
): Promise<{ ok: true } | { ok: false; error: string }> =>
|
|
ipcRenderer.invoke('gitlab:closeMR', args),
|
|
|
|
reopenMR: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
}
|
|
): Promise<{ ok: true } | { ok: false; error: string }> =>
|
|
ipcRenderer.invoke('gitlab:reopenMR', args),
|
|
|
|
mergeMR: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
method?: 'merge' | 'squash' | 'rebase'
|
|
}
|
|
): Promise<{ ok: true } | { ok: false; error: string }> =>
|
|
ipcRenderer.invoke('gitlab:mergeMR', args),
|
|
|
|
updateMR: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
updates: unknown
|
|
}
|
|
): Promise<{ ok: true } | { ok: false; error: string }> =>
|
|
ipcRenderer.invoke('gitlab:updateMR', args),
|
|
|
|
updateMRReviewers: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
reviewerIds: number[]
|
|
projectRef?: unknown
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:updateMRReviewers', args),
|
|
|
|
addMRComment: (args: GitLabRepoSelectorArgs & { iid: number; body: string }): Promise<unknown> =>
|
|
ipcRenderer.invoke('gitlab:addMRComment', args),
|
|
|
|
addMRInlineComment: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
input: unknown
|
|
projectRef?: unknown
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:addMRInlineComment', args),
|
|
|
|
resolveMRDiscussion: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
iid: number
|
|
discussionId: string
|
|
resolved: boolean
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:resolveMRDiscussion', args),
|
|
|
|
jobTrace: (
|
|
args: GitLabRepoSelectorArgs & { jobId: number; projectRef?: unknown; logExcerpt?: boolean }
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:jobTrace', args),
|
|
|
|
retryJob: (
|
|
args: GitLabRepoSelectorArgs & { jobId: number; projectRef?: unknown }
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:retryJob', args),
|
|
|
|
workItemByPath: (
|
|
args: GitLabRepoSelectorArgs & {
|
|
host: string
|
|
path: string
|
|
iid: number
|
|
type: 'issue' | 'mr'
|
|
}
|
|
): Promise<unknown> => ipcRenderer.invoke('gitlab:workItemByPath', args)
|
|
}
|