diff --git a/assets/js/src/core/app/config/services/index.ts b/assets/js/src/core/app/config/services/index.ts index 8b903dedcf..a626d81ce3 100644 --- a/assets/js/src/core/app/config/services/index.ts +++ b/assets/js/src/core/app/config/services/index.ts @@ -23,6 +23,7 @@ import { ImageTabManager } from '@Pimcore/modules/asset/editor/types/image/tab-m import { TextTabManager } from '@Pimcore/modules/asset/editor/types/text/tab-manager/text-tab-manager' import { UnknownTabManager } from '@Pimcore/modules/asset/editor/types/unknown/tab-manager/unknown-tab-manager' import { JobComponentRegistry } from '@Pimcore/modules/execution-engine/services/job-component-registry' +import { JobRehydrationRegistry } from '@Pimcore/modules/execution-engine/services/job-rehydration-registry' import { ExecutionEngine } from '@Pimcore/modules/execution-engine/services/execution-engine' import { VideoTabManager } from '@Pimcore/modules/asset/editor/types/video/tab-manager/video-tab-manager' import { ThumbnailService } from '@Pimcore/modules/asset/services/thumbnail-service' @@ -711,6 +712,7 @@ container.bind(serviceIds['DynamicTypes/Grid/Transformers/PHPCode']).to(DynamicT // Execution engine container.bind(serviceIds['ExecutionEngine/JobComponentRegistry']).to(JobComponentRegistry).inSingletonScope() +container.bind(serviceIds['ExecutionEngine/JobRehydrationRegistry']).to(JobRehydrationRegistry).inSingletonScope() container.bind(serviceIds.executionEngine).to(ExecutionEngine).inSingletonScope() // Background processor diff --git a/assets/js/src/core/app/config/services/service-ids.ts b/assets/js/src/core/app/config/services/service-ids.ts index ed7fae6df5..4a103a27a9 100644 --- a/assets/js/src/core/app/config/services/service-ids.ts +++ b/assets/js/src/core/app/config/services/service-ids.ts @@ -379,6 +379,7 @@ export const serviceIds = { // Execution engine 'ExecutionEngine/JobComponentRegistry': 'ExecutionEngine/JobComponentRegistry', + 'ExecutionEngine/JobRehydrationRegistry': 'ExecutionEngine/JobRehydrationRegistry', // Execution Engine executionEngine: 'ExecutionEngine', diff --git a/assets/js/src/core/modules/bulk-import/index.ts b/assets/js/src/core/modules/bulk-import/index.ts index 986546d53f..43ea598084 100644 --- a/assets/js/src/core/modules/bulk-import/index.ts +++ b/assets/js/src/core/modules/bulk-import/index.ts @@ -15,6 +15,9 @@ import { type MainNavRegistry } from '@Pimcore/modules/app/base-layout/main-nav/ import { useBulkImportContext } from '@Pimcore/modules/bulk-import/components/bulk-import-modal/context/bulk-import-context' import { UserPermission } from '@sdk/modules/auth' import { NavPermission } from '@sdk/modules/perspectives' +import { type JobRehydrationRegistry } from '@Pimcore/modules/execution-engine/services/job-rehydration-registry' +import { MessageBusJobHandler } from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler' +import { t } from 'i18next' moduleSystem.registerModule({ onInit: () => { @@ -41,5 +44,15 @@ moduleSystem.registerModule({ } } }) + + const rehydrationRegistry = container.get(serviceIds['ExecutionEngine/JobRehydrationRegistry']) + + rehydrationRegistry.register( + ['studio_ee_job_bulk_import_class_definitions'], + (parent) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.bulk-import-job.title') + }) + ) } }) diff --git a/assets/js/src/core/modules/execution-engine/app-loader/rehydrate-jobs-loader.ts b/assets/js/src/core/modules/execution-engine/app-loader/rehydrate-jobs-loader.ts new file mode 100644 index 0000000000..3c2db312c9 --- /dev/null +++ b/assets/js/src/core/modules/execution-engine/app-loader/rehydrate-jobs-loader.ts @@ -0,0 +1,35 @@ +/** + * This source file is available under the terms of the + * Pimcore Open Core License (POCL) + * Full copyright and license information is available in + * LICENSE.md which is distributed with this source code. + * + * @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com) + * @license Pimcore Open Core License (POCL) + */ + +import { container } from '@Pimcore/app/depency-injection' +import { serviceIds } from '@Pimcore/app/config/services/service-ids' +import { type Loader } from '@Pimcore/modules/app/app-loader/services/app-loader-registry' +import { type ExecutionEngine } from '../services/execution-engine' +import { api } from '../execution-engine-api-slice-enhanced' +import { type JobRun } from '../execution-engine-api-slice.gen' +import { store } from '@Pimcore/app/store' + +export const rehydrateJobsLoader: Loader = { + name: 'rehydrate-running-jobs', + + async onLoad (): Promise { + const { data } = await store.dispatch( + api.endpoints.executionEngineListJobs.initiate( + { body: { filters: { page: 1, pageSize: 100 } } }, + { forceRefetch: true } + ) + ) + const activeStates = ['running', 'not_started'] + const items: JobRun[] = (data?.items ?? []).filter(j => activeStates.includes(j.state)) + + const executionEngine = container.get(serviceIds.executionEngine) + executionEngine.rehydrateRunningJobs(items) + } +} diff --git a/assets/js/src/core/modules/execution-engine/index.ts b/assets/js/src/core/modules/execution-engine/index.ts index a4e8cf8a9e..6cb11a8134 100644 --- a/assets/js/src/core/modules/execution-engine/index.ts +++ b/assets/js/src/core/modules/execution-engine/index.ts @@ -11,14 +11,23 @@ import { container } from '@Pimcore/app/depency-injection' import { moduleSystem, type AbstractModule } from '@Pimcore/app/module-system/module-system' import { type JobComponentRegistry } from './services/job-component-registry' +import { type JobRehydrationRegistry } from './services/job-rehydration-registry' import { serviceIds } from '@Pimcore/app/config/services/service-ids' import { MessageBusJobNotification as MessageBusJobContainer } from './message-handlers/message-bus-job/message-bus-job-notification' +import { registerAllJobRehydrations } from './job-rehydrations' +import { rehydrateJobsLoader } from './app-loader/rehydrate-jobs-loader' +import { type AppLoaderRegistry } from '@Pimcore/modules/app/app-loader/services/app-loader-registry' export const executionEngineModule: AbstractModule = { onInit () { const jobComponentRegistry = container.get(serviceIds['ExecutionEngine/JobComponentRegistry']) - jobComponentRegistry.registerComponent('default-message-bus', MessageBusJobContainer) + + const rehydrationRegistry = container.get(serviceIds['ExecutionEngine/JobRehydrationRegistry']) + registerAllJobRehydrations(rehydrationRegistry) + + const appLoaderRegistry = container.get(serviceIds['AppLoader/Registry']) + appLoaderRegistry.registerLoader(rehydrateJobsLoader) } } diff --git a/assets/js/src/core/modules/execution-engine/job-rehydrations.ts b/assets/js/src/core/modules/execution-engine/job-rehydrations.ts new file mode 100644 index 0000000000..1e381f99c0 --- /dev/null +++ b/assets/js/src/core/modules/execution-engine/job-rehydrations.ts @@ -0,0 +1,200 @@ +/** + * This source file is available under the terms of the + * Pimcore Open Core License (POCL) + * Full copyright and license information is available in + * LICENSE.md which is distributed with this source code. + * + * @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com) + * @license Pimcore Open Core License (POCL) + */ + +import { t } from 'i18next' +import { store } from '@Pimcore/app/store' +import { MessageBusJobHandler } from './message-handlers/message-bus-job/message-bus-job-handler' +import { ChildJobStepTracker } from './message-handlers/message-bus-job/step-tracker/child-job-step-tracker' +import { DefaultStepTracker } from './message-handlers/message-bus-job/step-tracker/default-step-tracker' +import { ProgressFieldCalculator } from './message-handlers/message-bus-job/progress-calculator/progress-field-calculator' +import { StepCompletionCalculator } from './message-handlers/message-bus-job/progress-calculator/step-completion-calculator' +import { type JobRun } from './execution-engine-api-slice.gen' +import { type JobRehydrationRegistry } from './services/job-rehydration-registry' +import { invalidatingTags } from '@Pimcore/app/api/pimcore/tags' +import { api } from '@Pimcore/app/api/pimcore' +import { refreshTreeByElementType } from '@Pimcore/components/element-tree/element-tree-slice' +import { getPrefix } from '@Pimcore/app/api/pimcore/route' +import { type JobButtonCustomizationContext } from './message-handlers/message-bus-job/message-bus-job-notification' + +export function registerAllJobRehydrations (registry: JobRehydrationRegistry): void { + // Single-element delete + registry.register( + ['studio_ee_job_delete_assets', 'studio_ee_job_delete_data_objects', 'studio_ee_job_delete_documents'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.delete-job.title'), + progressCalculator: new StepCompletionCalculator() + }) + ) + + // Batch delete + registry.register( + ['studio_ee_job_batch_delete_assets', 'studio_ee_job_batch_delete_data_objects'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.batch-delete-job.title'), + progressCalculator: new StepCompletionCalculator() + }) + ) + + // Clone (may have child job) + registry.register( + ['studio_ee_job_clone_assets', 'studio_ee_job_clone_data_objects', 'studio_ee_job_clone_documents'], + (parent: JobRun, child?: JobRun) => { + const isChild = child !== undefined + return new MessageBusJobHandler({ + jobRunId: child?.id ?? parent.id, + ancestorJobRunIds: isChild ? [parent.id] : undefined, + title: t('jobs.clone-job.title'), + stepTracker: new ChildJobStepTracker({ startAtStep: isChild ? 2 : 1 }), + progressCalculator: new StepCompletionCalculator() + }) + } + ) + + // Batch edit (patch elements / rewrite references) + registry.register( + ['studio_ee_job_patch_elements', 'studio_ee_job_rewrite_element_references'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.batch-edit-job.title') + }) + ) + + // ZIP upload (two-step: extract → create assets) + registry.register( + ['studio_ee_job_upload_zip_file'], + (parent: JobRun, child?: JobRun) => { + const isChild = child !== undefined + return new MessageBusJobHandler({ + jobRunId: child?.id ?? parent.id, + ancestorJobRunIds: isChild ? [parent.id] : undefined, + title: isChild ? t('jobs.zip-upload-job.step2.title') : t('jobs.zip-upload-job.step1.title'), + stepTracker: new ChildJobStepTracker({ totalSteps: 2, startAtStep: isChild ? 2 : 1 }), + progressCalculator: new ProgressFieldCalculator() + }) + } + ) + + // Download: selected-row CSV/XLSX (no child job) + registry.register( + ['studio_ee_job_create_csv', 'studio_ee_job_create_xlsx'], + (parent: JobRun) => { + const downloadUrl = parent.jobName === 'studio_ee_job_create_csv' + ? `${getPrefix()}/export/download/csv/{jobRunId}` + : `${getPrefix()}/export/download/xlsx/{jobRunId}` + + return new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.download-job.title'), + stepTracker: new DefaultStepTracker(), + progressCalculator: new ProgressFieldCalculator(), + onCustomizeButtons: buildDownloadButton(downloadUrl) + }) + } + ) + + // Download: ZIP (no child job) + registry.register( + ['studio_ee_job_create_download_zip'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.download-job.title'), + stepTracker: new DefaultStepTracker(), + progressCalculator: new ProgressFieldCalculator(), + onCustomizeButtons: buildDownloadButton(`${getPrefix()}/assets/download/zip/{jobRunId}`) + }) + ) + + // Folder export: collect step transitions to a CSV/XLSX child + registry.register( + ['studio_ee_job_collect_folder_export_elements'], + (parent: JobRun, child?: JobRun) => { + const isChild = child !== undefined + const childJobName = child?.jobName ?? '' + let downloadUrl: string | undefined + if (childJobName === 'studio_ee_job_create_csv') { + downloadUrl = `${getPrefix()}/export/download/csv/{jobRunId}` + } else if (childJobName === 'studio_ee_job_create_xlsx') { + downloadUrl = `${getPrefix()}/export/download/xlsx/{jobRunId}` + } + + return new MessageBusJobHandler({ + jobRunId: child?.id ?? parent.id, + ancestorJobRunIds: isChild ? [parent.id] : undefined, + title: t('jobs.download-job.title'), + stepTracker: new ChildJobStepTracker({ totalSteps: 2, startAtStep: isChild ? 2 : 1 }), + progressCalculator: new ProgressFieldCalculator(), + ...(downloadUrl !== undefined && { onCustomizeButtons: buildDownloadButton(downloadUrl) }) + }) + } + ) + + // Recycle bin restore + registry.register( + ['studio_ee_job_recycle_bin_restore'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.recycle-bin-restore-job.title'), + onJobCompletion: async (data) => { + if (data.isFinished) { + store.dispatch(refreshTreeByElementType({ elementTypes: ['asset', 'data-object', 'document'] })) + store.dispatch(api.util.invalidateTags(invalidatingTags.RECYCLING_BIN())) + } + } + }) + ) + + // Recycle bin delete + registry.register( + ['studio_ee_job_recycle_bin_delete'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.recycle-bin-delete-job.title'), + onJobCompletion: async (data) => { + if (data.isFinished) { + store.dispatch(api.util.invalidateTags(invalidatingTags.RECYCLING_BIN())) + } + } + }) + ) + + // Tag assign/replace + registry.register( + ['studio_ee_job_batch_tag_assign', 'studio_ee_job_batch_tag_replace'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.tag-assign-job.title') + }) + ) + + // Search and replace assignments + registry.register( + ['studio_ee_job_element_usage_replace'], + (parent: JobRun) => new MessageBusJobHandler({ + jobRunId: parent.id, + title: t('jobs.search-replace-assignments-job.title') + }) + ) +} + +function buildDownloadButton (downloadUrl: string): (context: JobButtonCustomizationContext) => void { + return (context: JobButtonCustomizationContext) => { + context.addSuccessButton({ + label: t('jobs.job.button-download'), + handler: () => { + const a = document.createElement('a') + a.href = downloadUrl.replace('{jobRunId}', context.jobRunId.toString()) + a.download = '' + a.click() + } + }) + } +} diff --git a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler-types.ts b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler-types.ts index e2317e1a7e..f3c66860ee 100644 --- a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler-types.ts +++ b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler-types.ts @@ -23,6 +23,7 @@ export interface MessageBusJob extends AbstractJob { onCustomizeButtons?: (context: JobButtonCustomizationContext) => void messages?: string[] jobRunId: number + ancestorJobRunIds?: number[] } export interface JobCompletionData { @@ -35,6 +36,7 @@ export interface JobCompletionData { export interface MessageBusJobHandlerOptions { jobRunId: number + ancestorJobRunIds?: number[] title: string | ((job: MessageBusJob) => string) stepDescriptions?: Record stepTracker?: StepTracker diff --git a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler.ts b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler.ts index b6c86260d5..8cbab3ef46 100644 --- a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler.ts +++ b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler.ts @@ -31,6 +31,7 @@ export type { MessageBusJob, JobCompletionData, MessageBusJobHandlerOptions } fr export class MessageBusJobHandler extends AbstractMessageHandler { private jobRunId: number + private ancestorJobRunIds: number[] | undefined private job: MessageBusJob | null = null private readonly onJobCompletion?: (data: JobCompletionData) => void | Promise private readonly onRetry?: () => void | Promise @@ -53,6 +54,7 @@ export class MessageBusJobHandler extends AbstractMessageHandler { constructor (options: MessageBusJobHandlerOptions) { super() this.jobRunId = options.jobRunId + this.ancestorJobRunIds = options.ancestorJobRunIds this.title = options.title this.stepDescriptions = options.stepDescriptions this.stepTracker = options.stepTracker ?? new DefaultStepTracker() @@ -136,7 +138,8 @@ export class MessageBusJobHandler extends AbstractMessageHandler { stepDescriptionKey: this.stepDescriptions?.[currentStep], onRetry: this.onRetry, onCustomizeButtons: this.onCustomizeButtons, - jobRunId: this.jobRunId + jobRunId: this.jobRunId, + ancestorJobRunIds: this.ancestorJobRunIds } job.title = this.getTitle(job) @@ -164,6 +167,7 @@ export class MessageBusJobHandler extends AbstractMessageHandler { private transitionToChildJob (newJobRunId: number): void { const oldJobRunId = this.jobRunId + this.ancestorJobRunIds = [...(this.ancestorJobRunIds ?? []), oldJobRunId] this.jobRunId = newJobRunId @@ -191,7 +195,8 @@ export class MessageBusJobHandler extends AbstractMessageHandler { totalSteps: newState.totalSteps, stepDescriptionKey: this.stepDescriptions?.[newState.currentStep] }), - jobRunId: newJobRunId + jobRunId: newJobRunId, + ancestorJobRunIds: this.ancestorJobRunIds }) } diff --git a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-notification.tsx b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-notification.tsx index c629cadaac..0782b1dc9b 100644 --- a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-notification.tsx +++ b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-notification.tsx @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next' import { isUndefined, isEmpty } from 'lodash' import { type MessageBusJob } from './message-bus-job-handler' import { JobErrorModal } from './job-error-modal' -import { useExecutionEngineAbortJobRunByIdMutation } from '@Pimcore/modules/execution-engine/execution-engine-api-slice-enhanced' +import { useExecutionEngineAbortJobRunByIdMutation, useExecutionEngineHideJobRunsMutation } from '@Pimcore/modules/execution-engine/execution-engine-api-slice-enhanced' import { container } from '@Pimcore/app/depency-injection' import { serviceIds } from '@Pimcore/app/config/services/service-ids' import { type GlobalMessageBus } from '@Pimcore/modules/global-message-bus/services/global-message-bus' @@ -36,6 +36,7 @@ export const MessageBusJobNotification = (props: MessageBusJobProps): React.JSX. const { t } = useTranslation() const [showErrorModal, setShowErrorModal] = useState(false) const [abortJobRun] = useExecutionEngineAbortJobRunByIdMutation() + const [hideJobRuns] = useExecutionEngineHideJobRunsMutation() const handleAbort = async (): Promise => { const { error } = await abortJobRun({ jobRunId: Number(props.jobRunId) }) @@ -53,7 +54,12 @@ export const MessageBusJobNotification = (props: MessageBusJobProps): React.JSX. const hideButtonAction: ButtonAction = { label: t('jobs.job.button-hide'), - handler: () => { removeJob(props.id) } + handler: () => { + const idsToHide = [props.jobRunId, ...(props.ancestorJobRunIds ?? [])] + void hideJobRuns({ body: { jobRunIds: idsToHide } }).finally(() => { + removeJob(props.id) + }) + } } const successButtonActions: ButtonAction[] = [hideButtonAction] diff --git a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/step-tracker/child-job-step-tracker.ts b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/step-tracker/child-job-step-tracker.ts index f2d09f4d6f..3553d3b749 100644 --- a/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/step-tracker/child-job-step-tracker.ts +++ b/assets/js/src/core/modules/execution-engine/message-handlers/message-bus-job/step-tracker/child-job-step-tracker.ts @@ -17,6 +17,11 @@ export interface ChildJobStepTrackerOptions { * the UI will show "Step 1", "Step 2" etc. without a fraction. */ totalSteps?: number + /** + * Initial step to start at. Used during rehydration when the job has + * already transitioned to a child run — pass 2 to skip step 1. + */ + startAtStep?: number } /** @@ -38,7 +43,7 @@ export class ChildJobStepTracker implements StepTracker { private _state: StepTrackerState constructor (options: ChildJobStepTrackerOptions = {}) { - this._state = { currentStep: 1, totalSteps: options.totalSteps } + this._state = { currentStep: options.startAtStep ?? 1, totalSteps: options.totalSteps } } get state (): StepTrackerState { diff --git a/assets/js/src/core/modules/execution-engine/services/execution-engine.ts b/assets/js/src/core/modules/execution-engine/services/execution-engine.ts index ca7c1c64b8..92d4487104 100644 --- a/assets/js/src/core/modules/execution-engine/services/execution-engine.ts +++ b/assets/js/src/core/modules/execution-engine/services/execution-engine.ts @@ -13,22 +13,17 @@ import { serviceIds } from '@Pimcore/app/config/services/service-ids' import { type GlobalMessageBus } from '@Pimcore/modules/global-message-bus' import { type GlobalMessageBusProcess } from '@Pimcore/modules/background-processor' import { type JobInterface } from '../jobs/job-interface' +import { type JobRehydrationRegistry } from './job-rehydration-registry' +import { type JobRun } from '../execution-engine-api-slice.gen' -/** - * Service for executing jobs in the execution engine - * Simply delegates to the job's run method - */ @injectable() export class ExecutionEngine { constructor ( @inject(serviceIds.globalMessageBus) private readonly messageBus: GlobalMessageBus, - @inject(serviceIds.globalMessageBusProcess) private readonly globalProcess: GlobalMessageBusProcess + @inject(serviceIds.globalMessageBusProcess) private readonly globalProcess: GlobalMessageBusProcess, + @inject(serviceIds['ExecutionEngine/JobRehydrationRegistry']) private readonly rehydrationRegistry: JobRehydrationRegistry ) {} - /** - * Run a job using the execution engine - * Delegates all logic to the job itself - */ async runJob (job: JobInterface): Promise { if (!this.globalProcess.isConnected()) { this.globalProcess.start() @@ -36,4 +31,31 @@ export class ExecutionEngine { await job.run({ messageBus: this.messageBus }) } + + rehydrateRunningJobs (items: JobRun[]): void { + if (items.length === 0) return + + if (!this.globalProcess.isConnected()) { + this.globalProcess.start() + } + + // IDs that appear as a child of another item in this response + const childIds = new Set( + items + .filter(j => j.jobRunChildId != null) + .map(j => j.jobRunChildId as number) + ) + + // Process only top-level (parent) items; pass child as second argument + for (const parent of items.filter(j => !childIds.has(j.id))) { + const fn = this.rehydrationRegistry.get(parent.jobName) + if (fn === undefined) continue + + const child = parent.jobRunChildId != null + ? items.find(j => j.id === parent.jobRunChildId) + : undefined + + this.messageBus.registerHandler(fn(parent, child)) + } + } } diff --git a/assets/js/src/core/modules/execution-engine/services/job-rehydration-registry.ts b/assets/js/src/core/modules/execution-engine/services/job-rehydration-registry.ts new file mode 100644 index 0000000000..410c455272 --- /dev/null +++ b/assets/js/src/core/modules/execution-engine/services/job-rehydration-registry.ts @@ -0,0 +1,30 @@ +/** + * This source file is available under the terms of the + * Pimcore Open Core License (POCL) + * Full copyright and license information is available in + * LICENSE.md which is distributed with this source code. + * + * @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com) + * @license Pimcore Open Core License (POCL) + */ + +import { injectable } from 'inversify' +import { type JobRun } from '../execution-engine-api-slice.gen' +import { type MessageBusJobHandler } from '../message-handlers/message-bus-job/message-bus-job-handler' + +export type RehydrationFn = (parent: JobRun, child?: JobRun) => MessageBusJobHandler + +@injectable() +export class JobRehydrationRegistry { + private readonly registry = new Map() + + register (jobNames: string[], fn: RehydrationFn): void { + for (const name of jobNames) { + this.registry.set(name, fn) + } + } + + get (jobName: string): RehydrationFn | undefined { + return this.registry.get(jobName) + } +}