Skip to content
Open
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
55 changes: 38 additions & 17 deletions docs/developer-docs/6.x/background-tasks/about.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ Tasks allow you to run long operations asynchronously without blocking your appl

To create a background task, you implement the `TaskDefinition.Interface` and register it using `TaskDefinition.createImplementation()`.

<Alert type="info">

Background tasks also power Headless CMS bulk actions: every custom [entries bulk action](/{version}/developer-docs/headless-cms/custom-entries-bulk-action) you register is automatically turned into a pair of background tasks (plus a GraphQL mutation) — so you get batching, resume-on-timeout, and monitoring for free, without defining a `TaskDefinition` yourself.

</Alert>

## Example

```typescript
Expand Down Expand Up @@ -61,16 +67,18 @@ class PublishEntriesBackgroundTask implements TaskDefinition.Interface {

public constructor(
private getModel: GetModelUseCase.Interface,
private publishEntry: PublishEntryUseCase.Interface
private publishEntry: PublishEntryUseCase.Interface,
) {}
/**
* On each task run, this method is called with the input parameters.
* The method must return one of the TaskDefinition.Result types.
*/
public async run({
input,
controller
}: TaskDefinition.RunParams<Input, Output>): Promise<TaskDefinition.Result<Input, Output>> {
controller,
}: TaskDefinition.RunParams<Input, Output>): Promise<
TaskDefinition.Result<Input, Output>
> {
const { model, list } = input;

const modelResult = await this.getModel.execute(model);
Expand All @@ -89,82 +97,95 @@ class PublishEntriesBackgroundTask implements TaskDefinition.Interface {
} else if (controller.runtime.isCloseToTimeout()) {
return controller.response.continue({
...input,
last
last,
});
}

last = entryId;

try {
const publishEntryResult = await this.publishEntry.execute(cmsModel, entryId);
const publishEntryResult = await this.publishEntry.execute(
cmsModel,
entryId,
);
if (publishEntryResult.isFail()) {
results.push({
id: entryId,
error: publishEntryResult.error.message
error: publishEntryResult.error.message,
});
continue;
}
results.push({
id: entryId
id: entryId,
});
} catch (ex) {
results.push({
id: entryId,
error: ex.message
error: ex.message,
});
}
}
return controller.response.done({
list: results
list: results,
});
}
/**
* Before the task is triggered, this method is called.
* Users can do some preparation or checks here and break the execution by throwing an error.
*/
public async onBeforeTrigger(params: TaskDefinition.BeforeTriggerParams<Input>): Promise<void> {
public async onBeforeTrigger(
params: TaskDefinition.BeforeTriggerParams<Input>,
): Promise<void> {
//
}
/**
* When the task ends with a done status, this method is called.
*/
public async onDone(params: TaskDefinition.LifecycleHookParams<Input, Output>): Promise<void> {
public async onDone(
params: TaskDefinition.LifecycleHookParams<Input, Output>,
): Promise<void> {
//
}
/**
* When the task ends with an error status, this method is called.
*/
public async onError(params: TaskDefinition.LifecycleHookParams<Input, Output>): Promise<void> {
public async onError(
params: TaskDefinition.LifecycleHookParams<Input, Output>,
): Promise<void> {
//
}
/**
* When the task ends with an abort status, this method is called.
*/
public async onAbort(params: TaskDefinition.LifecycleHookParams<Input, Output>): Promise<void> {
public async onAbort(
params: TaskDefinition.LifecycleHookParams<Input, Output>,
): Promise<void> {
//
}
/**
* When the maximum number of iterations is reached, this method is called.
*/
public async onMaxIterations(
params: TaskDefinition.LifecycleHookParams<Input, Output>
params: TaskDefinition.LifecycleHookParams<Input, Output>,
): Promise<void> {
//
}
/**
* A method to create input validation schema based on the input interface.
*/
public createInputValidation({ validator }: TaskDefinition.CreateInputValidationParams) {
public createInputValidation({
validator,
}: TaskDefinition.CreateInputValidationParams) {
return validator.object({
model: validator.string().min(1),
list: validator.array(validator.string()).min(1).max(10000)
list: validator.array(validator.string()).min(1).max(10000),
});
}
}

export default TaskDefinition.createImplementation({
implementation: PublishEntriesBackgroundTask,
dependencies: [GetModelUseCase, PublishEntryUseCase]
dependencies: [GetModelUseCase, PublishEntryUseCase],
});
```

Expand Down
22 changes: 22 additions & 0 deletions docs/developer-docs/6.x/headless-cms/ai-summary-bulk-action.ai.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
AI Context: Generate AI Summary Bulk Action (ai-summary-bulk-action.mdx)

## Source of Information

1. wby-next5 `extensions/bulkActions/aiContent/**` — the working "Generate AI summary" demo (API bulk action, dropdown Admin button, websocket handler, extension wiring).
2. Public APIs (Webiny 6.5.0): `EntriesBulkAction` (`webiny/api/cms/entry`), `CmsGenerateEntryContentUseCase` (`webiny/api/ai-powerups`), `GetSettingsFeature` (`webiny/admin/ai-powerups`), `WebsocketEventHandler` (`webiny/admin/websockets`), `WebsocketsSendToIdentityUseCase` + `IdentityContext` (`webiny/api`, `webiny/api/security`).

## Key Documentation Decisions

1. Explicitly a follow-on to custom-entries-bulk-action.mdx — reader is told to read that first. This page only covers the AI-specific deltas.
2. Delegates generation to AI Power Ups `CmsGenerateEntryContentUseCase` rather than a raw AI call — so it follows the editor's configured provider + persona/project. Do NOT show model/key handling.
3. `CmsGenerateEntryContentUseCase.execute(...)` returns `Result` with `value.values` = the generated entry as a structured object (keyed by fieldId). Doc reads `result.value.values.aiSummary` directly, no parse. (This is the reshaped API from webiny-js#5477, targeting release/6.5.0 alongside these docs; serialization now happens only at the transport edge — the task's websocket stream.)
4. Re-runnable convergence via a per-run token: contrast with the discount page's permanent boolean flag. `runId` fresh per click; `processData` stamps `aiSummarizedRun`; `loadData` filters `aiSummarizedRun_not: runId`. Given its own section.
5. GraphQL where nests custom fields under `values: {}`; backend `loadData` flattens to `values.<field>`. Shown on both sides (frontend builds nested, backend flattens).
6. Button is a `DropdownMenu` of AI contexts loaded from `GetSettingsFeature`: Projects, Writer Personas, Reader Personas, plus "Default (no context)". Graceful when AI Power Ups unconfigured (catch → only Default).
7. Websockets: API emits via `WebsocketsSendToIdentityUseCase` keyed to `IdentityContext.getIdentity()`, best-effort (never fail the task). Admin `WebsocketEventHandler` filters by `action` and toasts via `Notifications`. Registered with `createFeature` + `RegisterFeature`.
8. Requires AI Power Ups configured — info callout at top.

## Links Used

- Prereq/base: `/{version}/developer-docs/headless-cms/custom-entries-bulk-action` (+ #making-the-task-converge, #registering-the-bulk-action anchors)
- `/{version}/developer-docs/build-with-ai/ai-assisted-development`
Loading