Skip to content

feat(app-headless-cms): headless (code-based) CMS bulk actions — phase 1 - #5515

Open
adrians5j wants to merge 1 commit into
release/6.5.0from
adrian/cms-bulk-action-headless
Open

feat(app-headless-cms): headless (code-based) CMS bulk actions — phase 1#5515
adrians5j wants to merge 1 commit into
release/6.5.0from
adrian/cms-bulk-action-headless

Conversation

@adrians5j

Copy link
Copy Markdown
Member

What

A code-based ("headless") admin API for Headless CMS bulk actions. Today a custom entry-list bulk action needs ~4 hand-written admin files (button component, websocket handler, createFeature, registration). This adds a CmsBulkAction DI abstraction so the whole thing is one class — implement + createImplementation, mirroring the API-side EntriesBulkAction. No hand-written React.

This is Phase 1 (the non-form path). Phase 2 adds the buildForm (FormModel) dialog path.

API

class ApplyDiscountBulkAction implements CmsBulkAction.Interface<ApplyDiscountData> {
  readonly name = "applyDiscount";
  readonly modelIds = ["product"];
  readonly icon = "discount";                 // IconRegistry key (or a React element)
  button() { return { text: "Apply -10%" }; }
  confirm(ctx) { return { title, message, loadingLabel }; }
  buildData() { return { percent: 10 }; }
  readonly notifications = { "cms.product.discountApplied": d => ({ variant, title, description }) };
}
export default CmsBulkAction.createImplementation({ implementation: ApplyDiscountBulkAction, dependencies: [] });

The framework renders the toolbar BulkActionButton, resolves the icon, computes the selection where (allSelected ? undefined : { id_in }), opens the confirm dialog, triggers BulkActionFeature (action = PascalCased name), deselects, toasts, and generates one WebsocketEventHandler per notifications key.

Changes

  • CmsBulkAction abstraction → exported from webiny/admin/cms/entry/list. Form path (formTitle/buildForm) marked TODO(phase 2).
  • IconRegistry DI feature + registerIcon (string key → @webiny/icons component, tree-shake-friendly; also accepts a raw element) → exported from webiny/admin.
  • Generated toolbar button + registrar (resolves all registered CmsBulkAction impls into ContentEntryListConfig.Browser.BulkAction) + per-notification handler factory.
  • applyDiscount demo migrated: 3 admin files → 1 class (−122 lines).
  • AGENTS.md: no inline object types for params/returns — use a named interface.

Notes / decisions

  • One impl = one toolbar button (single button()), not actions(): Action[].
  • Registrar resolves in a post-commit useEffect (snapshot of resolveAll). Works for synchronously-registered extensions (the norm); lazy/async-registered actions after first commit would be missed — worth revisiting if lazy extensions land.
  • Success ("task started") toast is a framework default — the abstraction has no field for it yet; a startToast? could be added if we want it authored.
  • Admin-only; layered on 6.5.0's public BulkActionFeature/BulkActionButton/useBulkActionDialog. No API-side changes.
  • Both @webiny/app-admin and @webiny/app-headless-cms build green; lint clean.

Follow-ups

  • Phase 2: buildForm(form, ctx) FormModel dialog path (formTitle() for title/submit label; ctx.values from the built form) + migrate the aiContent demo (persona/project select fields).

🤖 Generated with Claude Code

Add a `CmsBulkAction` admin abstraction so a custom entry-list bulk action is
declared as one DI class (implement + createImplementation), mirroring the
API-side `EntriesBulkAction` — no hand-written React button/dialog/handler.

Phase 1 (non-form path):
- `CmsBulkAction` abstraction: name, modelIds, icon, button(), confirm(),
  buildData(), notifications map. Form path (formTitle/buildForm) is TODO(p2).
- `IconRegistry` DI feature + `registerIcon` (string key → @webiny/icons React
  component), exported from `webiny/admin`.
- Framework renders the toolbar button (icon → confirm → trigger → deselect →
  toast) and generates one WebsocketEventHandler per notification key.
- Export `CmsBulkAction` from `webiny/admin/cms/entry/list`.
- Migrate the applyDiscount demo: 3 admin files → 1 class.
- AGENTS.md: no inline object types for params/returns — use a named interface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant