pipeline dag visualization + Dashboard command center upgrade + command palette wiring. fixed repo pipeline page.
This commit is contained in:
@@ -48,18 +48,34 @@ const dashRepoSchema = z.object({
|
||||
openIssueCount: z.number(),
|
||||
})
|
||||
|
||||
const dashRunSchema = z.object({
|
||||
id: z.number(),
|
||||
repoId: z.number(),
|
||||
repoName: z.string(),
|
||||
ownerName: z.string(),
|
||||
triggerRef: z.string(),
|
||||
triggerSha: z.string(),
|
||||
triggeredBy: z.string(),
|
||||
status: z.string(),
|
||||
startedAt: z.string().nullable(),
|
||||
finishedAt: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
})
|
||||
|
||||
const dashboardSchema = z.object({
|
||||
stats: statsSchema,
|
||||
reviewQueue: z.array(dashPRSchema),
|
||||
myOpenPRs: z.array(dashPRSchema),
|
||||
myOpenIssues: z.array(dashIssueSchema),
|
||||
repos: z.array(dashRepoSchema),
|
||||
recentRuns: z.array(dashRunSchema).optional().default([]),
|
||||
})
|
||||
|
||||
export type DashboardData = z.infer<typeof dashboardSchema>
|
||||
export type DashPR = z.infer<typeof dashPRSchema>
|
||||
export type DashIssue = z.infer<typeof dashIssueSchema>
|
||||
export type DashRepo = z.infer<typeof dashRepoSchema>
|
||||
export type DashRun = z.infer<typeof dashRunSchema>
|
||||
|
||||
export function useDashboard() {
|
||||
return useQuery({
|
||||
|
||||
@@ -1,37 +1,190 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { z } from 'zod'
|
||||
import { api } from '../client'
|
||||
import type { Pipeline } from '../../types/api'
|
||||
import type { Pipeline, PipelineRun, RunDetail, StepLogs } from '../../types/api'
|
||||
|
||||
// ── Zod schemas ───────────────────────────────────────────────────────────────
|
||||
|
||||
const runStatusSchema = z.enum(['queued', 'running', 'succeeded', 'failed', 'cancelled'])
|
||||
|
||||
const pipelineSchema = z.object({
|
||||
id: z.number(),
|
||||
repoId: z.number(),
|
||||
ref: z.string(),
|
||||
status: z.enum(['pending', 'running', 'success', 'failure', 'cancelled']),
|
||||
name: z.string(),
|
||||
filePath: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
const pipelinesSchema = z.array(pipelineSchema)
|
||||
const runSchema = z.object({
|
||||
id: z.number(),
|
||||
pipelineId: z.number(),
|
||||
repoId: z.number(),
|
||||
triggerRef: z.string(),
|
||||
triggerSha: z.string(),
|
||||
triggeredBy: z.string(),
|
||||
status: runStatusSchema,
|
||||
startedAt: z.string().nullable(),
|
||||
finishedAt: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
})
|
||||
|
||||
const stepSchema = z.object({
|
||||
id: z.number(),
|
||||
jobId: z.number(),
|
||||
seq: z.number(),
|
||||
name: z.string(),
|
||||
runCmd: z.string(),
|
||||
usesAction: z.string(),
|
||||
status: runStatusSchema,
|
||||
exitCode: z.number(),
|
||||
startedAt: z.string().nullable(),
|
||||
finishedAt: z.string().nullable(),
|
||||
})
|
||||
|
||||
const jobSchema = z.object({
|
||||
id: z.number(),
|
||||
runId: z.number(),
|
||||
name: z.string(),
|
||||
image: z.string(),
|
||||
needs: z.string(),
|
||||
status: runStatusSchema,
|
||||
startedAt: z.string().nullable(),
|
||||
finishedAt: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
steps: z.array(stepSchema),
|
||||
})
|
||||
|
||||
const runDetailSchema = runSchema.extend({
|
||||
jobs: z.array(jobSchema),
|
||||
})
|
||||
|
||||
const stepLogSchema = z.object({
|
||||
id: z.number(),
|
||||
stepId: z.number(),
|
||||
chunkIndex: z.number(),
|
||||
content: z.string(),
|
||||
createdAt: z.string(),
|
||||
})
|
||||
|
||||
const stepLogsSchema = z.array(
|
||||
stepSchema.extend({ logs: z.array(stepLogSchema) }),
|
||||
)
|
||||
|
||||
// ── Queries ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Pipeline definitions for a repo. */
|
||||
export function usePipelines(owner: string, repo: string) {
|
||||
return useQuery({
|
||||
queryKey: ['repos', owner, repo, 'pipelines'],
|
||||
queryFn: () =>
|
||||
api.get<Pipeline[]>(`/api/v1/repos/${owner}/${repo}/pipelines`, pipelinesSchema),
|
||||
api.get<Pipeline[]>(`/api/v1/repos/${owner}/${repo}/pipelines`, z.array(pipelineSchema)),
|
||||
enabled: Boolean(owner && repo),
|
||||
refetchInterval: 5000, // poll while pipelines may be running
|
||||
})
|
||||
}
|
||||
|
||||
export function usePipeline(owner: string, repo: string, runId: number) {
|
||||
/** Pipeline runs for a repo, newest first. */
|
||||
export function useRuns(owner: string, repo: string, limit = 30) {
|
||||
return useQuery({
|
||||
queryKey: ['repos', owner, repo, 'pipelines', runId],
|
||||
queryKey: ['repos', owner, repo, 'runs', limit],
|
||||
queryFn: () =>
|
||||
api.get<Pipeline>(
|
||||
`/api/v1/repos/${owner}/${repo}/pipelines/${runId}`,
|
||||
pipelineSchema,
|
||||
api.get<PipelineRun[]>(
|
||||
`/api/v1/repos/${owner}/${repo}/runs?limit=${limit}`,
|
||||
z.array(runSchema),
|
||||
),
|
||||
enabled: Boolean(owner && repo && runId),
|
||||
enabled: Boolean(owner && repo),
|
||||
refetchInterval: 8_000, // poll while runs may be active
|
||||
})
|
||||
}
|
||||
|
||||
/** Run detail: run + jobs (each with steps). */
|
||||
export function useRunDetail(owner: string, repo: string, runId: number) {
|
||||
return useQuery({
|
||||
queryKey: ['repos', owner, repo, 'runs', runId],
|
||||
queryFn: () =>
|
||||
api.get<RunDetail>(
|
||||
`/api/v1/repos/${owner}/${repo}/runs/${runId}`,
|
||||
runDetailSchema,
|
||||
),
|
||||
enabled: Boolean(owner && repo && runId),
|
||||
refetchInterval: (query) => {
|
||||
const status = query.state.data?.status
|
||||
return status === 'running' || status === 'queued' ? 3_000 : false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Step-level log chunks for a job. */
|
||||
export function useJobLogs(owner: string, repo: string, runId: number, jobId: number) {
|
||||
return useQuery({
|
||||
queryKey: ['repos', owner, repo, 'runs', runId, 'jobs', jobId, 'logs'],
|
||||
queryFn: () =>
|
||||
api.get<StepLogs>(
|
||||
`/api/v1/repos/${owner}/${repo}/runs/${runId}/jobs/${jobId}/logs`,
|
||||
stepLogsSchema,
|
||||
),
|
||||
enabled: Boolean(owner && repo && runId && jobId),
|
||||
refetchInterval: (query) => {
|
||||
// Keep polling only while the job may still be running
|
||||
const hasRunning = query.state.data?.some(s => s.status === 'running')
|
||||
return hasRunning ? 2_000 : false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Recent pipeline runs across all repos owned by the current user. */
|
||||
export function useRecentRuns(limit = 20) {
|
||||
return useQuery({
|
||||
queryKey: ['pipelines', 'runs', limit],
|
||||
queryFn: () =>
|
||||
api.get<RecentRun[]>(`/api/v1/pipelines/runs?limit=${limit}`, recentRunSchema),
|
||||
refetchInterval: 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Mutations ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCancelRun(owner: string, repo: string) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (runId: number) =>
|
||||
api.post(`/api/v1/repos/${owner}/${repo}/runs/${runId}/cancel`, z.unknown(), undefined),
|
||||
onSuccess: (_data, runId) => {
|
||||
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'runs'] })
|
||||
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'runs', runId] })
|
||||
qc.invalidateQueries({ queryKey: ['pipelines', 'runs'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useRetryJob(owner: string, repo: string, runId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (jobId: number) =>
|
||||
api.post(
|
||||
`/api/v1/repos/${owner}/${repo}/runs/${runId}/jobs/${jobId}/retry`,
|
||||
z.unknown(),
|
||||
undefined,
|
||||
),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'runs', runId] })
|
||||
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'runs'] })
|
||||
qc.invalidateQueries({ queryKey: ['pipelines', 'runs'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ── Cross-repo recent run type ─────────────────────────────────────────────────
|
||||
// Returned by GET /api/v1/pipelines/runs — extends PipelineRun with repo context.
|
||||
|
||||
export interface RecentRun extends PipelineRun {
|
||||
repoName: string
|
||||
ownerName: string
|
||||
}
|
||||
|
||||
const recentRunSchema = z.array(
|
||||
runSchema.extend({
|
||||
repoName: z.string(),
|
||||
ownerName: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user