feat(publishing-queue): Angular portlet + backend wiring#36413
feat(publishing-queue): Angular portlet + backend wiring#36413hmoreras wants to merge 48 commits into
Conversation
…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>
…paginator
The rows-per-page dropdown was a no-op: onLazyLoad never persisted
event.rows to the store, so the next fetch still went out with the
stale size. The store's reactive effect also did not track rowsPerPage,
so even a direct patch would not have refetched.
- Add setRowsPerPage(rows) to the store. It snaps bundlesPage back to 1
on a size change so the user does not land on an out-of-range page.
- Track store.rowsPerPage() in the withHooks effect so a size change
triggers loadBundles automatically (same way bundlesPage already does).
- onLazyLoad now routes a size change through setRowsPerPage when
event.rows differs from the store, and through setBundlesPage on a
page-only change. Without that split, PrimeNG's combined "page +
rows" event was discarded entirely.
Paginator chrome now matches dot-folder-list-view (content-drive):
showFirstLastIcon=false, showPageLinks=false, showCurrentPageReport=true
with a "Page {currentPage}" template. rowsPerPageOptions becomes
[20, 40, 60] (was [10, 25, 50]) and the store default rowsPerPage
bumps from 10 to 20 to keep the dropdown's selected value valid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lash The 15-second auto-refresh routed through the same loadBundles() path as user-initiated reloads, which sets bundlesStatus to 'loading'. That flipped the template into the skeleton branch on every tick and produced a visible flicker between the loading state and the actual rows. Background polls now run in a "silent" mode: keep the existing rows on screen, only patch in the new data when the response arrives. User actions (search, filter, page, sort, post-action refresh) still take the loud path so the user gets skeleton feedback that the system is responding to their click. Silent polls also swallow transient errors instead of flipping the table into the red error state — the next tick retries. Loud loads still surface errors via DotHttpErrorManagerService as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…abel The toolbar status filter hardcoded its own list of 18 statuses instead of deriving from the PublishAuditStatus enum. Two visible bugs: - SCHEDULED was missing. When the synthetic status was added to the enum + chip in d1ee134, the filter array was not updated, so users could not filter for future-dated bundles. - Multiple enum codes share the same translated label (SUCCESS and BUNDLE_SENT_SUCCESSFULLY both render as "Sent"; BUNDLE_SAVED_SUCCESSFULLY as "Saved"; etc.). The dropdown showed them as separate rows, which read as duplicates. Rewires the filter to: - Use STATUS_ORDER as the explicit display order, asserted by a new "covers every value of PublishAuditStatus" test so future enum additions force a placement decision instead of silently disappearing. - Group options by translated label and store the underlying codes on each option. Picking "Sent" sends the union { SUCCESS, BUNDLE_SENT_SUCCESSFULLY } to the BE; the option only renders as selected when ALL of its grouped codes are present in the store filter. - Add explicit regression tests: SCHEDULED is present, "Sent" appears once and groups its two codes, partial-state grouped options are not shown as selected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the Select Bundle dialog's Download button — which used to pop
the global DotDownloadBundleDialogComponent on top of the current modal
— with an inline tiered menu that opens upward from the button:
Download ▾
├── To Publish ›
│ ├── Content and Relationships
│ ├── Content, Assets and Pages
│ ├── Everything and Dependencies
│ ├── Force Push Everything
│ └── Only Selected Items
└── To Unpublish
Picking a leaf fires the same backend the legacy modal fires — exact
same payload, same .tar.gz — without the second modal layer.
- DotPublishingQueueService.generateBundle(id, op, filterKey) POSTs to
/api/bundle/_generate with the legacy {'0' | '1'} operation vocabulary
the endpoint expects, observes the response as a blob, and parses the
filename from content-disposition. Uses HttpClient (not raw fetch) so
the project's auth + error interceptors apply.
- Select Bundle dialog drops DotDownloadBundleDialogService and gains a
downloadFilters signal (loaded once in ngOnInit via DotPushPublish-
FiltersService), an isDownloading signal, and a downloadMenuItems
computed that builds the 2-level MenuItem array. To Unpublish is a
leaf (legacy modal disables the filter dropdown for unpublish anyway).
- PrimeNG v21 has no placement prop on TieredMenu/SplitButton/Menu/
Popover, and its DomHandler.absolutePosition measures the viewport,
not the dialog container, so auto-flip never triggers for a footer-
positioned trigger. onShow event hook reads the trigger rect via
getBoundingClientRect and resets the overlay's top so the menu opens
above the button. Smallest possible override that still uses PrimeNG's
public event API.
- DotPushPublishFiltersService removed from the component's providers
array — it's providedIn:'root' and stateless, the per-instance copy
was a misread of the legacy DotPushPublishDialogComponent pattern.
DotDownloadBundleDialogService + DotDownloadBundleDialogComponent are
untouched — the global modal remains the entry point for every other
caller in the admin UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… the link The asset list inside the Select Bundle dialog rendered each name as an underlined primary-coloured anchor when an edit URL was resolved. That gave the row two click targets (anchor + delete) with no whole-row hover affordance. - Asset name renders as a plain <span>, no anchor, no underline. - The row itself is clickable when DotContentletEditUrlService resolves an edit URL for the asset; clicking opens that URL in a new tab (window.open with target=_blank, noopener) — same context the prior anchor used, so the dialog stays open. - cursor-pointer only when the row has a resolved URL — rows without one (templates, languages, etc.) don't pretend to be clickable. - [rowHover]="true" on the asset p-table for PrimeNG-native row hover bg across the whole table (consistent visual feedback regardless of click affordance). - Trash cell gets pr-3 (12px) right padding so the icon doesn't touch the dialog edge, and (click)="$event.stopPropagation()" so deleting doesn't also navigate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…'t support it The Data Entered / Last Update / Status column headers carried pSortableColumn + <p-sortIcon> directives that emitted lazy-load events with a sortField. The v1 endpoint backing the table (GET /api/v1/publishing) does not accept a sort query param — PublishAuditAPIImpl returns rows in a hardcoded "ORDER BY status_updated DESC" (see SELECT_ALL_ORDER_BY_STATUSUPDATED_DESC at PublishAuditAPIImpl.java:49). So every click on those headers fired a useless network round-trip and the visible order never changed. - Remove pSortableColumn + <p-sortIcon> from the three previously- sortable columns. Headers render as plain text. - Remove the now-unreachable event.sortField branch from onLazyLoad. - Drop the unused PublishingSortField import. Code comments in the template and the handler point at the BE constraint so future devs know exactly where to re-enable when the backend gains the sort param. The store's bundlesSort / bundlesSortDirection state and cycleBundlesSort action are kept intentionally — they'll wire back up cleanly once the BE supports sort. Removing them now would balloon into the service signature, the spec, and several call sites with no immediate benefit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Select Bundle dialog's Remove / Download / Configure buttons used to render as disabled while the user had nothing checked. The buttons were the most obvious target for new users, so "disabled with no explanation" was an information gap. - The three buttons stay enabled at all times (Download still disables itself while a download is in flight, to prevent double-fire). Click handlers pre-validate the selection: empty → warn, multi for Download → warn, otherwise proceed. - New validationWarningKey signal holds the i18n key of the current warning; the footer renders an inline red message with a triangle icon on the left when the key is set. - onCheckedChange clears the warning automatically so the user gets immediate feedback when they take a corrective action. - Two warning strings: "Select at least one bundle first." (Remove, Configure, Download with N=0) and the existing "Select a single bundle to download." (Download with N>1, repurposed from the old tooltip). - Download's old `[pTooltip]` is gone — the inline warning replaces it and is more discoverable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a "Scheduled for" row to the bundle details dialog's metadata table that
renders only when the bundle is in SCHEDULED status. The BE already returns
`scheduledPublishDate` on `GET /api/v1/publishing/{bundleId}` and leaves it
null for every other status, so wiring the FE model + a conditional row is
all that's needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user checks one or more rows, Add Bundle is replaced by Retry Send as the toolbar's primary action (no icon). Refresh stays in place and Remove moves in as a tertiary danger-text button on the left of Refresh. The "N selected" label is dropped — the checked rows + active primary read the selection just fine. Renames the bulk button label from "Delete" to "Remove" to match the kebab-menu vocabulary used elsewhere in the portlet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onfirm The old "Select Bundles to Delete" dialog exposed SELECTED / ALL / SUCCESS / FAILED scopes as buttons. That's redundant now that the toolbar's Remove button is selection-gated and the status filter + search let the user shape the working set before selecting. Drop the dialog and replace it with a ConfirmDialog on the shell (same pattern as dot-tags): "Are you sure you want to remove N bundle(s)? Bundles are removed in the background — this action cannot be undone." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…HEDULED Cluster of small UX fixes for the "View Contents" dialog: - Row click opens the contentlet in a new tab (same DotContentletEditUrlService pattern as Select Bundle). Non-contentlet rows stay non-clickable. - Row hover uses PrimeNG's rowHover; cursor-pointer only turns on for rows that have a resolved edit URL, so users see which rows are linkable. - Footer Close button wired to DynamicDialogRef.close. - Trash button used focus:opacity-100, which kept the icon visible on the last-clicked row after mouse-out. Switch to focus-visible so mouse clicks don't leave it stuck on; keyboard tab still surfaces it for a11y. - Extra vertical spacing so the search input doesn't crowd the table. - SCHEDULED bundles are queued but not in-flight, so the shell now includes them in EDITABLE_ASSET_STATUSES — users can prune assets before the cron picks the bundle up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sh icon The delete icon was flush with the dialog's right edge because the trash column was 3.5rem wide with only pr-3 of padding on the cell. Widen the column to 5rem and bump the padding to pr-5 so the icon sits away from the dialog border. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s dialog Two changes to the Bundle Details and View Contents dialogs: - Add a primary Close button to the Bundle Details footer (previously the only exit was the dialog's X). View Contents already had a text Close button; promoted it to primary to match. Both dialogs now expose the same terminal action. - Swap the timestamp cells in the details dialog from Angular's 'medium' format to `MMM d, y - h:mm:ss a` (e.g. "Jun 25, 2026 - 5:57:02 PM") for consistency with the design spec — same format now applies to Scheduled for / Bundle start-end / Publish start-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lumns Three related polish items on the main bundles table: - Unify column font size — drop text-xs from Bundle Id, Date Entered, and Last Update so all columns read at the same weight instead of the id + dates being visually demoted. - Adopt the new date format `MMM d, y - h:mm:ss a` on both Date Entered and the fallback in the Last Update dotRelativeDate call (the relative "N days ago" behavior is unchanged; it just uses the new format when the diff crosses the relative window). - Freeze the checkbox column (left) and the kebab-actions column (right) via pFrozenColumn. On narrow viewports the middle columns scroll horizontally while bulk-selection and per-row actions remain reachable. On wide screens PrimeNG doesn't render the sticky shadow, so no visual regression on desktop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The trash button on each asset row was persistently visible, competing with the row content for attention. Match the pattern already used by the View Contents dialog: opacity-0 by default, group-hover:opacity-100 when the row is hovered, focus-visible:opacity-100 so keyboard tabbing still surfaces it for a11y. The row's <tr> gets `group` to scope the hover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er, send) Cluster of related UX improvements to the Add Bundle wizard: - Pagination: drop BUNDLES_PER_PAGE from 10 to 6 so the list fits without scroll. The BE endpoint reports numRows = current page's row count (not the absolute total), so switch from numeric maxPage to cursor-style bundlesHasMore (true while pages come back full, false on partial). Also detect empty responses past page 1 and roll back to the previous page so users never land on a spurious "No bundles found" page after a Next click on a total that's an exact multiple of 6. - Dialog header: hide PrimeNG's built-in title (showHeader: false) and render a custom header inside the dialog body. When step === 'select' it reads "Select Bundle"; when step === 'configure' it swaps to "← Configure & Send [N bundle(s)]" — same content that used to live in a separate configure-header bar inside the body, now unified with the dialog chrome. - Send button behavior: no longer disabled by form validity. Clicking with an invalid or empty form surfaces the appropriate inline warning (form-invalid or select-one) via the existing validationWarningKey pattern. Only disabled while an upload is in-flight to guard against double-submit. - Add an "Failed bundles retry automatically up to 3×" hint row above the configure footer so users understand the retry policy before hitting Send. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…inline warning Match the pattern just landed on Select Bundle's Send button: keep the Upload button clickable regardless of whether a file is selected, and surface the missing-file case as an inline error message instead of silently no-oping on the disabled button. The button only disables while an upload is in-flight, to guard against double-submit. Reuses the existing errorMessage signal + p-message channel that already handled BE errors — server errors and client validation share the same visual affordance. Auto-clears when the user picks a valid bundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @hmoreras's task in 1m 10s —— View job Rollback-Safety Analysis
Verdict: ✅ Safe to Rollback The backend-touching changes in this PR are limited to:
No Label |
🤖 dotBot Review (Bedrock)Reviewed 21 file(s); 12 candidate(s) → 10 confirmed, 1 uncertain (unverified, kept for review).
Confirmed findings
🔎 Uncertain (could not confirm or disprove — review manually)
us.deepseek.r1-v1:0 · Run: #28636108729 · tokens: in: 151501 · out: 35629 · total: 187130 · calls: 45 · est. ~$0.397 |
| * (or `endpointProtocols`) flag we can read directly, the FE has to ask | ||
| * the actual download endpoint. HEAD is the lightweight option — JAX-RS | ||
| * auto-handles HEAD by invoking the `@GET` handler and discarding the | ||
| * body, so we get the file-existence answer without paying for the |
There was a problem hiding this comment.
🟠 [High] Missing URI encoding for bundleId in URL path
The URL construction at line 245 directly interpolates bundleId into the path without URI encoding. This could allow path traversal attacks if bundle IDs contain '/' characters or other special URL characters. While bundle IDs are typically UUIDs in dotCMS, the lack of encoding violates secure URL construction practices and leaves a potential injection vector if ID formats ever change.
| */ | ||
| export enum PublishAuditStatus { | ||
| BUNDLE_REQUESTED = 'BUNDLE_REQUESTED', | ||
| BUNDLING = 'BUNDLING', |
There was a problem hiding this comment.
🟠 [High] Typo in PublishingStatus enum value
The enum value FAILED_TO_SENT in PublishingStatus is a typo. Backend status strings likely use 'FAILED_TO_SEND' (past tense 'sent' vs base verb 'send'). This mismatch would cause frontend status filtering/display to break for failed bundle states, as the frontend enum wouldn't match backend responses.
| [filter]="true" | ||
| [filterPlaceHolder]="'search' | dm" | ||
| [scrollHeight]="LISTBOX_SCROLL_HEIGHT" | ||
| [pt]="listboxPt" |
There was a problem hiding this comment.
🟠 [High] Missing @output() onChange emitter
The template binds to (onChange) event but the component lacks an @output() onChange emitter. Angular will attempt to call a non-existent method, causing runtime errors when selecting filter options. The component's TypeScript file does not declare this output, breaking the event binding.
Context
Closes #36040. Part of epic #34734 (Publishing Queue: Dojo → Angular migration). Consumes the API + IA audit from spike #36039. User-facing documentation follows in #36041.
Design divergence from the parent task worth flagging: the original plan listed a
p-tabViewwith aQueuetab (two-columnREADY TO SEND+IN PROGRESS) and aHistorytab. Design iterated during implementation to a single unified bundles table with a status-chip filter — same information, less nesting. Status buckets (READY, IN PROGRESS, SCHEDULED, FAILED, SUCCESS) are surfaced through the chip row instead of tabs + columns; a syntheticSCHEDULEDstatus was added server-side so bundles waiting for a futurepublishDateshow up correctly without a persistent audit row.Summary
libs/portlets/dot-publishing-queue(shell + toolbar + unified table + status chip filter + four dialogs + SignalStore) replacing the legacy JSP-driven Publishing Queue and History screens.libs/data-access/src/lib/dot-publishing-queuecovering the v1 publishing endpoints (/api/v1/publishing/*,/api/v1/bundles/*) plus the legacy/api/bundle/*endpoints that v1 hasn't consolidated yet (getunsendbundles,syncupload,_generate,{id}/assets,_download,{id}/manifest).Add BundleforRetry Sendas the primary action. Checkbox + kebab columns stay frozen on narrow viewports.Backend wiring
dotCMS/src/main/webapp/WEB-INF/portlet.xml:publishing-queueJSP entry renamed topublishing-queue-legacy(kept as the rollback escape hatch).publishing-queueentry points at the Angular shell viacom.dotcms.spring.portlet.PortletController.dotCMS/src/main/java/com/dotmarketing/util/PortletID.java: addsPUBLISHING_QUEUE_LEGACY("publishing-queue-legacy").dotCMS/src/main/webapp/WEB-INF/messages/Language.properties: adds portlet titles for bothpublishing-queueandpublishing-queue-legacy, plus the FE's UI strings.com.dotcms.rest.api.v1.publishing.PublishingResource: gates the two destructive endpoints (retry+push) behindrequiredPortlet("publishing-queue"), matching the pattern the other v1 endpoints already use.Test plan
pnpm nx test portlets-dot-publishing-queue-portletpasses (196+ specs green)/dotAdmin/#/c/publishing-queueserved by the Angular shell.tar.gz/.tgzonly; clicking Upload without a file surfaces the file-required warning inlinepublishing-queue↔publishing-queue-legacyinportlet.xmlreverts to the JSP without redeploypublishing-queueportlet,requireLicense(true)on uploads)Follow-ups
/api/bundle/*endpoints into/api/v1/publishing/*is out of scope for this PR — filed as separate tasks per the spike's backend hardening backlog on [Spike] Publishing Queue: consolidated audit — current state, backend API, target UI mapping #36039.🤖 Generated with Claude Code