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
@@ -19,6 +19,7 @@ import { useCreateDictionary, useUpdateDictionary } from '@/api/mutations'
import { dictionaryDetailQuery, useDictionaries } from '@/api/queries'
import type { CreateDictionaryRequest, DataScope, DictionaryDetail } from '@/api/client'
import { SchemaBuilder } from './SchemaBuilder'
import { EventsPreviewTab } from './EventsPreviewTab'
import {
buildSchemaJson,
diffSchemas,
@@ -48,7 +49,7 @@ const SCOPE_OPTIONS = (t: (k: string) => string): SelectOption[] => [
{ id: 'RESTRICTED', label: t('scope.RESTRICTED') },
]
type EditorTab = 'metadata' | 'schema' | 'preview'
type EditorTab = 'metadata' | 'schema' | 'preview' | 'events'
export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props) => {
const { t } = useTranslation()
@@ -172,6 +173,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
{ id: 'metadata', label: t('schema.tabs.metadata') },
{ id: 'schema', label: t('schema.tabs.schema') },
{ id: 'preview', label: t('schema.tabs.preview') },
{ id: 'events', label: t('schema.tabs.events') },
]
return (
@@ -297,6 +299,18 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
</pre>
</div>
<div className={activeTab === 'events' ? 'block' : 'hidden'}>
{/* Phase 4.5 / CEO plan v1: integrators видят shape Kafka events
до published'а dictionary. Sample data на основе schema fields. */}
<EventsPreviewTab
dictionaryName={name}
dataScope={scope}
bundle={bundle}
schema={schemaJson}
businessKeyField={idSource || undefined}
/>
</div>
{isEdit && breaking && (
<Alert variant="error" title={t('schema.diff.blockedTitle')}>
<div className="space-y-2">