fix(reviews): friendly error в ReviewDrawer вместо AxiosError
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
TextInput,
|
||||
QueryErrorState,
|
||||
} from '@/ui'
|
||||
import axios from 'axios'
|
||||
import { CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'
|
||||
import { useDraft, useLiveRecord, useReviewQueue, useSchemaReviewQueue } from '@/api/queries'
|
||||
import { useApproveDraft, useRejectDraft } from '@/api/mutations'
|
||||
@@ -402,6 +403,69 @@ type DrawerProps = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Превращает Axios error в human-readable title + body. Mapping для
|
||||
* известных backend кодов (см. {@code OrdinisException.conflict}):
|
||||
* - self_approve_forbidden — maker = reviewer (D3 maker-checker invariant)
|
||||
* - draft_not_pending — кто-то уже approve/reject'нул этот draft
|
||||
* - forbidden_role — у actor'а нет нужной workflow роли (Phase 1d enforcement)
|
||||
*
|
||||
* Default fallback — backend message + статус код. Сырой "AxiosError ... 409"
|
||||
* больше не показывается.
|
||||
*/
|
||||
function extractReviewError(err: unknown): { title: string; body: string } {
|
||||
if (!err) return { title: 'Ошибка', body: '' }
|
||||
if (axios.isAxiosError(err)) {
|
||||
const status = err.response?.status
|
||||
const data = err.response?.data as
|
||||
| { code?: string; message?: string; error?: string }
|
||||
| undefined
|
||||
const code = data?.code
|
||||
const msg = data?.message ?? data?.error ?? err.message
|
||||
|
||||
if (code === 'self_approve_forbidden') {
|
||||
return {
|
||||
title: 'Нельзя одобрить свой собственный черновик',
|
||||
body:
|
||||
'Maker-checker invariant: тот, кто создал draft, не может его approve. Попроси другого reviewer\'а из пула.',
|
||||
}
|
||||
}
|
||||
if (code === 'self_reject_forbidden') {
|
||||
return {
|
||||
title: 'Нельзя отклонить свой собственный черновик',
|
||||
body:
|
||||
'Тот, кто создал draft, не может его reject. Если передумал — используй Отозвать (withdraw).',
|
||||
}
|
||||
}
|
||||
if (code === 'draft_not_pending') {
|
||||
return {
|
||||
title: 'Draft уже не pending',
|
||||
body: 'Кто-то уже принял решение по этому draft\'у. Обнови очередь.',
|
||||
}
|
||||
}
|
||||
if (code === 'forbidden_role') {
|
||||
return {
|
||||
title: 'Недостаточно прав',
|
||||
body: msg || 'У вас нет нужной workflow role для этого действия.',
|
||||
}
|
||||
}
|
||||
if (code === 'reject_reason_required') {
|
||||
return {
|
||||
title: 'Укажите причину отклонения',
|
||||
body: 'Поле «Комментарий ревьюера» обязательно для reject (для compliance).',
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: `Ошибка ${status ?? ''}`.trim(),
|
||||
body: msg,
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: 'Ошибка',
|
||||
body: err instanceof Error ? err.message : String(err),
|
||||
}
|
||||
}
|
||||
|
||||
function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
const { t } = useTranslation()
|
||||
const draftQ = useDraft(draftId)
|
||||
@@ -513,11 +577,15 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(approveMut.error || rejectMut.error) && (
|
||||
<Alert variant="error" title={t('error.failed')}>
|
||||
{String(approveMut.error || rejectMut.error)}
|
||||
{(approveMut.error || rejectMut.error) && (() => {
|
||||
const err = approveMut.error || rejectMut.error
|
||||
const info = extractReviewError(err)
|
||||
return (
|
||||
<Alert variant="error" title={info.title}>
|
||||
{info.body}
|
||||
</Alert>
|
||||
)}
|
||||
)
|
||||
})()}
|
||||
|
||||
<div className="border-t border-line pt-3 space-y-2">
|
||||
<label
|
||||
|
||||
Reference in New Issue
Block a user