Skip to content

merge queue: checking main (d42bcec) and #36413 together - #36492

Closed
mergify[bot] wants to merge 81 commits into
mainfrom
mergify/merge-queue/cb4b4e9e7a
Closed

merge queue: checking main (d42bcec) and #36413 together#36492
mergify[bot] wants to merge 81 commits into
mainfrom
mergify/merge-queue/cb4b4e9e7a

Conversation

@mergify

@mergify mergify Bot commented Jul 9, 2026

Copy link
Copy Markdown

⏳ The pull request #36413 is queued for merge and currently being checked. ⏳

Branch main (d42bcec) and #36413 are queued together for merge.

This pull request has been created by Mergify to check the mergeability of #36413.
You don't need to do anything. Mergify will close this pull request automatically when it is complete.

Required conditions of queue rule default for merge:

Required conditions to stay in the queue:

---
checking_base_sha: d42bcec250b53f5d17d05ffb6227f703e73c5d50
previous_check_retries: []
previous_failed_batches: []
pull_requests:
  - number: 36413
    scopes: []
scopes: []
...

hmoreras and others added 30 commits June 8, 2026 16:28
…t list modal

First slice of the Publishing Queue Dojo → Angular migration. Lands
the foundation end-to-end so subsequent slices (History tab,
Configure & send, Upload Bundle, kebab actions, Bundle details
modal) layer on the same shell + store + data-access surface.

Frontend
- New Nx library libs/portlets/dot-publishing-queue (shell + page
  + reusable list + toolbar + asset list dialog + SignalStore)
- Path alias @dotcms/portlets/dot-publishing-queue/portlet in
  tsconfig.base.json; route registered in apps/dotcms-ui PORTLETS_ANGULAR
- Top bar: search (300ms debounce), Refresh, Upload Bundle (disabled +
  tooltip), site selector (disabled + tooltip — backend filter pending)
- Queue tab: two-column grid (Ready to Send + In Progress) with counts,
  status chips, skeleton/empty/error states, paginator per column
- Row click on either column opens the Asset list modal (Name/Type/State)
- 40 Jest+Spectator tests, 98.7% coverage

Data access + models
- DotPublishingQueueService at libs/data-access/.../dot-publishing-queue
  covers GET /v1/publishing and GET /bundle/{id}/assets
- New models: PublishingJobView, AssetPreviewView, PublishingJobsResponse,
  PublishAuditStatus enum (mirrors the 18-value Java enum), READY_STATUSES
  / IN_PROGRESS_STATUSES constants, BundleAssetView

Backend wiring
- portlet.xml: existing JSP entry renamed to publishing-queue-legacy;
  new Angular publishing-queue entry (com.dotcms.spring.portlet.PortletController)
  in the Angular Portlets section — admins can roll back without redeploy
  by flipping the two <portlet-class> values
- PortletID enum: PUBLISHING_QUEUE_LEGACY("publishing-queue-legacy")
- Language.properties: publishing-queue-legacy title + 35 new UI keys
- Resource-level requiredPortlet("publishing-queue") gates unchanged —
  the portlet name string is identical, only the class flipped

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n v1 destructive ops

The legacy /bundle/* and /v1/publishqueue destructive ops correctly
required the publishing-queue portlet, but the equivalent v1 ops
(DELETE /v1/publishing/{bundleId}, DELETE /v1/publishing/purge) did
not. A backend user without portlet access could therefore delete
bundles via the v1 endpoints even though the UI was hidden from them.

Adds requiredPortlet("publishing-queue") to both methods, matching
the gating on PublishQueueResource and BundleResource.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e & send, Upload), kebab/Send/Retry, polling

Lands the full Publishing Queue surface on top of the foundation slice:
History tab, all modals, row actions, auto-refresh polling, and the
site filter wiring. Backend security gate (#36045) shipped in a
separate commit on this branch.

History tab (`p-tabs` shell, switched from p-tabView to PrimeNG v20 API)
- Sortable Bundle / Status / Modified columns (three-state sort cycle)
- p-table with selection, bulk select, pagination
- Bulk action bar: Retry Send + Remove (with confirm dialog)
- Sent / Failed chips; row click opens Bundle Details modal

Bundle Details modal
- 9-field metadata definition list (bundle start/end + publish start/end
  via the existing AbstractTimestampsView — #36044 already covered BE-side)
- Endpoints-by-environment table with per-endpoint status chip
- Conditional Download button for completed bundles

Configure & send modal
- Push / Remove / Push+Remove action cards
- Send now / Schedule segmented control with timezone display
- ISO 8601 + timezone-offset date serialization
- Searchable environment dropdown + filter dropdown
- FE maps design operations (push/remove/pushremove) →
  backend PushBundleForm operations (publish/expire/publishexpire)
  per the spike's recommendation; no BE rename required

Upload Bundle dialog
- p-fileUpload basic mode for .tar.gz
- POST /api/bundle/sync (licensed); progress + error surface

Per-row actions
- READY rows: primary Send button + p-menu kebab with
  Configure & send / Generate & download / Remove from queue
- IN PROGRESS failed rows: inline Retry button
- Confirm-remove dialog for destructive actions

SignalStore expansion
- New state: activeTab, historyRows/page/total/status/sort/sortDirection/
  selectedIds, detail*, environments, pushBundleTarget, pushInFlight,
  uploadInFlight/Progress, siteId
- New methods: loadHistory, loadDetail, loadEnvironments,
  openDetail/closeDetail, openConfigureSend/closeConfigureSend/submitPush,
  retryBundles, deleteBundle, deleteBundlesBulk (loops per-id until #36046
  lands), generateBundle, uploadBundle, startPolling/stopPolling,
  setSiteId, setHistoryPage/cycleHistorySort/setHistorySelection
- onInit effect splits queue vs history loads by activeTab; polling
  fires every 15s for IN PROGRESS (paused when document.hidden)

Data-access service
- Adds getPublishingJobDetails, pushBundle, retryBundles, deleteBundle,
  deleteBundles, generateBundle, uploadBundle, getBundleDownloadUrl,
  getEnvironments
- Adds PublishingJobDetailView, EnvironmentDetailView, EndpointDetailView,
  TimestampsView, RetryBundleResultView, PushBundleResultView models

Site filter
- Toolbar now hosts the existing DotSiteSelectorDirective on a p-select
- Site selection flows through store.setSiteId; backend ignores the field
  today, FE is ready to forward it once #36043 expands the filter scope

Tests
- 89 Jest+Spectator tests, all green
- New specs for History, Bundle Details, Configure & Send, Upload, plus
  expanded store + page coverage

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…p, History column rework, hover-reveal copy

Round of design + correctness improvements after testing the slice
locally. Key fixes:

READY TO SEND now hits the correct endpoint
- Switched from GET /v1/publishing?status=WAITING_FOR_PUBLISHING,…
  to GET /api/bundle/getunsendbundles/userid/{userId}. The v1 list
  reads publish_audit (bundles already in the queue), but the design
  expects user-owned drafts which only live in publishing_bundle.
  Confirmed via openapi.json: no v1 endpoint exists for drafts today
  (legacy /bundle/* migration tracked under #36048).
- Store caches the userId via DotCurrentUserService on first load
- PublishingJobView.status widened to PublishAuditStatus | null so the
  same row type can represent drafts (no audit row → no status)

Tab order + default
- History tab is now first and the default open tab
- Queue tab loads lazily on switch (saves the initial double fetch)

History table rework
- Five new columns per dev feedback: Bundle Id (first), Filter,
  Status, Data Entered, Last Update
- Bundle Id renders the full id in monospace with a copy-to-clipboard
  button that fades in on row hover (group-hover/opacity pattern from
  es-search), reuses DotCopyButtonComponent for the canonical
  clipboard + "Copied!" tooltip feedback
- Dates formatted with the DatePipe medium preset

New dot-publishing-status-chip component
- Lives at libs/portlets/dot-publishing-queue/src/lib/components/
  (portlet-local, not promoted to libs/ui yet — only one consumer)
- Mirrors the project standard set by dot-contentlet-status-chip:
  p-chip with bg-{c}-100! text-{c}-700! border-{c}-100! text-xs
- Centralises the 18-status → 4-bucket mapping (success / danger /
  warning / info). Replaces three duplicate severity functions and
  three duplicate constant Sets across list / history / details
- Exports publishingStatusBucket() as a pure fn for direct testing

Empty states standardised
- Replaced the hand-rolled empty-state markup in list + history with
  the canonical DotEmptyContainerComponent (folder icon + bold title
  + lighter subtitle, hideContactUsLink=true). Same pattern that
  dot-query-tool / dot-analytics / dot-velocity-playground use
- Filed #36111 to migrate dot-tags to the same pattern

Site selector dropped
- Removed from the toolbar entirely. Bundles are scoped by owner,
  not by site (confirmed in BE: /v1/publishing has no site param,
  Dojo JSPs never filtered by site). The global admin chrome already
  ships a site selector for everything else

i18n keys backfill
- Audit caught 51 referenced-but-missing keys (tab labels, configure
  & send modal, bundle details, kebab, upload dialog, confirm dialogs,
  generic actions). Diffed grep output for every publishing-queue.*
  reference vs the properties file — 79 referenced, 79 defined, no
  orphans left

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…download bundle dialogs

Replace the custom Configure & Send dialog with the canonical
`DotPushPublishDialogService.open({ assetIdentifier, title, isBundle: true })`
(same service used by templates, containers, content, content types and
pages — mounted globally in `main-legacy.component.html`). Replace
`store.generateBundle` with `DotDownloadBundleDialogService.open(bundleId)`
following the same global-singleton pattern.

Companion store / service / model / i18n cleanup:
- Drop store state and methods: `pushBundleTarget`, `pushInFlight`,
  `environments`, `environmentsStatus`, `loadEnvironments`,
  `openConfigureSend`, `closeConfigureSend`, `submitPush`, `generateBundle`
- Drop service methods: `pushBundle`, `generateBundle`, `getEnvironments`
- Drop `PushBundleResultView` / `PushOperation` / `PushBundlePayload`
- Drop the `publishing-queue.configure-send.*` i18n keys and
  `upload-bundle.coming-soon`
- Drop the configure-send sync effect from the shell
- Delete the custom Configure & Send dialog (component + spec + template)

UX polish that surfaced fixing the wiring (same kebab still drives both
Configure & Send and Generate / Download):
- Make `readyKebabFor` an arrow-function class property (stable reference)
  so the list component's `kebabMenus` memoization works — fixes the
  first-click-only-closes-the-menu thrash in `<p-menu>` reported by users
- Add `showTransitionOptions="0ms"` / `hideTransitionOptions="0ms"` and
  `(mousedown)="$event.stopPropagation()"` on the kebab toggle
- Remove icons from kebab menu items (per user request)
- Memoize per-bundle kebab `MenuItem[]` in `kebabMenus` computed

Asset list dialog gains a hover-revealed per-row delete button that calls
the new `service.removeAssetsFromBundle` + `store.removeBundleAsset`
endpoint, with a fixed `h-96` container plus PrimeNG `[loading]` +
`loadingbody` skeleton template to prevent the dialog from shrinking and
re-expanding while the row reloads. A conditional `<p-iconField>` search
input appears when the bundle has > 10 assets, plus a "no matches" empty
state. The `State` column is removed from both asset tables — the
backend transformer never returns it, so it was always "—".

Bundle Details modal gains an Assets section under Endpoints (per user
ordering preference), with the same fixed-height + skeleton + conditional
search pattern. Lazy-loaded via the new `store.loadDetailAssets` and reset
when the dialog is reused for a different bundle (`detailAssetsStatus`).

Model rename: `BundleAssetView.id` → `asset` to match the backend
transformer's universal key (`BundleResource.java`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tern) + tab-panel padding

Make the History table go flush edge-to-edge, matching the look and feel
of the dot-tags portlet (which we treat as the canonical reference for
data-table portlets). Three small cleanups in one pass since they all
work together to remove the visual padding around the table:

- History container: drop the outer `p-3 gap-2` padded wrapper, drop the
  `rounded-md border bg-white` "card" around the `<p-table>`. The table
  now sits flush against the tab panel on all four sides.

- History bulk-action bar: replace the `<p-toolbar>` wrapper with a thin
  inline strip that only appears when there's a selection (no chrome
  above the table when nothing is selected). Buttons use `size="small"`.

- History skeleton + empty state: move both into the table itself —
  skeletons render inline inside `pTemplate="body"` with `h-17` for a
  uniform row height; the empty state lives in `pTemplate="emptymessage"`
  with a `[pt]` config that sets `width: 100%, height: 100%` so the
  empty container fills the available space (instead of collapsing).

- Shell tab-panels: zero out PrimeNG's default tab-panel padding via
  `[pt]="tabPanelsPt"` / `[pt]="tabPanelPt"` on `<p-tabpanels>` and
  `<p-tabpanel>` (same pattern dot-query-tool uses). Without this, the
  History table inherits a built-in `p-4` from PrimeNG's theme that
  doesn't match the rest of the admin UI.

- Top toolbar: drop the `pi-upload` icon from the Upload Bundle button
  (per user request — label-only matches the rest of the admin UI).

Spec update: `'shows the bulk action bar only when there is a selection'`
no longer checks for the removed `pq-history-bulk-bar` testid (the
toolbar is gone); now checks the conditional bulk-action buttons
(`pq-history-bulk-retry` / `pq-history-bulk-remove`) directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… JSP

Customers shouldn't see different status text in the new Angular portlet vs
the legacy Push Publishing JSP. The status chip was resolving against
`publishing-queue.status.*` (the alt-compact label set: "Sent", "Bundling",
etc.) while the legacy JSP uses `publisher_status_*` ("Success", "Bundle
sent", etc.).

Switch the chip's labelKey to the JSP-matching `publisher_status_*` pattern
and plug the one missing entry: `publisher_status_FAILED_INTEGRITY_CHECK`
(JSP itself had no key for this status; new portlet would have rendered the
raw i18n key).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renders the human-readable bundle name as the leftmost data column (between
the row checkbox and the bundle id), with an em-dash fallback when the name
is null. Column order becomes: ☐ · Bundle Name · Bundle Id · Filter · Status
· Data Entered · Last Update. Empty-state colspan and skeleton row updated
accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pes) + toolbar bulk actions

Mirrors the legacy JSP "Select Bundles to Delete" modal end-to-end. The
History tab now has the same fire-and-forget delete flow as the JSP, with
all four scopes:

  SELECTED · ALL · SUCCESS · FAILED

Endpoint wiring (all async + WebSocket-notified, matching legacy):
- SELECTED → DELETE /api/bundle/ids  body { identifiers: [...] }
- ALL      → DELETE /api/v1/publishing/purge   (BE safe defaults)
- SUCCESS  → DELETE /api/v1/publishing/purge?status=SUCCESS,SUCCESS_WITH_WARNINGS
- FAILED   → DELETE /api/v1/publishing/purge?status=<exact legacy 5>

ALL is gated behind a confirm-dialog ("…cannot be undone") to reproduce the
legacy `confirm()` step. The FAILED status list deliberately excludes
FAILED_INTEGRITY_CHECK / INVALID_TOKEN / LICENSE_REQUIRED to stay 1:1 with
`BundleResource#deleteAllFail` — matching legacy semantics is the priority.

Relocates the bulk action UI from a row below the tabs to the top toolbar:
- "Retry Send" appears only when the history tab has a selection (with an
  N-selected count next to it).
- "Delete Bundles" is visible whenever the history tab has any rows.
- The inline `<p-confirmDialog>` for bulk-remove moves to the shell (the
  dialog is the single overlay owner now).

Service changes (`dot-publishing-queue.service.ts`):
- `deleteBundles(bundleIds)` now hits legacy `/api/bundle/ids` (the
  endpoint the JSP uses) instead of a non-existent v1 path.
- New `purgeBundles(statuses?)` calls `/api/v1/publishing/purge` with
  optional comma-joined status filter.

Store changes (`dot-publishing-queue.store.ts`):
- `deleteBundlesBulk` becomes a single async call (no more per-id
  forkJoin fan-out); clears selection on success.
- New `purgeBundles(statuses?, onDone?)` action.
- Exports `PURGE_SUCCESS_STATUSES` and `PURGE_FAILED_STATUSES` constants
  (the exact lists from legacy `/api/bundle/all/{success,fail}`).

Tests: 152 passing — 11 suites including the new delete-dialog spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ction

Show the Delete Bundles button only when the user has at least one row
checked in the History tab, matching the visibility model of the bulk Retry
Send button (and the "N selected" indicator). Both bulk-action buttons —
plus the count and the separator — now appear and disappear together
behind a single `hasBulkActions` predicate.

The dialog itself still handles the no-selection case defensively (SELECTED
disabled) because it doesn't know how it was opened, but in practice that
branch is no longer reachable from the toolbar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…table + extract assets into View Contents

The bundle details dialog used to render one `<p-table>` per environment
group (with its own column headers), which looked like a duplicated table
when a bundle had multiple endpoints. Flatten to a single table that carries
the environment name as the leftmost column — uniform grid, no subheader
rows. Add a `whitespace-nowrap` on the Status column so long labels like
"Failed to send to all environments" stay on one line.

Endpoint address is now built via `endpointAddress(endpoint)` which returns
`null` when the underlying address is empty — the cell shows "—" instead of
the malformed `://:` the JSP renders. Protocol and port are optional and
omitted from the URL when blank.

Extract the assets section into the existing
`DotPublishingQueueAssetListDialogComponent`, reused as a read-only "View
Contents" surface. The asset-list dialog gains an `allowRemove` flag read
from `DynamicDialogConfig.data` (defaults to true so Queue/Ready callers
keep their edit UX). The shell decides per `activeTab()`: Queue → true,
History → false. The trash column, button, and skeleton cell are hidden
when the flag is false; the empty-state colspan adjusts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…and-drop pattern + inline error

The upload dialog used PrimeNG's `<p-fileUpload mode="basic">` and delegated
errors to the global toast via `DotHttpErrorManagerService.handle()` from
inside the store. That violated the canonical pattern documented in
`libs/portlets/CLAUDE.md` ("Store MUST NOT interact with UI") and offered a
different UX than the other 3 portlet upload dialogs (`dot-tags-import`,
`dot-plugins-upload`, `dot-categories-import`).

Refactor to match those canonical sites:
- `<p-fileUpload mode="advanced">` with a custom drag-and-drop content
  template (icon + dropzone copy + file-types hint)
- Component owns `selectedFile`, `uploading`, and `errorMessage` signals
- Calls `service.uploadBundle(file)` directly; on success → `store.refresh()`
  + close the dialog; on error → set `errorMessage` and stay open so the
  user can correct + retry
- `extractErrorMessage(HttpErrorResponse)` handles the 4 shapes the dotCMS
  BE returns: array body, `{ errors: [...] }`, `{ message: ... }`, plain
  string
- Inline `<p-message severity="error">` at the top of the dialog (full-bleed
  with `-mx-6` + `!rounded-none`) — same look as `dot-tags-import`

Store cleanup: removed `uploadBundle`, `uploadInFlight`, and `uploadProgress`.
The component now owns all upload state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lete confirms

Per-row kebab in the History tab — same pattern as the Queue list's
`readyKebabFor`. Three items: View details · View Contents · separator ·
Delete. Items intentionally text-only (no icons) per design feedback.

- View details → `store.openDetail(bundleId)` (current Bundle Details dialog)
- View Contents → `store.openAssetList(bundleId)` (the same `AssetListDialog`
  the Queue tab uses, but opened read-only via the `allowRemove=false` flag
  introduced in the previous commit)
- Delete → per-row confirm + `store.deleteBundle(bundleId)`

The kebab button uses `<p-button>` (auto-rounded `p-button-icon-only
p-button-rounded` look) and is wrapped in a hover-only `opacity-0
group-hover:opacity-100 focus-within:opacity-100` div so it stays out of
sight until the user mouses over the row. The row click handler still opens
the Details dialog so the dialog and the kebab's "View details" both behave
identically.

Critical fix: `kebabFor(row)` returns a memoized `MenuItem[]` reference
(map keyed by `bundleId`) — `<p-menu [model]="…">` thrashes when it
receives a brand-new array on every CD cycle, causing the well-known
"first click only closes the menu" bug. Mirrors the fix already in
`dot-publishing-queue-list`.

Layout polish:
- Explicit `<th style="width: …">` widths per column + `table-layout: fixed`.
- Switched the table to `width: auto` (via `$ptConfig`) so leftover
  container space stops being distributed across the fixed columns —
  that was leaving big gaps after Bundle Id / Status while squeezing
  Filter to ellipsis.
- `whitespace-nowrap` on Status (chip doesn't wrap "Failed to send to all
  environments" anymore) and on the date columns.

Bundle Id cell:
- Removed `font-mono`, capped to 32 chars in TS (`truncateBundleId`) with
  the full id exposed via `title=`. Standard 26-char ULIDs are unchanged;
  longer ids (custom imports) get "…" suffix.
- Replaced `<dot-copy-button>` with the inline pattern from
  `dot-es-search-copy-identifier`: `<p-button text size="small"
  icon="pi pi-copy">` + `DotClipboardUtil` + `DotGlobalMessageService`.
  The button sits next to the text (via `inline-flex`) and only appears
  on row hover. Click is `stopPropagation`'d so it doesn't fall through
  to the row's `openDetail` handler.

Delete confirms (both per-row in history and the ALL scope in the shell):
- New i18n keys `publishing-queue.delete.confirm.header=Delete` +
  `publishing-queue.delete.confirm.message=Are you sure you want to delete
  "{0}"? This action cannot be undone.`
- Header is "Delete"; accept label is "Delete" (reusing the kebab key).
- Styling: `acceptButtonStyleClass: 'p-button-primary'` (NOT danger/red),
  `rejectButtonStyleClass: 'p-button-text'` (tertiary). Default focus
  stays on reject as a safety measure.
- Toolbar trigger relabeled "Delete Bundles" → "Delete" via the i18n
  value of `publishing-queue.delete-bundles`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ze/weight overrides

Match the site-standard table font (per dot-tags-list) by letting the
default `p-datatable` typography apply uniformly across cells:

- Bundle Name: drop `text-sm font-medium text-color` from the cell wrapper
  (kept the `truncate`).
- Bundle Id: drop `text-xs` from the id span — uses the row default like
  every other column.
- Date columns: drop `text-xs` from the wrapper class (kept
  `text-color-secondary whitespace-nowrap`).

Status chip keeps its own `text-xs` internally (chip convention, owned by
the chip component, not the table cell).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t-1 on Contents search

History table:
- Bundle Id span gets `text-xs` so the ULID reads as a secondary identifier
  (the human-readable Bundle Name in the previous column carries the visual
  weight). Matches the dates' size for visual consistency.
- Date columns (`pq-history-created`, `pq-history-modified`) keep `text-xs` —
  the previous "drop per-cell overrides" commit was too aggressive on these.

Asset list dialog (View Contents):
- `mt-1` on the search bar reserves room for the input's focus ring; without
  it the ring clipped against the dialog header when the input got keyboard
  focus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… filter

Collapse the two-tab UI (Queue/History) into one table that holds both
history and active (in-progress + scheduled) bundles. Status filter chip
in the toolbar lets the user narrow by any subset of statuses; per-row
kebab adapts to the row's status (Retry on failures, Configure & send on
scheduled/active, View details / View Contents / Generate-download /
Delete everywhere).

Bundle details dialog: meta block switches from a two-column dl/dt/dd
grid to a single-column key/value p-table with shaded labels, matching
the design spec.

Drops the legacy getunsendbundles (user-owned drafts) flow — those don't
live in publish_audit and aren't part of the unified view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mpty

Previously the store sent every known `PublishAuditStatus` value when the user
hadn't selected any status chip. Now it omits the param entirely — the BE
treats that as "all statuses." Three benefits:

- Shorter request URLs
- One less thing to keep in sync with the BE enum
- Forward-compatible with new BE statuses (e.g. SCHEDULED, see #36267) —
  they'll appear in the unified table the day the BE ships, no FE deploy
  needed

Removes the local `ALL_BUNDLE_STATUSES` array from the store, makes
`statuses` optional on `ListPublishingJobsParams`, and updates both the store
and service specs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alog

Replaces the single "Upload Bundle" button with an "Add Bundle" dropdown
(chevron) exposing two actions:

  - Select Bundle  → opens a new two-pane picker dialog
  - Upload         → opens the existing upload dialog

The Select Bundle dialog mirrors the legacy "Bundles" tab in modern shape:
left pane lists drafts (sourced from getUnsendBundles), right pane shows
the active draft's contents. Both panes use fixed-layout PrimeNG tables so
content can't push them past the modal width. Action bar at the bottom:
Remove (bulk) · Download · Configure & send. The active bundle row gets a
primary-tinted background + left accent stripe so it's visible at a glance.

Asset name cell links to the right editor route for contentlet rows via
`DotContentletEditUrlService` (new vs legacy decided by the per-content-type
flag, cached). Non-contentlet rows render as plain text — matches what the
legacy JSP did. HTML pages fall through to the contentlet editor URL rather
than the dedicated page editor because `/api/bundle/{id}/assets` doesn't
return `baseType` — documented in a code comment, acceptable for now.

Also corrects `BundleAssetView` field names (`content_type_name`,
`language_code`, `country_code`) to match what `PublishQueueElementTransformer`
actually emits on the wire — they were previously typed as camelCase but
always undefined at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…status column

Per design feedback the legacy `publisher_status_*` labels were too long for
the new chip — e.g. "Failed to send to some environments" (35 chars) wrapped
the chip onto two lines and forced the status column to 16rem.

Switches the chip to portlet-scoped keys `publishing-queue.status.*` so we
can shorten labels without affecting the legacy JSPs that still read
`publisher_status_*`. Each enum value gets a short label:

  All success →  Sent / Saved
  In-flight  →  Bundling / Sending / Publishing / Received
  Pending    →  Pending / Waiting
  Failures   →  Build error / Send error / Publish error
                Failed (all) / Failed (some)
                Integrity / Auth error / No license
  Warnings   →  Sent (warn)

Bundles table: status column width 16rem → 9rem (fits the new longest label
`Publish error` with margin).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… truncation

Two issues in the asset table:

1. The Type chip text was wrapping onto two lines for long content type
   names (e.g. "Language Variable"). PrimeNG `<p-tag>` defaults to
   `white-space: normal`, so a 17-char value inside a 10rem cell broke
   across lines.

2. The Name column wasn't truncating long titles (e.g.
   `com.dotcms.repackage.javax.portlet.title.c_Blogs`) despite the
   `truncate` class. Cause: the `<a>` / `<span>` are flex items, and
   flex items have `min-width: auto` so they expand to fit content.

Fixes:
- Name link/span: add `min-w-0 flex-1` so the flex item can shrink and
  `truncate` kicks in. Full text still available via the existing
  `[title]` tooltip.
- Type chip: `styleClass` with `max-w-full whitespace-nowrap overflow-hidden
  text-ellipsis` + nested `.p-tag-label` truncation. Added `[pTooltip]`
  with the full content type for hover-to-see-full.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…xt menu

- Add Items column to the bundles table (asset count as a gray p-tag),
  move Status to last column before the row kebab.
- Use the bundle name as the asset list dialog title via a dedicated
  header component injected through DynamicDialogConfig.templates.header,
  with truncation past 30 chars and a "{N} item(s)" pill next to it.
- Wrap the asset type column in a gray p-tag for visual consistency.
- Right-clicking a row opens the same actions menu as the kebab via a
  shared p-contextMenu; matchMedia polyfill added to the test setup.
- Tighten the toolbar search placeholder to "Search bundles".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…AD probes

The bundle-detail dialog now mirrors the legacy JSP's file-on-disk gating
for the Download Bundle and Download Manifest buttons. Because
GET /api/v1/publishing/{bundleId} doesn't currently expose hasBundle /
hasManifest, the store fires two HEAD probes on openDetail and only
shows each button once its probe confirms a 200 — replacing the previous
status heuristic (SUCCESS_STATUSES) that didn't account for purged
.tar.gz files or older bundles without a manifest entry.

The probe rationale is documented on probeBundleDownload /
probeBundleManifest in the data-access service, on the new store state
fields, and on the canDownloadBundle / canDownloadManifest computeds in
the dialog — including the path to retire the probes once the BE adds
the flags to the detail response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e for last update

- Bundle id text turns text-red-700 + medium weight when the row is in any
  failure bucket (mirrors the danger color the status chip already uses),
  giving an at-a-glance failure signal even when the Status column scrolls
  off on narrow viewports.
- Data Entered column switches to MM/dd/yyyy hh:mma absolute format.
- Last Update column adopts the project-standard DotRelativeDatePipe from
  @dotcms/ui ("now", "N minutes ago", "N hours ago", "N days ago" up to
  7 days; absolute MM/dd/yyyy hh:mma after that) — same pipe used by
  dot-folder-list-view, edit-content sidebars, and content-compare.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI was failing on `pnpm install --frozen-lockfile` because
`jest-util@^30.0.2` was added to core-web/package.json without a
matching importer entry in pnpm-lock.yaml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The BE introduced PublishAuditStatus.SCHEDULED (#36267) — a synthetic
status for bundles with a future publish_date that haven't yet been
picked up by PublisherQueueJob. The FE was missing it everywhere it
mattered:

- Added SCHEDULED to the PublishAuditStatus TS enum (mirror of the Java
  source of truth) with a doc explaining its synthetic nature.
- Mapped SCHEDULED → 'info' bucket in the status chip, alongside
  BUNDLE_REQUESTED / WAITING_FOR_PUBLISHING (all "queued, not yet
  started" semantics).
- Added publishing-queue.status.SCHEDULED=Scheduled so the chip renders
  a label instead of the raw key.
- Updated the chip's exhaustive-coverage test so it stays exhaustive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…via v1 REST

Wires the multi-bundle "Configure (N)" → "Configure & Send" flow inside the
existing Select Bundle dialog instead of stacking a second modal:

- Adds a step signal ('select' | 'configure') to the dialog and a switch in
  the template. Step 2 embeds the existing DotPushPublishFormComponent (the
  same form the legacy global push-publish dialog uses) so customers see the
  exact field set they know: action / publishDate / expireDate / timezone /
  environment / push filter. Configured once, applied to every selected
  bundle.
- All footer buttons (Remove, Download, Configure) now gate on the checkbox
  selection. Download stays single-target — disabled with a tooltip when
  N != 1.
- Send hits the modern REST endpoint POST /api/v1/publishing/push/{bundleId}
  (PublishingResource.pushBundle, JSON + proper status codes) instead of the
  legacy /DotAjaxDirector/.../cmd/pushBundle AJAX action. Adds
  DotPublishingQueueService.pushBundle and PushBundleForm/PushBundleResultView
  types. Submit fans out one call per checked bundle with the same payload;
  full success closes the dialog (shell refreshes the unified table on
  onClose), partial failure surfaces via DotGlobalMessageService and the
  dialog stays in step 2.
- toPushBundleForm() helper translates the form's DotPushPublishData into
  the v1 shape: renames operation/environments and combines the form's Date
  + selected timezoneId into ISO 8601 with offset (computed via
  Intl.DateTimeFormat so DST is handled correctly).
- DotPushPublishFiltersService is provided at the component level (mirrors
  the legacy DotPushPublishDialogComponent) so the embedded form's
  ngOnInit lookup resolves.
- DotPushPublishFormComponent lives in apps/dotcms-ui; imported via the same
  @nx/enforce-module-boundaries disable already used here for
  DotDownloadBundleDialogService. Track extraction to a shared lib alongside
  the v1 consolidation work (#36048).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hmoreras and others added 22 commits July 3, 2026 17:39
`jest-util` was never imported directly; Jest already provides it
transitively. Removing it and regenerating the lockfile keeps the
resolved version identical (30.3.0) while shrinking package.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Model: convert PublishAuditStatus TS enum to `as const` per TYPESCRIPT_STANDARDS
- Model: rescope READY/IN_PROGRESS_STATUSES to describe status semantics rather than the dropped tab-based UI
- Service: fix misleading JSDoc that claimed no bespoke push endpoint (v1 push does exist here)
- Service: guard `response.body as Blob` cast in generateBundle with a null-body check
- Store: return `assetListStatus: 'error'` (not `'loaded'`) on asset-load failure
- Store: add error handlers to probeBundleDownload/Manifest so download buttons don't stay hidden on sync throws
- Backend: gate retry + push endpoints behind requiredPortlet('publishing-queue') to match the PR description
- Asset-list dialog: replace nested per-asset subscribes with forkJoin + catchError; skip assets without content_type_name (edit-URL cache collided) and fix O(n²) group append
- Select-bundle dialog: surface a real error on push failure via httpErrorManager and drop succeeded ids from the checked set so retry can't re-push already-sent bundles
- Select-bundle dialog: add error handler to getCurrentUser() so a session expiry surfaces instead of leaving the dialog blank
- Select-bundle dialog: log swallowed download-filters error before falling back to empty list
- Upload dialog: set an "invalid file type" message when the picked file isn't .tar.gz/.tgz (previously fell through to "file required")
- Queue-table: reword the misleading sort comment (sort IS supported; the store drives it, not p-table's lazy-load)
- Status-chip: swap `p-chip` + `bg-*-100!` overrides for `p-tag` + `severity` per STYLING_STANDARDS
- Standards: remove default `standalone: true` from all portlet components; mark injected stores as `protected readonly` (private in shell)
- Shell: replace hardcoded `[style]="{ width: '500px' }"` with `styleClass="w-[500px]"`; convert dialog table inline widths to Tailwind classes
- Filter: drop the passthrough `$selectedLabels` computed
- Tests: add uploadBundle + null-body generateBundle coverage; convert `done`-callback tests to synchronous style; align asset-groups spec with the new skip-empty behavior; update status-chip spec for the p-tag swap; align service spec with the READY/IN_PROGRESS resegmentation
…ields

Applies TYPESCRIPT_STANDARDS "Private Properties: Use `#` Prefix" to every
private field in the new publishing-queue code: the service, all six
components (shell, table, toolbar, status-filter, plus the four dialogs) and
the two utility spots (asset-list header, status-chip). Injected services,
subject subjects, dialog refs, and helper methods all move from
`private readonly`/`private` to `#`. `viewChild()` fields stay
`private readonly` because the Angular compiler rejects ES-private on
`viewChild`/`viewChildren`.

Public and template-facing members are unchanged: outputs remain public and
`store` remains `protected readonly` where the template reads it.
Adds the \$ prefix to signals declared via input(), output(), signal(),
computed() and linkedSignal() across the portlet, updating internal refs,
templates and specs to match.

Coverage:
- status-chip: \$status / \$bucket / \$labelKey (input uses alias to keep
  external template binding "status")
- toolbar: \$uploadClick / \$selectBundleClick / \$deleteClick (aliased so
  shell bindings stay unchanged) and \$hasBulkActions
- table: \$first / \$selectedRows (existing \$ptConfig already prefixed)
- asset-list dialog: \$assetSearch / \$assetEditUrls / \$showAssetSearch /
  \$filteredAssets / \$hasNoMatches
- asset-list header: \$itemsCountLabel
- bundle-details dialog: \$metaRows / \$canDownloadBundle /
  \$canDownloadManifest / \$endpointRows
- upload dialog: \$selectedFile / \$uploading / \$errorMessage

Non-signal helpers (constant records, arrow methods, PrimeNG MenuItem
arrays, DynamicDialog refs) are left unprefixed since they aren't signals.
The select-bundle dialog (~30 signals with a large public-API-driven spec)
is out of scope here and is tracked as a follow-up.
`pnpm nx format:check --base=origin/main` was flagging line-length wraps in
these two files. Ran `format:write` to match the project's prettier config.
… access

The `outcomes.filter((o) => !o.ok)` in `onSend` needed a type-predicate to
narrow the array to the `{ ok: false; error }` branch of the union — without
it, the Angular compiler flagged `firstFailure.error` as missing on the
success branch (TS2339). Also dropped the redundant `if (!firstFailure.ok)`
guard the narrowing now makes unnecessary.
- service.spec: migrated from `TestBed.configureTestingModule` to Spectator's
  `createServiceFactory` per TESTING_REVIEW_RULES (O9). All ~30 tests keep
  the same body — only the setup lines change.
- store.spec: added four error-path tests around retryBundles, deleteBundle,
  deleteBundlesBulk and purgeBundles that assert `httpErrorManager.handle`
  fires when the underlying service throws (O16). Each destructive op wraps
  its subscribe in `catchError → handle → EMPTY` — these tests pin that
  contract so a future refactor can't silently drop the error routing.
- queue-table.spec: filled the coverage gaps flagged in review (O18):
  onSelectionChange row→id mapping, truncateBundleId both branches (short
  + long id + empty string), onRowContextMenu preventDefault + row pinning,
  contextMenuItems mirroring kebab items, $ptConfig empty vs populated
  branches. Also strengthened the Configure & Send test to assert the
  `isBundle: true` payload — regressing that quietly routes publishes to
  the wrong endpoint.
- \$ signal prefix: applied to the ~29 signals declared via signal(),
  computed() and linkedSignal() in the component and their internal /
  template / spec references (Group D tail from O11). \`downloadMenuRef\`
  stays \`private readonly\` because the Angular compiler rejects
  \`viewChild()\` on ES-private members.
- Public-API tests (O19): added a \`checkBundles(ids)\` helper that drives
  the checked state through \`onCheckedChange\` the way the p-table emits
  it. Rewired the \`activeBundleId === null\` cases to recreate the
  component with an empty \`getUnsendBundles\` response
  (\`mockReturnValueOnce\` so it's scoped to the current test).
  Rewired \`isDownloading = true\` through a Subject that never resolves.
  Remaining direct \`.set(...)\` calls are test fixtures for state that
  would otherwise require a full HTTP round-trip — each now carries an
  inline comment explaining why the direct write stays.
- Drop schemas (O8): removed \`[CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]\`
  from the createComponentFactory. Replaced the embedded
  \`DotPushPublishFormComponent\` (which lives in apps/dotcms-ui and pulls
  DotcmsConfigService / DotParseHtmlService / PushPublishService) with
  \`MockComponent(DotPushPublishFormComponent)\` via \`overrideComponents\`.
  A misspelled selector, missing import or wrong data-testid now fails a
  test again instead of being silently absorbed.
- data-testid on the send button (O26 tail): rewired the send \`<p-button>\`
  to expose \`data-testid\` on the inner \`<button>\` via
  \`[pt]="{ root: { 'data-testid': ... } }"\`. Spec queries the id directly
  with \`byTestId\` instead of reaching into PrimeNG's DOM with
  \`querySelector('button')\`.
…O26)

Rewired the toolbar's refresh, bulk-delete and bulk-retry \`<p-button>\`
tags to expose \`data-testid\` on the inner \`<button>\` via PrimeNG's
pass-through API (\`[pt]="{ root: { 'data-testid': '...' } }"\`) instead
of on the host component. Specs query the id directly with \`byTestId\`
instead of reaching in with \`?.querySelector('button')\` — the tests
no longer break when PrimeNG changes its wrapper markup.
…mn labels

Removed the `text-red-700` styling on the bundle id for failed rows (and
its `isFailedRow` helper + tests) — the status chip already carries the
failure signal. Renamed the Bundle Name / Bundle Id columns to Name / Id
to drop the redundant prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turns the inline "Download" chevron menu in the Select Bundle dialog into
a third wizard step ("select" → "configure" → "download"). The new step
embeds a shared `<dot-download-bundle-form>` component (Publish/Unpublish
toggle + filter select) that mirrors the legacy download-bundle dialog's
form and emits BE-shaped values back to the parent.

Why: the tiered submenu opened downward from a footer button and
overflowed the enclosing dialog — PrimeNG only flips submenus when they'd
overflow the viewport, so the filter list kept getting clipped. The
step-based flow removes the popup entirely.

- Add `dot-download-bundle-form` (presentational, loads its own filters).
- Extend `$step` to include `'download'`; wire header back-button + footer
  Download submit; call `DotPublishingQueueService.generateBundle` and
  return to the select step on success.
- Drop `p-tieredMenu`, `$downloadMenuItems`, `onDownloadMenuShow/Hide`,
  the trigger/observer refs, and the eager filter fetch in `ngOnInit`.
- Refresh tests: 9 tests for the new step (transition guards, submit,
  no-op paths, isDownloading toggle) + 7 tests for the shared form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ialog

The `<p-tag>` in the Items column now sits inside a transparent button
that opens the asset list (same target as the kebab "View Contents"
action). Clicking the tag stops propagation so the row's default detail
dialog does not also open. When `assetCount` is 0 the tag stays
non-interactive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The requiredPortlet("publishing-queue") additions on the v1 publishing
endpoints are not needed — reverting PublishingResource.java back to
its state on main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…jo untouched

Adopt the new portlet-migration pattern: instead of renaming the Dojo
portlet to '-legacy' and taking over the original id with the Angular
build, leave the Dojo portlet exactly as it is on main and register
the new Angular build as a separate Beta portlet.

- portlet.xml: restore Dojo <portlet-name>publishing-queue</portlet-name>
  / 'Content Publishing Tools'; rename the Angular entry to
  <portlet-name>publishing-queue-beta</portlet-name> /
  'Content Publishing Tools (Beta)'.
- Language.properties: replace publishing-queue-legacy title key with
  publishing-queue-beta = 'Publishing Queue (Beta)'.
- app.routes.ts: route path 'publishing-queue' -> 'publishing-queue-beta'
  so PortletController maps the Beta portletId to the Angular routes.
- SerializationHelperTest: portlet count stays at 55 (still +1 vs main);
  assertion flipped from publishing-queue-legacy -> publishing-queue-beta.

The Beta portlet is registered only; it is not added to any default
layout, so admins can opt in via 'Add Portlet' in Tools. A follow-up
issue will promote it to primary and rename the Dojo one to '-legacy'
once the Beta bake-in is approved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

❌ Issue Linking Required

This PR could not be linked to an issue. All PRs must be linked to an issue for tracking purposes.

How to fix this:

Option 1: Add keyword to PR body (Recommended - auto-removes this comment)
Edit this PR description and add one of these lines:

  • This PR fixes #123 or Fixes: #123

  • This PR closes #123 or Closes: #123

  • This PR resolves #123 or Resolves: #123

  • Other supported keywords: fix, fixed, close, closed, resolve, resolved
    Option 2: Link via GitHub UI (Note: won't clear the failed check)

  1. Go to the PR → Development section (right sidebar)

  2. Click "Link issue" and select an existing issue

  3. Push a new commit or re-run the workflow to clear the failed check
    Option 3: Use branch naming
    Create a new branch with one of these patterns:

  • 123-feature-description (number at start)

  • issue-123-feature-description (issue-number at start)

  • feature-issue-123 (issue-number anywhere)

Why is this required?

Issue linking ensures proper tracking, documentation, and helps maintain project history. It connects your code changes to the problem they solve.---

This comment was automatically generated by the issue linking workflow

@github-actions github-actions Bot added Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code labels Jul 9, 2026
@mergify mergify Bot closed this Jul 9, 2026
@mergify
mergify Bot deleted the mergify/merge-queue/cb4b4e9e7a branch July 9, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants