feat(webhooks): event-type multi-select + schema draft events (E)

This commit is contained in:
Александр Зимин
2026-05-12 11:48:34 +00:00
parent 53ebfc3f9d
commit 27ffd36dca
2 changed files with 39 additions and 11 deletions
+4 -4
View File
@@ -159,8 +159,8 @@ i18n
'webhooks.field.scopeFilterHint': 'Пусто = все scope', 'webhooks.field.scopeFilterHint': 'Пусто = все scope',
'webhooks.field.dictionaryFilter': 'Справочники (CSV)', 'webhooks.field.dictionaryFilter': 'Справочники (CSV)',
'webhooks.field.dictionaryFilterHint': 'Через запятую: spacecraft, satellite_type. Пусто = все', 'webhooks.field.dictionaryFilterHint': 'Через запятую: spacecraft, satellite_type. Пусто = все',
'webhooks.field.eventTypeFilter': 'Типы событий (CSV)', 'webhooks.field.eventTypeFilter': 'Типы событий',
'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Пусто = все', 'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Пусто = все события.',
'webhooks.field.description': 'Описание', 'webhooks.field.description': 'Описание',
'webhooks.field.createdBy': 'Создатель', 'webhooks.field.createdBy': 'Создатель',
'webhooks.error.nameUrlRequired': 'Имя и URL обязательны', 'webhooks.error.nameUrlRequired': 'Имя и URL обязательны',
@@ -799,8 +799,8 @@ i18n
'webhooks.field.scopeFilterHint': 'Empty = all scopes', 'webhooks.field.scopeFilterHint': 'Empty = all scopes',
'webhooks.field.dictionaryFilter': 'Dictionaries (CSV)', 'webhooks.field.dictionaryFilter': 'Dictionaries (CSV)',
'webhooks.field.dictionaryFilterHint': 'Comma-separated: spacecraft, satellite_type. Empty = all', 'webhooks.field.dictionaryFilterHint': 'Comma-separated: spacecraft, satellite_type. Empty = all',
'webhooks.field.eventTypeFilter': 'Event types (CSV)', 'webhooks.field.eventTypeFilter': 'Event types',
'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Empty = all', 'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Empty = all events.',
'webhooks.field.description': 'Description', 'webhooks.field.description': 'Description',
'webhooks.field.createdBy': 'Created by', 'webhooks.field.createdBy': 'Created by',
'webhooks.error.nameUrlRequired': 'Name and URL are required', 'webhooks.error.nameUrlRequired': 'Name and URL are required',
+35 -7
View File
@@ -49,6 +49,34 @@ const SCOPE_OPTIONS: SelectOption[] = [
{ id: 'RESTRICTED', label: 'RESTRICTED' }, { id: 'RESTRICTED', label: 'RESTRICTED' },
] ]
/**
* Known outbox event types в порядке от самых частых (record CRUD) до
* реже встречающихся (schema workflow). Backend emit'ит PascalCase
* (см. DictionaryRecordService, DictionaryDefinitionService,
* SchemaDraftService). При новых событиях — добавлять сюда.
*
* <p>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() { function WebhooksPage() {
const { t } = useTranslation() const { t } = useTranslation()
const { data, isLoading, error } = useWebhookSubscriptions() const { data, isLoading, error } = useWebhookSubscriptions()
@@ -206,7 +234,7 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) {
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
const [scopeFilter, setScopeFilter] = useState<string[]>([]) const [scopeFilter, setScopeFilter] = useState<string[]>([])
const [dictionaryFilterCsv, setDictionaryFilterCsv] = useState('') const [dictionaryFilterCsv, setDictionaryFilterCsv] = useState('')
const [eventTypeFilterCsv, setEventTypeFilterCsv] = useState('') const [eventTypeFilter, setEventTypeFilter] = useState<string[]>([])
const [description, setDescription] = useState('') const [description, setDescription] = useState('')
const [errorMsg, setErrorMsg] = useState<string | null>(null) const [errorMsg, setErrorMsg] = useState<string | null>(null)
@@ -215,7 +243,7 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) {
setUrl('') setUrl('')
setScopeFilter([]) setScopeFilter([])
setDictionaryFilterCsv('') setDictionaryFilterCsv('')
setEventTypeFilterCsv('') setEventTypeFilter([])
setDescription('') setDescription('')
setErrorMsg(null) setErrorMsg(null)
} }
@@ -243,7 +271,7 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) {
url: url.trim(), url: url.trim(),
scopeFilter: scopeFilter.length > 0 ? (scopeFilter as DataScope[]) : undefined, scopeFilter: scopeFilter.length > 0 ? (scopeFilter as DataScope[]) : undefined,
dictionaryFilter: csvToArray(dictionaryFilterCsv), dictionaryFilter: csvToArray(dictionaryFilterCsv),
eventTypeFilter: csvToArray(eventTypeFilterCsv), eventTypeFilter: eventTypeFilter.length > 0 ? eventTypeFilter : undefined,
active: true, active: true,
description: description.trim() || undefined, description: description.trim() || undefined,
} }
@@ -297,12 +325,12 @@ function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) {
value={dictionaryFilterCsv} value={dictionaryFilterCsv}
onChange={(e) => setDictionaryFilterCsv(e.target.value)} onChange={(e) => setDictionaryFilterCsv(e.target.value)}
/> />
<TextInput <MultiSelect
label={t('webhooks.field.eventTypeFilter')} label={t('webhooks.field.eventTypeFilter')}
placeholder="record.created, record.updated" options={EVENT_TYPE_OPTIONS}
value={eventTypeFilter}
onChange={(ids) => setEventTypeFilter(ids as string[])}
hint={t('webhooks.field.eventTypeFilterHint')} hint={t('webhooks.field.eventTypeFilterHint')}
value={eventTypeFilterCsv}
onChange={(e) => setEventTypeFilterCsv(e.target.value)}
/> />
<TextArea <TextArea
label={t('webhooks.field.description')} label={t('webhooks.field.description')}