From 27ffd36dcaf204424fb24fdddf2b6cd5c1d215ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=97=D0=B8=D0=BC=D0=B8=D0=BD?= Date: Tue, 12 May 2026 11:48:34 +0000 Subject: [PATCH] feat(webhooks): event-type multi-select + schema draft events (E) --- ordinis-admin-ui/src/i18n.ts | 8 ++-- .../src/routes/webhooks.index.tsx | 42 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/ordinis-admin-ui/src/i18n.ts b/ordinis-admin-ui/src/i18n.ts index b40455c..a634c1d 100644 --- a/ordinis-admin-ui/src/i18n.ts +++ b/ordinis-admin-ui/src/i18n.ts @@ -159,8 +159,8 @@ i18n 'webhooks.field.scopeFilterHint': 'Пусто = все scope', 'webhooks.field.dictionaryFilter': 'Справочники (CSV)', 'webhooks.field.dictionaryFilterHint': 'Через запятую: spacecraft, satellite_type. Пусто = все', - 'webhooks.field.eventTypeFilter': 'Типы событий (CSV)', - 'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Пусто = все', + 'webhooks.field.eventTypeFilter': 'Типы событий', + 'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Пусто = все события.', 'webhooks.field.description': 'Описание', 'webhooks.field.createdBy': 'Создатель', 'webhooks.error.nameUrlRequired': 'Имя и URL обязательны', @@ -799,8 +799,8 @@ i18n 'webhooks.field.scopeFilterHint': 'Empty = all scopes', 'webhooks.field.dictionaryFilter': 'Dictionaries (CSV)', 'webhooks.field.dictionaryFilterHint': 'Comma-separated: spacecraft, satellite_type. Empty = all', - 'webhooks.field.eventTypeFilter': 'Event types (CSV)', - 'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Empty = all', + 'webhooks.field.eventTypeFilter': 'Event types', + 'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Empty = all events.', 'webhooks.field.description': 'Description', 'webhooks.field.createdBy': 'Created by', 'webhooks.error.nameUrlRequired': 'Name and URL are required', diff --git a/ordinis-admin-ui/src/routes/webhooks.index.tsx b/ordinis-admin-ui/src/routes/webhooks.index.tsx index 62591c5..f818318 100644 --- a/ordinis-admin-ui/src/routes/webhooks.index.tsx +++ b/ordinis-admin-ui/src/routes/webhooks.index.tsx @@ -49,6 +49,34 @@ const SCOPE_OPTIONS: SelectOption[] = [ { id: 'RESTRICTED', label: 'RESTRICTED' }, ] +/** + * Known outbox event types в порядке от самых частых (record CRUD) до + * реже встречающихся (schema workflow). Backend emit'ит PascalCase + * (см. DictionaryRecordService, DictionaryDefinitionService, + * SchemaDraftService). При новых событиях — добавлять сюда. + * + *

Pre-v2.4: hint текст в форме фигурировал dot-case формат + * `record.created` — это был docs bug, backend такие не emit'ит. + * Multi-select закрывает дыру и не даёт пользователю ввести неверное + * строковое имя. + */ +const EVENT_TYPE_OPTIONS: SelectOption[] = [ + // Record CRUD + { id: 'RecordCreated', label: 'RecordCreated' }, + { id: 'RecordUpdated', label: 'RecordUpdated' }, + { id: 'RecordDeleted', label: 'RecordDeleted' }, + // Dict definition / schema + { id: 'DefinitionCreated', label: 'DefinitionCreated' }, + { id: 'DefinitionUpdated', label: 'DefinitionUpdated' }, + // Schema workflow (v2.2.0+) + { id: 'SchemaDraftCreated', label: 'SchemaDraftCreated' }, + { id: 'SchemaDraftUpdated', label: 'SchemaDraftUpdated' }, + { id: 'SchemaDraftReviewRequested', label: 'SchemaDraftReviewRequested' }, + { id: 'SchemaDraftDecided', label: 'SchemaDraftDecided' }, + { id: 'SchemaDraftPublished', label: 'SchemaDraftPublished' }, + { id: 'SchemaDraftWithdrawn', label: 'SchemaDraftWithdrawn' }, +] + function WebhooksPage() { const { t } = useTranslation() const { data, isLoading, error } = useWebhookSubscriptions() @@ -206,7 +234,7 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) { const [url, setUrl] = useState('') const [scopeFilter, setScopeFilter] = useState([]) const [dictionaryFilterCsv, setDictionaryFilterCsv] = useState('') - const [eventTypeFilterCsv, setEventTypeFilterCsv] = useState('') + const [eventTypeFilter, setEventTypeFilter] = useState([]) const [description, setDescription] = useState('') const [errorMsg, setErrorMsg] = useState(null) @@ -215,7 +243,7 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) { setUrl('') setScopeFilter([]) setDictionaryFilterCsv('') - setEventTypeFilterCsv('') + setEventTypeFilter([]) setDescription('') setErrorMsg(null) } @@ -243,7 +271,7 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) { url: url.trim(), scopeFilter: scopeFilter.length > 0 ? (scopeFilter as DataScope[]) : undefined, dictionaryFilter: csvToArray(dictionaryFilterCsv), - eventTypeFilter: csvToArray(eventTypeFilterCsv), + eventTypeFilter: eventTypeFilter.length > 0 ? eventTypeFilter : undefined, active: true, description: description.trim() || undefined, } @@ -297,12 +325,12 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) { value={dictionaryFilterCsv} onChange={(e) => setDictionaryFilterCsv(e.target.value)} /> - setEventTypeFilter(ids as string[])} hint={t('webhooks.field.eventTypeFilterHint')} - value={eventTypeFilterCsv} - onChange={(e) => setEventTypeFilterCsv(e.target.value)} />