From 95d43052418187b0923476a6a224f88a126cc85b Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 25 Sep 2026 16:16:53 +0700 Subject: [PATCH] feat(web): show job trigger and module details in jobs list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Колонка «Вид» в журнале задач (Операции → Задачи и Расписание) стала информативнее: подзаголовок с именами обновляемых модулей для tenant_refresh (из meta.module_ids), сообщение apply_summary для deploy_apply и бейдж триггера («по расписанию», «вручную», «после правки данных»). Поиск по журналу учитывает новые поля. Бэкенд: meta.trigger теперь записывается для module_refresh и deploy_apply при ручных запусках (POST /v1/modules/{id}/refresh, /v1/apply, /v1/speakers/{id}/apply), а deploy_apply из цепочки обновления наследует триггер родительской задачи. --- .../operations/operations-jobs-grid.tsx | 41 +++++++++------- .../schedule/schedule-jobs-card.tsx | 8 +++- .../schedule/schedule-jobs-grid.tsx | 33 ++++++++++--- apps/web/src/lib/ui-labels.ts | 48 +++++++++++++++++++ apps/web/src/routes/_auth/schedule.tsx | 2 + internal/httpapi/routes.go | 7 ++- internal/jobs/worker.go | 8 ++-- 7 files changed, 119 insertions(+), 28 deletions(-) diff --git a/apps/web/src/components/operations/operations-jobs-grid.tsx b/apps/web/src/components/operations/operations-jobs-grid.tsx index e8d549a..b84abee 100644 --- a/apps/web/src/components/operations/operations-jobs-grid.tsx +++ b/apps/web/src/components/operations/operations-jobs-grid.tsx @@ -4,6 +4,7 @@ import { ListTodo, SearchIcon } from 'lucide-react' import { toast } from 'sonner' import { Button } from '@evobgp/ui/components/button' +import { Badge } from '@/components/reui/badge' import { DataGridMutedCell, DataGridNameCell } from '@/components/data-grid-cell' import { StatusBadge } from '@/components/status-badge' import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header' @@ -14,7 +15,7 @@ import { type DataGridColumnDef, } from '@/components/reui-kit' import { apiMutate } from '@/lib/api-client' -import { jobKindRu } from '@/lib/ui-labels' +import { jobKindRu, jobSubtitleRu, jobTriggerRu } from '@/lib/ui-labels' import type { JobRow } from '@/types/api' import type { QueryClient } from '@tanstack/react-query' @@ -71,18 +72,25 @@ export function OperationsJobsGrid({ { accessorKey: 'kind', header: ({ column }) => , - cell: ({ row }) => ( - - ), + cell: ({ row }) => { + const trigger = jobTriggerRu(row.original.meta?.trigger) + return ( + + {jobKindRu(row.original.kind)} + {trigger ? ( + + {trigger} + + ) : null} + + } + subtitle={jobSubtitleRu(row.original.kind, row.original.meta, nameById)} + /> + ) + }, meta: { headerTitle: 'Вид' }, }, { @@ -156,10 +164,9 @@ export function OperationsJobsGrid({ onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))} getFilterFieldValue={(item, field) => { if (field !== 'search') return undefined - const moduleName = item.meta?.module_id - ? (nameById.get(String(item.meta.module_id)) ?? String(item.meta.module_id)) - : '' - return `${jobKindRu(item.kind)} ${item.status} ${item.job_id} ${moduleName}` + const subtitle = jobSubtitleRu(item.kind, item.meta, nameById) + const trigger = jobTriggerRu(item.meta?.trigger) + return `${jobKindRu(item.kind)} ${item.status} ${item.job_id} ${subtitle ?? ''} ${trigger ?? ''}` }} columns={columns} data={items} diff --git a/apps/web/src/components/schedule/schedule-jobs-card.tsx b/apps/web/src/components/schedule/schedule-jobs-card.tsx index bc8bb4b..326b58a 100644 --- a/apps/web/src/components/schedule/schedule-jobs-card.tsx +++ b/apps/web/src/components/schedule/schedule-jobs-card.tsx @@ -6,12 +6,14 @@ import { ScheduleJobsGrid } from './schedule-jobs-grid' /** Jobs ResourcePage with status tabs (data-grid-filtering-2). */ export function ScheduleJobsCard({ jobs, + nameById, isLoading, isError, error, onRetry, }: { jobs: JobRow[] + nameById?: Map isLoading: boolean isError: boolean error: unknown @@ -27,7 +29,11 @@ export function ScheduleJobsCard({ onRetry={onRetry} > {(items) => ( - 0} /> + 0} + /> )} ) diff --git a/apps/web/src/components/schedule/schedule-jobs-grid.tsx b/apps/web/src/components/schedule/schedule-jobs-grid.tsx index 377f5bc..6f6c370 100644 --- a/apps/web/src/components/schedule/schedule-jobs-grid.tsx +++ b/apps/web/src/components/schedule/schedule-jobs-grid.tsx @@ -10,7 +10,8 @@ import { createTextFilterQuery, type DataGridColumnDef, } from '@/components/reui-kit' -import { isRefreshJobKind, jobKindRu } from '@/lib/ui-labels' +import { Badge } from '@/components/reui/badge' +import { isRefreshJobKind, jobKindRu, jobSubtitleRu, jobTriggerRu } from '@/lib/ui-labels' import type { JobRow } from '@/types/api' const FAILED_STATUSES = ['failed', 'error', 'cancelled'] @@ -33,9 +34,11 @@ function tabFilter(item: JobRow, tabId: string): boolean { export function ScheduleJobsGrid({ items, + nameById = new Map(), isLoading = false, }: { items: JobRow[] + nameById?: Map isLoading?: boolean }) { const [filterQuery, setFilterQuery] = useState(() => @@ -64,9 +67,25 @@ export function ScheduleJobsGrid({ { accessorKey: 'kind', header: ({ column }) => , - cell: ({ row }) => ( - - ), + cell: ({ row }) => { + const trigger = jobTriggerRu(row.original.meta?.trigger) + return ( + + {jobKindRu(row.original.kind)} + {trigger ? ( + + {trigger} + + ) : null} + + } + subtitle={jobSubtitleRu(row.original.kind, row.original.meta, nameById)} + /> + ) + }, meta: { headerTitle: 'Вид' }, }, { @@ -119,7 +138,7 @@ export function ScheduleJobsGrid({ meta: { headerTitle: 'Ошибка' }, }, ], - [], + [nameById], ) return ( @@ -134,7 +153,9 @@ export function ScheduleJobsGrid({ onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))} getFilterFieldValue={(item, field) => { if (field !== 'search') return undefined - return `${jobKindRu(item.kind)} ${item.status} ${item.error ?? ''} ${item.job_id}` + const subtitle = jobSubtitleRu(item.kind, item.meta, nameById) + const trigger = jobTriggerRu(item.meta?.trigger) + return `${jobKindRu(item.kind)} ${item.status} ${item.error ?? ''} ${item.job_id} ${subtitle ?? ''} ${trigger ?? ''}` }} columns={columns} data={items} diff --git a/apps/web/src/lib/ui-labels.ts b/apps/web/src/lib/ui-labels.ts index 8e62c5a..92904fa 100644 --- a/apps/web/src/lib/ui-labels.ts +++ b/apps/web/src/lib/ui-labels.ts @@ -58,6 +58,54 @@ export function jobKindRu(kind: string): string { return JOB_KIND_RU[kind] ?? kind } +const JOB_TRIGGER_RU: Record = { + scheduler: 'по расписанию', + api: 'вручную', + peer_patch: 'правка пиров', +} + +export function jobTriggerRu(trigger: unknown): string | undefined { + if (typeof trigger !== 'string' || trigger === '') return undefined + const known = JOB_TRIGGER_RU[trigger] + if (known) return known + if (/_(create|patch|delete|import_csv)$/.test(trigger)) return 'после правки данных' + return undefined +} + +export function jobSubtitleRu( + kind: string, + meta: Record | undefined, + nameById: Map, +): string | undefined { + if (!meta) return undefined + switch (kind) { + case 'tenant_refresh': { + const ids = Array.isArray(meta.module_ids) + ? meta.module_ids.map(String).filter((id) => id !== '') + : [] + if (ids.length === 0) return undefined + const names = ids.map((id) => nameById.get(id) ?? id) + if (names.length <= 3) return names.join(', ') + return `${names.slice(0, 2).join(', ')} +${names.length - 2} ещё` + } + case 'module_refresh': { + const id = typeof meta.module_id === 'string' ? meta.module_id : '' + if (id === '') return undefined + return nameById.get(id) ?? id + } + case 'deploy_apply': { + const summary = meta.apply_summary + if (summary && typeof summary === 'object') { + const message = (summary as Record).message + if (typeof message === 'string' && message !== '') return message + } + return undefined + } + default: + return undefined + } +} + const JOB_STATUS_RU: Record = { queued: 'В очереди', running: 'Выполняется', diff --git a/apps/web/src/routes/_auth/schedule.tsx b/apps/web/src/routes/_auth/schedule.tsx index 53e3153..5ce5aab 100644 --- a/apps/web/src/routes/_auth/schedule.tsx +++ b/apps/web/src/routes/_auth/schedule.tsx @@ -11,6 +11,7 @@ import { KpiStatGrid, type KpiStatItem } from '@/components/reui-kit' import { ScheduleJobsCard } from '@/components/schedule/schedule-jobs-card' import { ScheduleModulesGrid } from '@/components/schedule/schedule-modules-grid' import { apiMutate } from '@/lib/api-client' +import { moduleNameById } from '@/queries/overview' import { modulesListQueryOptions } from '@/queries/modules' import { operationsJobsQueryOptions } from '@/queries/operations' @@ -117,6 +118,7 @@ function ScheduleComponent() {