Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions assets/js/src/sdk/modules/execution-engine/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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)
*/

if (module.hot !== undefined) {
module.hot.accept()
}

export type { ExecutionEngine } from '@Pimcore/modules/execution-engine/services/execution-engine'
export { useExecutionEngine } from '@Pimcore/modules/execution-engine/hooks/use-execution-engine'

export { JobStatus } from '@Pimcore/modules/execution-engine/jobs/abstact-job'
export type { JobInterface, JobRunOptions } from '@Pimcore/modules/execution-engine/jobs/job-interface'

export { MessageBusJobHandler } from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler'
export type {
JobCompletionData,
MessageBusJob,
MessageBusJobHandlerOptions
} from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-handler-types'
export type { JobButtonCustomizationContext } from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/message-bus-job-notification'

export {
PROGRESS_NO_UPDATE,
type ProgressCalculator,
type ProgressCalculatorContext,
type ProgressResult
} from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/progress-calculator/progress-calculator.interface'
export { StepCompletionCalculator } from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/progress-calculator/step-completion-calculator'
export type { StepTracker, StepTrackerState } from '@Pimcore/modules/execution-engine/message-handlers/message-bus-job/step-tracker/step-tracker.interface'

export { JobRehydrationRegistry } from '@Pimcore/modules/execution-engine/services/job-rehydration-registry'
export type { JobRunList, RehydratableJob } from '@Pimcore/modules/execution-engine/services/job-rehydration-registry'
1 change: 1 addition & 0 deletions assets/rsbuild.sdk.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default defineConfig({
'./modules/notifications': './js/src/sdk/modules/notifications/index.ts',
'./modules/perspectives': './js/src/sdk/modules/perspectives/index.ts',
'./modules/global-message-bus': './js/src/sdk/modules/global-message-bus/index.ts',
'./modules/execution-engine': './js/src/sdk/modules/execution-engine/index.ts',
Comment thread
xIrusux marked this conversation as resolved.
'./modules/gdpr-data-extractor': './js/src/sdk/modules/gdpr-data-extractor/index.ts',
'./utils': './js/src/sdk/utils/index.ts',
},
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ This structure ensures that the imports are intuitive and easy to locate within
#### Application Modules

`@pimcore/studio-ui-bundle/modules/*`: The various provided modules and parts of the application, such as the asset editor, data object editor, user management, etc.
For example, `@pimcore/studio-ui-bundle/modules/execution-engine` lets a plugin run a Generic Execution Engine job and show it in the running-jobs panel; see [How to Run a Background Job](../../04_Extending/02_Plugin_Development_Examples/20_Run_a_Background_Job.md).

#### Utils

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: How to Run a Background Job
---

# How to Run a Background Job

## Overview
Start a long-running operation from your plugin, show it in the running-jobs panel with progress, retry and error details, and keep it there across page reloads.

## Details
Pimcore Studio runs long operations through the Generic Execution Engine. The frontend never polls your endpoint: the backend publishes progress and the terminal state over Mercure to the user's private topic, and the `execution-engine` module renders those updates in the jobs panel. Both sides have to follow a small contract.

### Backend
- Start the job with `JobExecutionAgentInterface::startJobExecution($job, $userId, Config::CONTEXT_CONTINUE_ON_ERROR->value)` (or `CONTEXT_STOP_ON_ERROR`). Only runs in one of these two Studio execution contexts, owned by the current user, are listed by the running-jobs endpoint and can be rehydrated after a reload.
- Return the run id from your controller as `201 { "jobRunId": <int> }`.
- Publish progress from your step handlers with `HandlerProgressTrait::updateProgress()`.
- Add a `JobRunStateChangedEvent` subscriber filtered on your job name that publishes `Finished` to `UserTopicServiceInterface::getUserTopic($ownerId)` for `FINISHED`, and calls `EventSubscriberServiceInterface::handleFinishedWithErrors()` for `FINISHED_WITH_ERRORS`. `FAILED` is already published by Studio's `FailureSubscriber` for the two Studio contexts.

### Frontend
- Implement `JobInterface`. Declare `static readonly jobNames` with your backend job name(s) and `static rehydrate(jobRuns)`; both `run()` and `rehydrate()` build the same `MessageBusJobHandler`.
- Register the class in your module's `onInit()` on the `JobRehydrationRegistry` service. `ExecutionEngine.runJob()` throws for a job whose names are not registered.
- Run it with `useExecutionEngine().runJob(new MyJob(...))`. `runJob()` resolves once the request is dispatched and the handler registered, not when the job ends; put completion side effects in `onJobCompletion`.
- Pass `StepCompletionCalculator` as `progressCalculator` when each backend step completes as a unit. For any other progress shape implement `ProgressCalculator`; `calculateProgress()` returns a number (0–100), `null` (indeterminate) or `PROGRESS_NO_UPDATE` (leave the bar as is).

```typescript
import { t } from 'i18next'
import { store } from '@pimcore/studio-ui-bundle/app'
import {
MessageBusJobHandler,
StepCompletionCalculator,
type JobCompletionData,
type JobInterface,
type JobRunList,
type JobRunOptions,
type RehydratableJob
} from '@pimcore/studio-ui-bundle/modules/execution-engine'
import { api } from '../my-api-slice-enhanced'

export class MyJob implements JobInterface {
static readonly jobNames = ['my_bundle_job'] as const

static rehydrate ([jobRun]: JobRunList): MessageBusJobHandler {
return MyJob.buildHandler({ jobRunId: jobRun.id })
}

private static buildHandler (options: { jobRunId: number, onRetry?: () => Promise<void> }): MessageBusJobHandler {
return new MessageBusJobHandler({
jobRunId: options.jobRunId,
title: t('my-bundle.job-title'),
progressCalculator: new StepCompletionCalculator(),
onRetry: options.onRetry,
onJobCompletion: async (data: JobCompletionData): Promise<void> => {
if (data.isFinished) {
store.dispatch(api.util.invalidateTags(['MyData']))
}
}
})
}

async run (options: JobRunOptions): Promise<void> {
const response = await store.dispatch(api.endpoints.myBundleStartJob.initiate({}))
const jobRunId = response.data?.jobRunId

if (jobRunId === undefined) {
return
}

options.messageBus.registerHandler(MyJob.buildHandler({
jobRunId,
onRetry: async () => { await this.run(options) }
}))
}
}

void (MyJob satisfies RehydratableJob)
```

```typescript
// in your module's onInit()
import { container } from '@pimcore/studio-ui-bundle'
import { serviceIds } from '@pimcore/studio-ui-bundle/app'
import { type JobRehydrationRegistry } from '@pimcore/studio-ui-bundle/modules/execution-engine'

container
.get<JobRehydrationRegistry>(serviceIds['ExecutionEngine/JobRehydrationRegistry'])
.register(MyJob)
```

## Code Example on GitHub
> A complete in-repo example is the bulk import job: [bulk-import-job.ts](https://github.com/pimcore/studio-ui-bundle/blob/2026.x/assets/js/src/core/modules/bulk-import/jobs/bulk-import-job.ts), registered in [bulk-import/index.ts](https://github.com/pimcore/studio-ui-bundle/blob/2026.x/assets/js/src/core/modules/bulk-import/index.ts).
3 changes: 2 additions & 1 deletion doc/04_Extending/02_Plugin_Development_Examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Each example covers a specific feature or integration pattern:
- [Customize Tree Icons and Tooltips](./17_Custom_Tree_Icons_and_Tooltips.md)
- [Add a Custom Grid Column](./18_Custom_Grid_Column.md)
- [Extend the Workflow Transition Modal](./19_Extend_Workflow_Transition_Modal.md)
- [Run a Background Job](./20_Run_a_Background_Job.md)

All examples are part of the
Unless a page links elsewhere, the examples are part of the
[Pimcore Studio Example Bundle](https://github.com/pimcore/studio-example-bundle/) on GitHub.
Loading