feat(admin-ui): webhook event preview tab в DictionaryEditorDialog

CEO plan v1 polish — закрывает gap "Webhook preview при определении схемы".

Что добавлено:
- New tab "События" (4-й после Метаданные / Поля / JSON) в schema editor.
- Per-event sample JSON envelope для всех 5 event types которые fire'ят
  на dict + records: RecordCreated, RecordUpdated, RecordDeleted,
  DefinitionCreated, DefinitionUpdated.
- Sample data автогенерируется из schema property types:
  string → "sample", integer → 42, number → 3.14, boolean → true,
  array → [<sample>], object → recursive, x-localized → {ru-RU, en-US},
  enum → first value, x-references → "REF-001", date/datetime → ISO.
- Topic name pattern shown: ordinis.{dict}.{scope.lower} для record events,
  ordinis.definitions для definition events.
- Disclaimer что это client-side preview — реальный shape определяется
  ordinis-events-api DTOs.

Helps integrators (Альтум, Геопортал, BI consumers) понять shape события
до того как dict published — без ожидания live event'а.

Files:
- src/components/schema/eventPreview.ts (new, ~155 LOC) — pure helper
- src/components/schema/EventsPreviewTab.tsx (new, ~70 LOC) — UI
- src/components/schema/DictionaryEditorDialog.tsx — +16 LOC, mount tab
- src/i18n.ts — schema.tabs.events + schema.events.{intro,topic,disclaimer}
  RU + EN

Verify:
- pnpm tsc --noEmit: clean
- pnpm test (vitest): 89/89 PASS (4.34s)
- pnpm build: clean
This commit is contained in:
Zimin A.N.
2026-05-08 12:14:35 +03:00
parent 7d2199a3f3
commit 5bc82d2951
4 changed files with 301 additions and 1 deletions
@@ -0,0 +1,78 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Badge, Tabs, type TabItem } from '@nstart/ui'
import { generateEventPreviews } from './eventPreview'
import type { DataScope, JsonSchema } from '@/api/client'
type Props = {
dictionaryName: string
dataScope: DataScope
bundle: string
schema: JsonSchema
businessKeyField?: string
}
/**
* Preview tab inside DictionaryEditorDialog. Показывает sample JSON envelope
* для каждого event type, который Kafka получит после save'а dict / records.
*
* <p>Помогает integrators (Альтум, Геопортал, etc) понять shape события до
* того как dict published. Без этого они вынуждены ждать live event'а
* чтобы посмотреть payload.
*
* <p>Pure client-side preview — не отправляет ничего в Kafka. Sample data
* генерится из schema properties (string → "sample", integer → 42, и т.д.).
*/
export const EventsPreviewTab = ({
dictionaryName,
dataScope,
bundle,
schema,
businessKeyField,
}: Props) => {
const { t } = useTranslation()
const previews = generateEventPreviews(
dictionaryName || 'sample_dict',
dataScope,
bundle || 'cuod',
schema,
businessKeyField,
)
const [active, setActive] = useState(previews[0]?.eventType ?? 'RecordCreated')
const current = previews.find((p) => p.eventType === active) ?? previews[0]
const tabs: TabItem[] = previews.map((p) => ({
id: p.eventType,
label: p.label,
}))
return (
<div className="space-y-3">
<p className="text-sm text-carbon/70">
{t('schema.events.intro')}
</p>
<Tabs items={tabs} value={active} onValueChange={setActive} />
{current && (
<div className="space-y-2">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="info">{current.eventType}</Badge>
<span className="text-2xs text-carbon/70">
{t('schema.events.topic')}:
</span>
<code className="text-2xs font-mono text-ultramarain">
{current.topic}
</code>
</div>
<p className="text-2xs text-carbon/60">{current.description}</p>
<pre className="bg-regolith/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
{JSON.stringify(current.envelope, null, 2)}
</pre>
<p className="text-2xs text-carbon/50">
{t('schema.events.disclaimer')}
</p>
</div>
)}
</div>
)
}