From fbf978aa397b66a309fafb35caa611e3f73164b8 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Wed, 13 May 2026 10:33:02 +0300 Subject: [PATCH] =?UTF-8?q?fix(schema-draft):=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=20display=20+=20gate=20=D0=9E=D1=82=D0=BE=D0=B7=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20mak?= =?UTF-8?q?er'=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Два бага в SchemaDraftDrawer (видны на review pending): 1. АВТОР показывал raw UUID (77ec2cc8-98e3-4d70-8037-...). Резолвим: - Если maker = current user (JWT sub claim) → 'Я • ' - Иначе → short UUID (первые 8 символов) с full в title attribute - Стилизация: 'Я' — text-ink (читаемая), чужой — font-mono text-mute - TODO Phase 1d: backend endpoint для resolve чужих sub → display name 2. Кнопка 'Отозвать' показывалась всем юзерам, не только автору. - Backend всё равно 403'нет non-maker withdraw — но UI confusion: reviewer видел кнопку которая всегда fail'ит. - Gate: показываем только когда auth.user.profile.sub === draft.makerId. - Reviewer теперь видит чистый action row (Approve / Запросить правки / Отклонить), без danger-button мимо контекста. Реальный случай: см. user screenshot на v2.16.x — Reviewer modal с UUID автора и кнопкой 'Отозвать' справа внизу. --- .../components/workflow/SchemaDraftDrawer.tsx | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/ordinis-admin-ui/src/components/workflow/SchemaDraftDrawer.tsx b/ordinis-admin-ui/src/components/workflow/SchemaDraftDrawer.tsx index bb9ff31..abe9ebd 100644 --- a/ordinis-admin-ui/src/components/workflow/SchemaDraftDrawer.tsx +++ b/ordinis-admin-ui/src/components/workflow/SchemaDraftDrawer.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { useAuth } from 'react-oidc-context' import axios from 'axios' import { Badge, Button, Drawer, LoadingBlock, QueryErrorState, TextArea, toast } from '@/ui' import { @@ -241,6 +242,7 @@ export function SchemaDraftDrawer({ inline т.к. Drawer sheet flow-based. */} "). Иначе — short UUID (первые 8 chars) с full + // tooltip. Полное UUID показывать бессмысленно — это Keycloak sub claim, + // нечитаемый для оператора. + // + // TODO Phase 1d: backend endpoint /admin/users/{sub}/display чтобы для + // не-current-user тоже показывать username. Пока — best-effort: current + // user resolved через JWT profile claim, другие — UUID prefix. + const currentSub = auth.user?.profile?.sub + const isMe = currentSub && currentSub === draft.makerId + const myDisplay = + auth.user?.profile?.preferred_username || + auth.user?.profile?.name || + auth.user?.profile?.email + const authorDisplay = isMe + ? `Я${myDisplay ? ` • ${myDisplay}` : ''}` + : draft.makerId.substring(0, 8) return (
- {draft.makerId} + + {authorDisplay} + v{draft.branchedFromVersion} @@ -396,6 +421,7 @@ function DraftActions({ onEdit, disabled, hasCurrentVersion, + makerId, }: { status: SchemaDraftStatus onSubmitReview: () => void @@ -405,13 +431,21 @@ function DraftActions({ onEdit?: () => void disabled: boolean hasCurrentVersion: boolean + /** UUID maker'а draft'а — для gate "Отозвать" (только сам автор может). */ + makerId: string }) { const { t } = useTranslation() + const auth = useAuth() // Phase 1c RBAC stub: для review_pending reviewer-only действия скрываем // если нет approve role. Publish — отдельная role. Backend всё равно // защищает. Phase 1d intergrate'нет Keycloak realm_access.roles. const canReview = useCanReviewSchema() const canPublish = useCanPublishSchema() + // "Отозвать" — maker-only action (withdraw own draft). Backend всё равно + // 403'нет non-maker call, но скрытие на UI level убирает confusion: + // reviewer видел кнопку которая всегда fail'ила. Сравниваем JWT sub с makerId. + const currentSub = auth.user?.profile?.sub + const isMaker = Boolean(currentSub) && currentSub === makerId if (!isActiveSchemaDraft(status)) { return ( @@ -501,16 +535,18 @@ function DraftActions({ {t('workflow.schemaDraft.actions.publish', { defaultValue: 'Опубликовать' })} )} - + {isMaker && ( + + )} ) }