Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 370 additions & 0 deletions web/e2e/authoring-flow.spec.ts

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions web/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ import type {
BatchMemberResponse,
SkillSuite,
SkillSuiteDraftInput,
AuthoringDraft,
DraftFileSummary,
DraftFileContent,
SaveDraftFileOutcome,
RuntimeBindingInfo,
ValidationRunInfo,
ValidationEventInfo,
ValidationFindingInfo,
SubmitDraftOutcome,
} from './types'
import { ApiError } from '@/shared/lib/api-error'
import i18n from '@/i18n/config'
Expand Down Expand Up @@ -2281,3 +2290,162 @@ export const notificationApi = {
})
},
}

export const authoringApi = {
async listDrafts(): Promise<AuthoringDraft[]> {
return fetchJson<AuthoringDraft[]>(`${WEB_API_PREFIX}/authoring/drafts`)
},

async createDraft(request: { namespaceSlug: string, name: string, requirement?: string }): Promise<AuthoringDraft> {
return fetchJson<AuthoringDraft>(`${WEB_API_PREFIX}/authoring/drafts`, {
method: 'POST',
headers: await ensureCsrfHeaders({
'Content-Type': 'application/json',
}),
body: JSON.stringify(request),
})
},

async getDraft(draftId: number): Promise<AuthoringDraft> {
return fetchJson<AuthoringDraft>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}`)
},

async deleteDraft(draftId: number): Promise<void> {
await fetchJson<null>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}`, {
method: 'DELETE',
headers: await ensureCsrfHeaders(),
})
},

async listFiles(draftId: number): Promise<DraftFileSummary[]> {
return fetchJson<DraftFileSummary[]>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/files`)
},

async readFile(draftId: number, path: string): Promise<DraftFileContent> {
const query = new URLSearchParams({ path })
return fetchJson<DraftFileContent>(
`${WEB_API_PREFIX}/authoring/drafts/${draftId}/files/content?${query.toString()}`,
)
},

async saveFile(
draftId: number,
request: { path: string, content: string, contentType?: string, expectedRevision?: number, base64?: boolean },
): Promise<SaveDraftFileOutcome> {
return fetchJson<SaveDraftFileOutcome>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/files`, {
method: 'PUT',
headers: await ensureCsrfHeaders({
'Content-Type': 'application/json',
}),
body: JSON.stringify({
path: request.path,
content: request.content,
encoding: request.base64 ? 'base64' : undefined,
contentType: request.contentType,
expectedRevision: request.expectedRevision,
}),
})
},

async deleteFile(draftId: number, path: string, expectedRevision?: number): Promise<void> {
const query = new URLSearchParams({ path })
if (expectedRevision !== undefined) {
query.set('expectedRevision', String(expectedRevision))
}
await fetchJson<null>(
`${WEB_API_PREFIX}/authoring/drafts/${draftId}/files?${query.toString()}`,
{
method: 'DELETE',
headers: await ensureCsrfHeaders(),
},
)
},

async getRuntimeBinding(draftId: number): Promise<RuntimeBindingInfo> {
return fetchJson<RuntimeBindingInfo>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/runtime`)
},

async saveRuntimeBinding(
draftId: number,
request: { agentType: string, config?: Record<string, unknown>, toolAllowlist?: string[], mcpServers?: Record<string, unknown>[] },
): Promise<RuntimeBindingInfo> {
return fetchJson<RuntimeBindingInfo>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/runtime`, {
method: 'PUT',
headers: await ensureCsrfHeaders({
'Content-Type': 'application/json',
}),
body: JSON.stringify({
agentType: request.agentType,
config: request.config ?? {},
toolAllowlist: request.toolAllowlist ?? [],
mcpServers: request.mcpServers ?? [],
}),
})
},

async startRun(draftId: number): Promise<ValidationRunInfo> {
return fetchJson<ValidationRunInfo>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/runs`, {
method: 'POST',
headers: await ensureCsrfHeaders(),
})
},

async listRuns(draftId: number): Promise<ValidationRunInfo[]> {
return fetchJson<ValidationRunInfo[]>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/runs`)
},

async getRun(runId: number): Promise<ValidationRunInfo> {
return fetchJson<ValidationRunInfo>(`${WEB_API_PREFIX}/authoring/runs/${runId}`)
},

async cancelRun(runId: number): Promise<ValidationRunInfo> {
return fetchJson<ValidationRunInfo>(`${WEB_API_PREFIX}/authoring/runs/${runId}/cancel`, {
method: 'POST',
headers: await ensureCsrfHeaders(),
})
},

async listEvents(runId: number, afterSeq?: number): Promise<ValidationEventInfo[]> {
const query = afterSeq === undefined ? '' : `?afterSeq=${afterSeq}`
return fetchJson<ValidationEventInfo[]>(`${WEB_API_PREFIX}/authoring/runs/${runId}/events${query}`)
},

async listFindings(runId: number): Promise<ValidationFindingInfo[]> {
return fetchJson<ValidationFindingInfo[]>(`${WEB_API_PREFIX}/authoring/runs/${runId}/findings`)
},

async applyFix(runId: number, findingId: number): Promise<ValidationFindingInfo> {
return fetchJson<ValidationFindingInfo>(
`${WEB_API_PREFIX}/authoring/runs/${runId}/findings/${findingId}/apply`,
{
method: 'POST',
headers: await ensureCsrfHeaders(),
},
)
},

async dismissFinding(runId: number, findingId: number): Promise<ValidationFindingInfo> {
return fetchJson<ValidationFindingInfo>(
`${WEB_API_PREFIX}/authoring/runs/${runId}/findings/${findingId}/dismiss`,
{
method: 'POST',
headers: await ensureCsrfHeaders(),
},
)
},

async submitDraft(draftId: number, visibility: 'PRIVATE' | 'PUBLIC' = 'PRIVATE'): Promise<SubmitDraftOutcome> {
return fetchJson<SubmitDraftOutcome>(`${WEB_API_PREFIX}/authoring/drafts/${draftId}/submit`, {
method: 'POST',
headers: await ensureCsrfHeaders({
'Content-Type': 'application/json',
}),
body: JSON.stringify({ visibility }),
})
},

/** Absolute URL for the SSE stream; EventSource resumes from the browser-managed Last-Event-ID. */
validationStreamUrl(runId: number): string {
return buildApiUrl(`${WEB_API_PREFIX}/authoring/runs/${runId}/events/stream`)
},
}
Loading