dotCMS / dotCMS/core

[UVE] Page Editor hangs indefinitely with no error when a container references a content type missing from the environment

Open
#36,305 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

OKR : Customer Support Team : Maintenance
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Summary

When the Universal Visual Editor (UVE) saves a page, it sends the full page payload to POST /api/v1/page/{pageId}/content. If any container on the page is configured to allow a content type that does not exist in the current environment (e.g., a type exists in QA but was never published to production), the backend throws a 400 Bad Request. However, the frontend silently swallows this error and leaves the editor in an infinite loading/spinner state with no message shown to the user.


Root Cause (confirmed via source code)

There are two compounding issues — one in the service layer and one that silently swallows errors from it:

1. DotPageApiService.save() swallows HTTP errors (dot-page-api.service.ts:94)
// core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api/dot-page-api.service.ts
save({ pageContainers, pageId, params }: SavePagePayload): Observable<unknown> {
    const variantName = params.variantName ?? DEFAULT_VARIANT_ID;
    return this.http
        .post(`/api/v1/page/${pageId}/content?variantName=${variantName}`, pageContainers)
        .pipe(catchError(() => EMPTY)); // ← swallows ALL HTTP errors, returns EMPTY
}

EMPTY is an Observable that completes without emitting. It is not an error — it is a silent completion.

2. Callers depend on an emission to proceed (withSave.ts:50, withPageApi.ts:354)

Both withSave.ts and withPageApi.ts follow the same pattern:

tap(() => patchState(store, { status: UVE_STATUS.LOADING })),  // ← spinner starts
switchMap(() =>
    dotPageApiService.save(payload).pipe(
        switchMap(() => { /* reload page, set LOADED */ }),   // ← never runs: EMPTY emits nothing
        catchError((e) => {
            patchState(store, { status: UVE_STATUS.ERROR }); // ← never runs: EMPTY doesn't error
            return EMPTY;
        })
    )
)

Because dotPageApiService.save() returns EMPTY on error:

  • The inner switchMap (which sets LOADED) never executes — no value to map over.
  • The catchError never executes — EMPTY is not an error.
  • The store remains permanently in UVE_STATUS.LOADING.
  • The spinner spins forever. No error toast, no message, no rollback.
3. Backend validation (PageResource.java:879, ContainerAPIImpl.java:604)

validateContainerEntries() calls getContainerContentTypes()getContentTypesInContainer(). That method silently skips content types it cannot find (if (type == null) { continue; }), producing an empty or incomplete allowed-types set. When a page save includes containers whose allowed types don't exist in the environment, the subsequent check:

if (!contentTypeSet.contains(contentlet.getContentType().variable())) {
    throw new BadRequestException("The content type: " + ... + " is not valid for the container");
}

...throws a BadRequestException (HTTP 400). The backend has a meaningful error message; the frontend never surfaces it.


Steps to Reproduce

  1. Set up two environments (e.g., QA and production) that share pages via push publishing.
  2. In QA, create a container that allows a new content type (e.g., ReactApplicationImport). Do not publish the content type to production.
  3. In QA, add a page that uses that container. Push-publish the page to production (using Only Selected Item).
  4. Open the page in the production UVE/Page Editor.
  5. Attempt to drag, reorder, or move any content item on the page (not just items of the missing type).

Expected: An error message is displayed explaining that a content type referenced by the page is missing. The editor remains functional for unaffected containers.

Actual: The editor enters an infinite loading/spinner state. No error is shown. After refreshing, all changes are lost.


Affected Files

File Issue
core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api/dot-page-api.service.ts save() uses catchError(() => EMPTY) — swallows all HTTP errors silently
core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/save/withSave.ts Store stuck in LOADING because EMPTY never emits
core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/page-api/withPageApi.ts Same pattern as withSave.tseditorSave also affected
dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java validateContainerEntries() (L879) blocks the save with a 400 when a referenced content type is missing

Proposed Fix

Minimal (frontend): Remove or replace the catchError(() => EMPTY) in DotPageApiService.save(). Both callers already have their own catchError that correctly patches state to UVE_STATUS.ERROR — they just never fire because the error is swallowed upstream. Letting the error propagate is sufficient to display the error state.

Better UX: Surface the backend's error message as a toast/notification so the user knows why the save failed, rather than just showing a generic error state.

Ideal (backend): validateContainerEntries() should tolerate missing content types rather than failing the entire page save. If a container references a type not present in the environment, it should either skip that container's validation or return a warning — it should not block reordering of completely unrelated content.


Environment

  • dotCMS Version: dotEvergreen (Current Release)
  • Environment: Production (Cloud)
  • Freshdesk Ticket: https://dotcms.freshdesk.com/a/tickets/38036
  • Reproduction confirmed: Yes (customer confirmed fix after publishing missing content type to prod)

Acceptance Criteria

  • Moving/reordering content in the Page Editor does not leave the editor in an infinite loading state when a non-fatal backend error occurs
  • HTTP errors from POST /api/v1/page/{pageId}/content propagate to the store's error handler so the spinner is cleared
  • A user-visible error message is shown when a page save fails, with enough context to understand the cause
  • Optionally: the backend does not block a full page save due to a container referencing a content type that is absent from the environment

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with dot-page-api.service.ts and trace save() into withSave.ts and withPageApi.ts, then inspect the POST /api/v1/page/{pageId}/content flow. Reproduce the missing-content-type scenario and verify the editor exits LOADING, reaches its error state, and shows a useful message instead of hanging.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, typescript
Domain
api, backend, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.