feat(schema-workflow): Phase 1b — SchemaDraftDrawer with status-aware actions

This commit is contained in:
Александр Зимин
2026-05-12 11:08:31 +00:00
parent 2a3f2099e5
commit dd3a2d97e5
6 changed files with 1065 additions and 3 deletions
+136
View File
@@ -7,11 +7,17 @@ import {
type CascadeCloseResult,
type CreateDictionaryRequest,
type CreateRecordRequest,
type CreateSchemaDraftRequest,
type CreateWebhookSubscriptionRequest,
type DecisionRequest,
type DictionaryDetail,
type DraftResponse,
type PublishSchemaDraftRequest,
type PublishSchemaDraftResult,
type RecordResponse,
type SchemaDraft,
type SubmitDraftRequest,
type SubmitForReviewRequest,
type WebhookDelivery,
type WebhookSubscription,
type WebhookTestPingResult,
@@ -480,6 +486,136 @@ export const useWithdrawDraft = () => {
})
}
// === Schema workflow mutations (Phase 1a, v2.2.0 backend) =============
/**
* Создаёт schema draft от current HEAD. Backend проверяет fromVersion
* против live schemaVersion (409 version_mismatch если расходится).
* Также 409 active_draft_exists если на dict'е уже есть non-terminal draft.
*
* <p>onSuccess invalidates: active-draft query (banner picks up) +
* drafts list + reviewer pool queue.
*/
export const useCreateSchemaDraft = (dictionaryName: string) => {
const qc = useQueryClient()
return useMutation({
mutationFn: async (payload: CreateSchemaDraftRequest): Promise<SchemaDraft> => {
const { data } = await apiClient.post<SchemaDraft>(
`/dictionaries/${dictionaryName}/drafts`,
payload,
)
return data
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['schema-draft-active', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-drafts', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-review-queue'] })
},
})
}
/** Maker submits draft → review_pending. 202 Accepted. */
export const useSubmitSchemaDraftForReview = (dictionaryName: string) => {
const qc = useQueryClient()
return useMutation({
mutationFn: async (params: {
draftId: string
body?: SubmitForReviewRequest
}): Promise<SchemaDraft> => {
const { data } = await apiClient.post<SchemaDraft>(
`/dictionaries/${dictionaryName}/drafts/${params.draftId}/review`,
params.body ?? {},
)
return data
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['schema-draft-active', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-draft', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-drafts', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-review-queue'] })
},
})
}
/**
* Reviewer decide: approve / request_changes / reject. Backend
* проверяет self_approve_forbidden (maker_id != actor) → 403.
*/
export const useDecideSchemaDraft = (dictionaryName: string) => {
const qc = useQueryClient()
return useMutation({
mutationFn: async (params: {
draftId: string
body: DecisionRequest
}): Promise<SchemaDraft> => {
const { data } = await apiClient.post<SchemaDraft>(
`/dictionaries/${dictionaryName}/drafts/${params.draftId}/decision`,
params.body,
)
return data
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['schema-draft-active', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-draft', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-drafts', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-review-queue'] })
// Changelog gets new entry (draft_approve / draft_reject / draft_changes_requested)
qc.invalidateQueries({ queryKey: ['changelog', dictionaryName] })
},
})
}
/**
* Publish approved draft → bump live HEAD. Backend computes new version
* (minor default, major на breaking). Returns wrapped {draft, newVersion, publishedAt}.
*
* <p>onSuccess invalidates dict detail (schema/version changed) + changelog +
* draft queries.
*/
export const usePublishSchemaDraft = (dictionaryName: string) => {
const qc = useQueryClient()
return useMutation({
mutationFn: async (params: {
draftId: string
body: PublishSchemaDraftRequest
}): Promise<PublishSchemaDraftResult> => {
const { data } = await apiClient.post<PublishSchemaDraftResult>(
`/dictionaries/${dictionaryName}/drafts/${params.draftId}/publish`,
params.body,
)
return data
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['schema-draft-active', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-draft', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-drafts', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-review-queue'] })
qc.invalidateQueries({ queryKey: ['dictionary', dictionaryName] })
qc.invalidateQueries({ queryKey: ['changelog', dictionaryName] })
},
})
}
/** Maker withdraws non-terminal draft (terminal state). */
export const useWithdrawSchemaDraft = (dictionaryName: string) => {
const qc = useQueryClient()
return useMutation({
mutationFn: async (draftId: string): Promise<SchemaDraft> => {
const { data } = await apiClient.delete<SchemaDraft>(
`/dictionaries/${dictionaryName}/drafts/${draftId}`,
)
return data
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['schema-draft-active', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-draft', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-drafts', dictionaryName] })
qc.invalidateQueries({ queryKey: ['schema-review-queue'] })
qc.invalidateQueries({ queryKey: ['changelog', dictionaryName] })
},
})
}
export const useRetryWebhookDelivery = () => {
const qc = useQueryClient()
return useMutation({