fix(qa-report): version tag, /drafts redirect, audit filter complete, i18n cleanup

QA report v2.31.x batch:

- **VersionController.tag**: было 'none' (maven build на main без git tag) → UI
  показывал «dev · 6077d4df». Helm передаёт image.tag через env
  ORDINIS_BUILD_IMAGE_TAG, controller парсит «v2-31-2-e3c82775» → «v2.31.2».
  Helm chart change в ordinis-infra (separate MR).

- **/drafts redirect** (BUG-003): пустой 404 «Not Found» plain text → теперь
  static redirect на /my-drafts.

- **Audit action filter** (BUG-004): дропдаун содержал только 3 значения
  (CREATE/UPDATE/CLOSE), но в таблице 11 типов (DRAFT_*, DEFINITION_*).
  Frontend AuditAction enum + ACTIONS list синхронизированы с backend
  AuditLogger/SchemaDraftService/DraftService.

- **i18n cleanup** (BUG-006/007/UX-005):
  - myDrafts.description: «Все ваши submissions с pending/approved...» →
    «Все ваши черновики с историей: ожидает рассмотрения, одобрен...»
  - reviews.description: «Pending drafts от makers, ждут approve/reject» →
    «Черновики от авторов, ждут одобрения или отклонения. Общая очередь...»
  - reviews.queueTotal: «N pending drafts» → «N черновиков в очереди»
  - myDrafts.col.reviewer: «Reviewer» → «Рецензент» (RU only)
  - notifications chip labels: 'submit/approved/rejected' → 'отправлен/
    одобрен/отклонён'
  - audit.action.* expanded к 13 entries для каждого backend action
    type, с понятными лейблами «Черновик одобрен» вместо «DRAFT_APPROVE»

- **WhatsNewModal markdown** (UX-006): тексты вроде «**График доставки
  webhook'ов**» рендерились с буквальными звёздочками. Mini inline renderer
  для **bold**, *italic*, `code` — без full markdown library.
This commit is contained in:
Zimin A.N.
2026-05-15 22:02:29 +03:00
parent e3c827752e
commit ff7e72e0fd
8 changed files with 201 additions and 28 deletions
@@ -220,17 +220,21 @@ function badgeVariant(
* Event type → short russian label для chip. Server-side resolution лучше,
* но пока mapping inline здесь — backend payloadPreview уже несёт полный
* человеческий текст, chip это просто категория.
*
* <p>UX-005 fix: были английские labels (submit/approved/rejected) — chip
* читается носителями русского как «Draft approved 15.05.2026», что выглядит
* как baked-in EN текст. Теперь короткие русские.
*/
function humanEventType(eventType: string): string {
const map: Record<string, string> = {
RecordDraftSubmitted: 'submit',
RecordDraftApproved: 'approved',
RecordDraftRejected: 'rejected',
RecordDraftWithdrawn: 'withdrawn',
SchemaDraftSubmitted: 'schema submit',
SchemaDraftApproved: 'schema approved',
SchemaDraftRejected: 'schema rejected',
SchemaDraftPublished: 'schema published',
RecordDraftSubmitted: 'отправлен',
RecordDraftApproved: 'одобрен',
RecordDraftRejected: 'отклонён',
RecordDraftWithdrawn: 'отозван',
SchemaDraftSubmitted: 'схема: отправлена',
SchemaDraftApproved: 'схема: одобрена',
SchemaDraftRejected: 'схема: отклонена',
SchemaDraftPublished: 'схема: опубликована',
}
return map[eventType] ?? eventType
}
@@ -1,3 +1,4 @@
import type * as React from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SparkleIcon } from '@phosphor-icons/react'
@@ -136,6 +137,32 @@ function EntryCard({ entry, isUnread }: { entry: ChangelogEntry; isUnread: boole
)
}
/**
* Mini inline-markdown renderer для changelog описаний. UX-006: тексты
* вроде «**График доставки webhook'ов**» рендерились с буквальными
* звёздочками. Pulling full markdown library overkill для трёх правил.
*
* <p>Поддерживает: {@code **bold**}, {@code *italic*}, {@code `code`}.
* Stops parsing на первом неподдержанном паттерне — текст остаётся как есть.
*/
function renderInlineMarkdown(text: string): React.ReactNode {
const parts: React.ReactNode[] = []
// Pattern matches **bold**, `code`, или *italic* (lazy, non-greedy).
const pattern = /(\*\*([^*\n]+?)\*\*|`([^`\n]+?)`|\*([^*\n]+?)\*)/g
let lastIndex = 0
let key = 0
let m: RegExpExecArray | null
while ((m = pattern.exec(text)) !== null) {
if (m.index > lastIndex) parts.push(text.slice(lastIndex, m.index))
if (m[2] !== undefined) parts.push(<strong key={key++}>{m[2]}</strong>)
else if (m[3] !== undefined) parts.push(<code key={key++} className="text-mono text-cap bg-surface-2 px-1 rounded-sm">{m[3]}</code>)
else if (m[4] !== undefined) parts.push(<em key={key++}>{m[4]}</em>)
lastIndex = pattern.lastIndex
}
if (lastIndex < text.length) parts.push(text.slice(lastIndex))
return parts.length > 0 ? parts : text
}
function SectionBlock({ section }: { section: ChangelogSection }) {
return (
<div>
@@ -151,7 +178,7 @@ function SectionBlock({ section }: { section: ChangelogSection }) {
{item.scope && (
<span className="text-mono text-cap text-accent mr-1.5">{item.scope}:</span>
)}
<span>{item.description}</span>
<span>{renderInlineMarkdown(item.description)}</span>
{item.commit && item.commitUrl && (
<a
href={item.commitUrl}