-
Notifications
You must be signed in to change notification settings - Fork 481
feat(publishing-queue): Angular portlet + backend wiring #36413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
80 commits
Select commit
Hold shift + click to select a range
063b740
feat(publishing-queue) #36040: Angular portlet shell, Queue tab, Asse…
hmoreras b01ed43
fix(publishing-queue) #36045: enforce publishing-queue portlet gate o…
hmoreras 8c05085
feat(publishing-queue) #36040: History tab, modals (Details, Configur…
hmoreras 4037903
feat(publishing-queue) #36040: UX iteration — empty state, status chi…
hmoreras dec6884
merge master
hmoreras 1fba21e
refactor(publishing-queue) #36040: reuse project-wide push publish + …
hmoreras 3d708df
refactor(publishing-queue) #36040: History table layout (dot-tags pat…
hmoreras a33928b
fix(publishing-queue) #36040: align history status labels with legacy…
hmoreras 5f86b2d
feat(publishing-queue) #36040: add Bundle Name column to history table
hmoreras 5e36a82
feat(publishing-queue) #36040: Select Bundles to Delete dialog (4 sco…
hmoreras 9658986
refactor(publishing-queue) #36040: gate Delete Bundles button on sele…
hmoreras b11b749
refactor(publishing-queue) #36040: bundle details — single endpoints …
hmoreras 8f983b5
feat(publishing-queue) #36040: upload bundle dialog — canonical drag-…
hmoreras 549b63b
feat(publishing-queue) #36040: history row kebab + layout polish + de…
hmoreras a2efc00
style(publishing-queue) #36040: history table — drop per-cell font si…
hmoreras 874232b
style(publishing-queue) #36040: smaller font for Bundle Id + dates; m…
hmoreras d796fdf
refactor(publishing-queue) #36040: single unified table + status chip…
hmoreras 6ec96bb
refactor(publishing-queue) #36040: omit status param when filter is e…
hmoreras 36b8937
feat(publishing-queue) #36040: Add Bundle dropdown + Select Bundle di…
hmoreras eb7bcb9
style(publishing-queue) #36040: shorter status chip labels + tighter …
hmoreras 502ed82
fix(publishing-queue) #36040: Select Bundle asset table — name + type…
hmoreras 12c16ab
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 2c4774f
feat(publishing-queue): items column, custom dialog header, row conte…
hmoreras 556366a
feat(publishing-queue): gate bundle + manifest download buttons on HE…
hmoreras c271d15
feat(publishing-queue): red bundle ids on failed rows, dotRelativeDat…
hmoreras 041da18
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras b379f31
merge manin
hmoreras fd17f4d
chore(core-web): regenerate pnpm-lock.yaml for jest-util
hmoreras d1ee134
feat(publishing-queue): surface the synthetic SCHEDULED status
hmoreras d8ff4db
feat(publishing-queue): inline Configure step in Select Bundle, push …
hmoreras c05873f
fix(publishing-queue): persist rows-per-page and match content-drive …
hmoreras ad5ddcc
fix(publishing-queue): silent polling — stop the every-15s skeleton f…
hmoreras 464b911
fix(publishing-queue): status filter — include SCHEDULED, dedupe by l…
hmoreras 7bdb68c
feat(publishing-queue): inline Download menu in Select Bundle dialog
hmoreras 6e9a58b
feat(publishing-queue): make Select Bundle asset rows clickable, drop…
hmoreras bb080e8
fix(publishing-queue): drop sort UI from the bundles table — BE doesn…
hmoreras 52261e6
feat(publishing-queue): inline warning instead of disabled buttons
hmoreras 8b18c84
feat(publishing-queue): surface scheduledPublishDate in bundle details
hmoreras 79b99bb
feat(publishing-queue): swap Add Bundle ↔ Retry Send on selection
hmoreras b4a6b70
feat(publishing-queue): replace 4-scope Remove dialog with a simple c…
hmoreras 556c955
feat(publishing-queue): asset-list dialog polish + allow remove on SC…
hmoreras b3934bd
fix(publishing-queue): breathe some room around the Select Bundle tra…
hmoreras 920f0a1
feat(publishing-queue): primary Close + unified date format on detail…
hmoreras 700a9f7
feat(publishing-queue): table typography, date format, frozen edge co…
hmoreras ab81fdf
fix(publishing-queue): hide the Select Bundle trash icon until row hover
hmoreras 3119267
feat(publishing-queue): Select Bundle dialog polish (pagination, head…
hmoreras d9b4e1b
feat(publishing-queue): Upload dialog — always-clickable Upload with …
hmoreras d6cd9f2
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 22142bc
fix(publishing-queue): address code-review feedback
hmoreras f251c09
fix(publishing-queue): URL-encode bundleId in service path interpolat…
hmoreras a9acf78
fix(publishing-queue): show loading skeleton on first Select Bundle a…
hmoreras ba45ed1
feat(publishing-queue): Select Bundle dialog uses dot-empty-container…
hmoreras ca33811
feat(publishing-queue): toast notifications for retry outcomes
hmoreras c43b53d
feat(publishing-queue): rename 'Sent' status label to 'Success'
hmoreras 5f11fad
chore(publishing-queue): apply nx format:check across the portlet
hmoreras d227b26
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 1a0ae6f
test(publishing-queue): update portlet count after adding publishing-…
hmoreras d2d4e60
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 460820c
fix(publishing-queue): render retry toasts via DotMessageDisplayService
hmoreras 5d859e7
Merge branch 'issue-36040-publishing-queue-angular-portlet' of https:…
hmoreras ceb573c
chore(publishing-queue): drop unused jest-util devDependency
hmoreras 9bbd649
fix(publishing-queue): address maintainer review feedback
hmoreras 9737eb3
refactor(publishing-queue): use `#` ECMAScript private for injected f…
hmoreras 6e04fb6
refactor(publishing-queue): apply \$ signal prefix per ANGULAR_STANDARDS
hmoreras 475fe70
style(publishing-queue): apply prettier formatting to service + spec
hmoreras 03fa3f3
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 95b4f4f
fix(publishing-queue): narrow push-failure union so TS accepts .error…
hmoreras b97b6dd
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
wezell b7c10dd
test(publishing-queue): Spectator migration + error-path + coverage gaps
hmoreras 95128df
refactor(publishing-queue): finish select-bundle-dialog review cleanup
hmoreras 6ff014d
test(publishing-queue): put toolbar data-testid on the inner button (…
hmoreras 5a5072a
refactor(publishing-queue): drop failed-row red bundle id + trim colu…
hmoreras 4dc6e27
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 79fe093
Merge branch 'issue-36040-publishing-queue-angular-portlet' of https:…
hmoreras a62cf19
refactor(publishing-queue): replace Download tiered menu with a step
hmoreras 1f5925f
feat(publishing-queue): make items count clickable to open contents d…
hmoreras ad61b20
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras 2e0f7c5
revert(publishing-queue): drop v1 PublishingResource portlet gate
hmoreras 4a90cbc
Merge branch 'main' into issue-36040-publishing-queue-angular-portlet
hmoreras a945ff1
refactor(publishing-queue): register Angular portlet as Beta, keep Do…
hmoreras File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
405 changes: 405 additions & 0 deletions
405
core-web/libs/data-access/src/lib/dot-publishing-queue/dot-publishing-queue.service.spec.ts
Large diffs are not rendered by default.
Oops, something went wrong.
368 changes: 368 additions & 0 deletions
368
core-web/libs/data-access/src/lib/dot-publishing-queue/dot-publishing-queue.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,368 @@ | ||
| import { Observable, of } from 'rxjs'; | ||
|
|
||
| import { HttpClient, HttpParams } from '@angular/common/http'; | ||
| import { Injectable, inject } from '@angular/core'; | ||
|
|
||
| import { catchError, map } from 'rxjs/operators'; | ||
|
|
||
| import { | ||
| BundleAssetView, | ||
| DotCMSResponse, | ||
| PublishAuditStatus, | ||
| PublishingJobDetailView, | ||
| PublishingJobsResponse, | ||
| PushBundleForm, | ||
| PushBundleResultView, | ||
| RemoveAssetResultView, | ||
| RetryBundleResultView, | ||
| UnsentBundlesResponse | ||
| } from '@dotcms/dotcms-models'; | ||
|
|
||
| export type PublishingSortField = 'bundle_name' | 'status' | 'created' | 'modified'; | ||
| export type PublishingSortDirection = 'asc' | 'desc'; | ||
|
|
||
| export interface ListPublishingJobsParams { | ||
| /** Empty/omitted = all statuses (BE returns every row in publish_audit). */ | ||
| statuses?: readonly PublishAuditStatus[]; | ||
| page?: number; | ||
| perPage?: number; | ||
| filter?: string; | ||
| sort?: PublishingSortField; | ||
| sortDirection?: PublishingSortDirection; | ||
| } | ||
|
|
||
| export type RetryDeliveryStrategy = 'ALL_ENDPOINTS' | 'FAILED_ENDPOINTS'; | ||
|
|
||
| export interface RetryBundlesPayload { | ||
| bundleIds: string[]; | ||
| forcePush?: boolean; | ||
| deliveryStrategy?: RetryDeliveryStrategy; | ||
| } | ||
|
|
||
| /** | ||
| * Backs the Publishing Queue Angular portlet. | ||
| * | ||
| * v1 endpoints (`com.dotcms.rest.api.v1.publishing.PublishingResource`): | ||
| * - `GET /api/v1/publishing` — list bundles by status (sort/filter via params) | ||
| * - `GET /api/v1/publishing/{bundleId}` — full bundle detail with endpoints | ||
| * - `POST /api/v1/publishing/retry` — retry bundles (bulk) | ||
| * - `DELETE /api/v1/publishing/{bundleId}` — delete single bundle (synchronous) | ||
| * - `DELETE /api/v1/publishing/purge?status=...` — bulk delete by status (async, | ||
| * WebSocket-notified; omit `status` to use safe defaults = ALL terminal/queued) | ||
| * | ||
| * Legacy endpoints (`com.dotcms.rest.BundleResource`) still used until #36048 lands: | ||
| * - `DELETE /api/bundle/ids` — bulk delete by bundle id (async, WebSocket-notified; | ||
| * same endpoint the legacy JSP uses for "SELECTED" in the delete-bundles dialog) | ||
| * - `GET /api/bundle/{bundleId}/assets` — asset list inside a bundle | ||
| * - `POST /api/bundle/sync` — synchronous .tar.gz upload (licensed) | ||
| * - `GET /api/bundle/_download/{bundleId}` — bundle download (URL only) | ||
| * | ||
| * Two push paths exist: | ||
| * - Row-level "Configure & Send" delegates to the project-wide push publish | ||
| * dialog (`DotPushPublishDialogService.open(...)` from `@dotcms/dotcms-js`), | ||
| * which hits the legacy `/DotAjaxDirector/.../cmd/pushBundle` endpoint via | ||
| * `PushPublishService`. | ||
| * - The Select Bundle dialog calls `pushBundle` (see below), which posts to | ||
| * `POST /api/v1/publishing/push/{bundleId}` — the modern v1 REST endpoint. | ||
| * | ||
| * Bundle generate-and-download is delegated to the project-wide download dialog | ||
| * (`DotDownloadBundleDialogService.open(bundleId)` from | ||
| * `@services/dot-download-bundle-dialog/...`), which posts to `/api/bundle/_generate` | ||
| * with the user-picked filter + operation and triggers a browser download. | ||
| * No bespoke generate endpoint lives here either. | ||
| */ | ||
| @Injectable({ | ||
| providedIn: 'root' | ||
| }) | ||
| export class DotPublishingQueueService { | ||
| readonly #http = inject(HttpClient); | ||
|
|
||
| listPublishingJobs(params: ListPublishingJobsParams): Observable<PublishingJobsResponse> { | ||
| let httpParams = new HttpParams(); | ||
|
|
||
| // Only send status when the caller selected one or more — omitting the | ||
| // param tells the BE "all statuses" and makes the FE forward-compatible | ||
| // with new server-side statuses (e.g. SCHEDULED, see #36267). | ||
| if (params.statuses && params.statuses.length > 0) { | ||
| httpParams = httpParams.set('status', params.statuses.join(',')); | ||
| } | ||
|
|
||
| if (params.page !== undefined) { | ||
| httpParams = httpParams.set('page', params.page); | ||
| } | ||
|
|
||
| if (params.perPage !== undefined) { | ||
| httpParams = httpParams.set('per_page', params.perPage); | ||
| } | ||
|
|
||
| if (params.filter && params.filter.length > 0) { | ||
| httpParams = httpParams.set('filter', params.filter); | ||
| } | ||
|
|
||
| if (params.sort) { | ||
| const order = params.sortDirection === 'desc' ? '-' : ''; | ||
| httpParams = httpParams.set('sort', `${order}${params.sort}`); | ||
| } | ||
|
|
||
| return this.#http.get<PublishingJobsResponse>('/api/v1/publishing', { | ||
| params: httpParams | ||
| }); | ||
| } | ||
|
|
||
| getPublishingJobDetails(bundleId: string): Observable<PublishingJobDetailView> { | ||
| return this.#http | ||
| .get< | ||
| DotCMSResponse<PublishingJobDetailView> | ||
| >(`/api/v1/publishing/${encodeURIComponent(bundleId)}`) | ||
| .pipe(map((response) => response.entity)); | ||
| } | ||
|
|
||
| retryBundles(payload: RetryBundlesPayload): Observable<RetryBundleResultView[]> { | ||
| return this.#http | ||
| .post<DotCMSResponse<RetryBundleResultView[]>>('/api/v1/publishing/retry', payload) | ||
| .pipe(map((response) => response.entity)); | ||
| } | ||
|
|
||
| /** | ||
| * Queues a bundle for publishing via the modern v1 REST endpoint. | ||
| * Replaces the legacy `/DotAjaxDirector/.../cmd/pushBundle` AJAX action — same | ||
| * effect, proper JSON in/out + proper HTTP status codes. | ||
| * | ||
| * BE endpoint: `POST /api/v1/publishing/push/{bundleId}` (see | ||
| * `com.dotcms.rest.api.v1.publishing.PublishingResource.pushBundle`). | ||
| * Single-bundle per call; callers needing to push N bundles must fan out | ||
| * (e.g. via `forkJoin`). | ||
| */ | ||
| pushBundle(bundleId: string, form: PushBundleForm): Observable<PushBundleResultView> { | ||
| return this.#http | ||
| .post< | ||
| DotCMSResponse<PushBundleResultView> | ||
| >(`/api/v1/publishing/push/${encodeURIComponent(bundleId)}`, form) | ||
| .pipe(map((response) => response.entity)); | ||
| } | ||
|
|
||
| deleteBundle(bundleId: string): Observable<{ message: string }> { | ||
| return this.#http.delete<{ | ||
| message: string; | ||
| }>(`/api/v1/publishing/${encodeURIComponent(bundleId)}`); | ||
| } | ||
|
|
||
| /** | ||
| * Bulk delete bundles by id — fire-and-forget. The BE acks immediately with a | ||
| * `ResponseEntityView<string>` message; the actual deletion runs on a background | ||
| * thread and notifies the user via WebSocket system message when finished. | ||
| * | ||
| * Uses the legacy `DELETE /api/bundle/ids` (same endpoint as the JSP) until the | ||
| * v1 consolidation work (#36048) ships an equivalent under `/api/v1/publishing`. | ||
| */ | ||
| deleteBundles(bundleIds: string[]): Observable<unknown> { | ||
|
hmoreras marked this conversation as resolved.
|
||
| return this.#http.request<unknown>('DELETE', '/api/bundle/ids', { | ||
| body: { identifiers: bundleIds } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Bulk-purges bundles by status — fire-and-forget. Mirrors the legacy | ||
| * `/api/bundle/all{,/success,/fail}` endpoints behind a single v1 path. Omit | ||
| * `statuses` to use the BE's safe defaults (all terminal + queued; in-progress | ||
| * statuses are rejected with 400). BE acks immediately; result is delivered via | ||
| * WebSocket system message. | ||
| */ | ||
| purgeBundles(statuses?: readonly PublishAuditStatus[]): Observable<unknown> { | ||
| const params = | ||
| statuses && statuses.length > 0 | ||
| ? new HttpParams().set('status', statuses.join(',')) | ||
| : new HttpParams(); | ||
| return this.#http.delete<unknown>('/api/v1/publishing/purge', { params }); | ||
| } | ||
|
|
||
| uploadBundle(file: File): Observable<{ bundleName: string; status: string }> { | ||
|
hmoreras marked this conversation as resolved.
|
||
| const formData = new FormData(); | ||
| formData.append('file', file, file.name); | ||
| return this.#http.post<{ bundleName: string; status: string }>( | ||
| '/api/bundle/sync', | ||
| formData | ||
| ); | ||
| } | ||
|
|
||
| /** Builds the absolute download URL for a bundle's `.tar.gz`. */ | ||
| getBundleDownloadUrl(bundleId: string): string { | ||
| return `/api/bundle/_download/${encodeURIComponent(bundleId)}`; | ||
| } | ||
|
|
||
| /** | ||
| * Mirrors the legacy `DotDownloadBundleDialogComponent` submit: POSTs to | ||
| * `/api/bundle/_generate` and returns the resulting `.tar.gz` blob plus the | ||
| * filename parsed from the `content-disposition` header. | ||
| * | ||
| * Used by the inline Download menu in the Select Bundle dialog — picking a | ||
| * filter from the menu must produce the exact same file a customer gets | ||
| * from the legacy modal, so the wire payload here is byte-identical: | ||
| * `{ bundleId, operation: '0' | '1', filterKey }` | ||
| * where `'0'` = publish, `'1'` = unpublish (BE vocabulary, not the v1 REST | ||
| * `'publish'` / `'expire'` strings — the `_generate` endpoint predates | ||
| * v1). | ||
| * | ||
| * Uses `HttpClient.post` with `{ observe: 'response', responseType: 'blob' }` | ||
| * so the project's auth + error interceptors apply and the headers are | ||
| * available for the filename parse — the legacy dialog used raw `fetch` | ||
| * only because of how it bootstrapped pre-v1. | ||
| */ | ||
| generateBundle( | ||
| bundleId: string, | ||
| operation: '0' | '1', | ||
| filterKey: string | ||
| ): Observable<{ blob: Blob; filename: string }> { | ||
| return this.#http | ||
| .post( | ||
| '/api/bundle/_generate', | ||
| { bundleId, operation, filterKey }, | ||
| { | ||
| observe: 'response', | ||
| responseType: 'blob' | ||
| } | ||
| ) | ||
| .pipe( | ||
| map((response) => { | ||
| if (!response.body) { | ||
| throw new Error('Empty bundle-generate response body'); | ||
| } | ||
| return { | ||
| blob: response.body, | ||
| filename: parseFilenameFromContentDisposition( | ||
| response.headers.get('content-disposition') | ||
| ) | ||
| }; | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| /** Builds the absolute download URL for a bundle's manifest CSV. */ | ||
| getBundleManifestUrl(bundleId: string): string { | ||
| return `/api/bundle/${encodeURIComponent(bundleId)}/manifest`; | ||
| } | ||
|
|
||
| /** | ||
| * Returns `true` when the bundle's `.tar.gz` is downloadable right now. | ||
| * | ||
| * Why this probe exists: | ||
| * The legacy JSP gates the "Download Bundle" button on two server-side | ||
| * conditions: | ||
| * 1. the `.tar.gz` file existing on disk (it may have been purged via | ||
| * `DELETE /api/bundle/olderthan/{N}` — there is no scheduled cleanup | ||
| * built into the core, so this happens only on manual admin action); | ||
| * 2. the targeted environments not being static-publish / S3 (those | ||
| * protocols don't produce a downloadable archive). | ||
| * | ||
| * Neither piece of state is exposed on the current detail response | ||
| * (`GET /api/v1/publishing/{bundleId}`). Until the BE adds a `hasBundle` | ||
| * (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 | ||
|
hmoreras marked this conversation as resolved.
|
||
| * payload. A 404 (file purged) becomes `false` via `catchError`. | ||
| * | ||
| * @see `RemotePublishAjaxAction.downloadBundle` and `BundleResource._download` | ||
| * for the server-side behavior. | ||
| */ | ||
| probeBundleDownload(bundleId: string): Observable<boolean> { | ||
| return this.#http.head(this.getBundleDownloadUrl(bundleId), { observe: 'response' }).pipe( | ||
| map((response) => response.status === 200), | ||
| catchError(() => of(false)) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns `true` when the bundle's manifest CSV is downloadable right now. | ||
| * | ||
| * Same rationale as {@link probeBundleDownload}: the legacy JSP calls | ||
| * `ManifestUtil.manifestExists(bundleId)` server-side, which (a) checks | ||
| * that the `.tar.gz` is on disk and (b) scans the archive for a manifest | ||
| * entry — bundles built before the manifest feature was introduced have | ||
| * no entry. Neither check is exposed on `GET /api/v1/publishing/{bundleId}`, | ||
| * so the FE probes the actual download endpoint with HEAD. | ||
| * | ||
| * @see `BundleResource.downloadManifest` and `ManifestUtil.manifestExists` | ||
| * for the server-side behavior. | ||
| */ | ||
| probeBundleManifest(bundleId: string): Observable<boolean> { | ||
| return this.#http.head(this.getBundleManifestUrl(bundleId), { observe: 'response' }).pipe( | ||
| map((response) => response.status === 200), | ||
| catchError(() => of(false)) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Lists unsent (draft) bundles owned by the given user. | ||
| * | ||
| * Backed by the legacy endpoint `GET /api/bundle/getunsendbundles/userid/{userId}` | ||
| * (`BundleResource#getUnsendBundles`). The newer v1 `/api/v1/publishing` reads | ||
| * from `publish_audit` and does NOT include drafts — drafts live in | ||
| * `publishing_bundle` only. This is the only endpoint that surfaces them | ||
| * until #36048 (legacy → v1 consolidation) lands. | ||
| * | ||
| * Response shape: `{ identifier, label, items: [{ id, name }, ...], numRows }`. | ||
| * Caller is responsible for mapping `items` to whatever row shape the UI needs. | ||
| */ | ||
| getUnsendBundles( | ||
| userId: string, | ||
| filter = '*', | ||
| start = 0, | ||
| count = 50 | ||
| ): Observable<UnsentBundlesResponse> { | ||
| const query = new HttpParams() | ||
| .set('name', filter || '*') | ||
| .set('start', start) | ||
| .set('count', count); | ||
|
|
||
| return this.#http.get<UnsentBundlesResponse>( | ||
| `/api/bundle/getunsendbundles/userid/${encodeURIComponent(userId)}`, | ||
| { params: query } | ||
| ); | ||
|
hmoreras marked this conversation as resolved.
|
||
| } | ||
|
|
||
| getBundleAssets(bundleId: string): Observable<BundleAssetView[]> { | ||
| const params = new HttpParams().set('limit', -1); | ||
|
|
||
| return this.#http.get<BundleAssetView[]>( | ||
| `/api/bundle/${encodeURIComponent(bundleId)}/assets`, | ||
| { params } | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Removes one or more assets from an unsent bundle. | ||
| * Backed by `BundleManagementResource#removeAssetsFromBundle` | ||
| * (`DELETE /api/v1/bundles/{bundleId}/assets`). | ||
| * | ||
| * Backend returns 409 if the bundle is already in progress. | ||
| * Per-asset results live inside `entity[]` — some may fail while others succeed. | ||
| */ | ||
| removeAssetsFromBundle( | ||
| bundleId: string, | ||
| assetIds: string[] | ||
| ): Observable<RemoveAssetResultView[]> { | ||
| return this.#http | ||
| .request< | ||
| DotCMSResponse<RemoveAssetResultView[]> | ||
| >('DELETE', `/api/v1/bundles/${encodeURIComponent(bundleId)}/assets`, { body: { assetIds } }) | ||
| .pipe(map((response) => response.entity)); | ||
| } | ||
| } | ||
|
hmoreras marked this conversation as resolved.
|
||
|
|
||
| /** Pulls `filename` out of a `content-disposition` header. Mirrors the parse | ||
| * the legacy download dialog does (`contentDisposition.slice(idx + 'filename='.length)`) | ||
|
hmoreras marked this conversation as resolved.
|
||
| * so generated filenames match exactly. Returns an empty string when the | ||
| * header is missing or malformed — `getDownloadLink` will still produce a | ||
| * usable anchor in that case. */ | ||
| function parseFilenameFromContentDisposition(header: string | null): string { | ||
| if (!header) { | ||
| return ''; | ||
| } | ||
|
hmoreras marked this conversation as resolved.
|
||
| const key = 'filename='; | ||
| const idx = header.indexOf(key); | ||
| if (idx < 0) { | ||
| return ''; | ||
| } | ||
| return header.slice(idx + key.length).replace(/^"|"$/g, ''); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.