Skip to content

fix(content): resolve relationship field save failure on Translate Manually#36178

Merged
erickgonzalez merged 4 commits into
mainfrom
issue-35862-relationship-translate-manually-fallback
Jun 16, 2026
Merged

fix(content): resolve relationship field save failure on Translate Manually#36178
erickgonzalez merged 4 commits into
mainfrom
issue-35862-relationship-translate-manually-fallback

Conversation

@erickgonzalez

@erickgonzalez erickgonzalez commented Jun 15, 2026

Copy link
Copy Markdown
Member

When translating content via "Translate Manually", saving failed with HTTP 500 if a related contentlet had no version in the target language. Two root causes:

  • RelationshipUtil.filterContentlet threw a NullPointerException when findContentletForLanguage returned null; it now falls back to findContentletByIdentifierAnyLanguage (relationships are stored at the identifier level) and skips with a warning if nothing is found.
  • ESContentletAPIImpl.getContentletRelationships threw a ClassCastException when the contentlet map held the raw comma-separated UUID String set by MapToContentletPopulator instead of a List; it now inspects the runtime type and resolves the String via RelationshipUtil.filterContentlet, catching DotStateException as well.

Adds integration coverage in RelationshipUtilTest (language fallback, missing identifier) and ESContentletAPIImplTest (String relationship value with language fallback, and empty-string clearing).

fixes: #35862

This PR fixes: #35862

…nually

When translating content via "Translate Manually", saving failed with HTTP 500
if a related contentlet had no version in the target language. Two root causes:

- RelationshipUtil.filterContentlet threw a NullPointerException when
  findContentletForLanguage returned null; it now falls back to
  findContentletByIdentifierAnyLanguage (relationships are stored at the
  identifier level) and skips with a warning if nothing is found.
- ESContentletAPIImpl.getContentletRelationships threw a ClassCastException when
  the contentlet map held the raw comma-separated UUID String set by
  MapToContentletPopulator instead of a List<Contentlet>; it now inspects the
  runtime type and resolves the String via RelationshipUtil.filterContentlet,
  catching DotStateException as well.

Adds integration coverage in RelationshipUtilTest (language fallback, missing
identifier) and ESContentletAPIImplTest (String relationship value with
language fallback, and empty-string clearing).

fixes: #35862

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

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

❌ Codex Review failed — openai.gpt-5.5

The review job failed before producing output. See the run for details.

Run: #27587613233

@claude

claude Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Claude finished @erickgonzalez's task in 1m 41s —— View job


Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against unsafe categories
  • Post results / apply label

Result: ✅ Safe To Rollback

All 9 changed files were evaluated against every category in the rollback-unsafe reference. No unsafe category was matched.

File Change Verdict
ESContentletAPIImpl.java Defensive instanceof check before List cast; calls RelationshipUtil.filterContentlet for raw String values Pure logic fix — no schema/mapping/contract change
ContentUtils.java Adds overloaded addRelationships(…, boolean languageFallback) — old 7-arg overload preserved, delegates to new with false Backward-compatible overload addition
ContentHelper.java Same pattern: existing addRelationshipsToJSON preserved, new 14-arg overload added + private helper resolveRelatedLanguageAgnostic Backward-compatible overload addition
ContentResource.java Calls new overload with languageFallback = true only for the content editor GET path Internal behavior change, not an API contract change (response shape is unchanged)
RelationshipUtil.java NPE fix: null-guards identifier/relatedContentlet; adds findContentletByIdentifierAnyLanguage fallback Pure defensive logic — no schema change
ESContentletAPIImplTest.java New integration tests Tests only
ContentUtilsTest.java New integration tests Tests only
RelationshipUtilTest.java New integration tests Tests only
ContentResourceV1.postman_collection.json New Postman test cases for issue #35862 Tests only

Categories checked (all clear):

  • C-1 Structural Data Model Change — no runonce task, no schema modification
  • C-2 ES Mapping Change — no changes to ESMappingAPIImpl, ESMappingConstants, or mapping methods
  • C-3 Content JSON Model Version Bump — no CURRENT_MODEL_VERSION change, no ImmutableContentlet field additions
  • C-4 DROP TABLE / DROP COLUMN — not present
  • H-1 through H-8 — no data migrations, no renames, no PK restructuring, no new field types, no storage provider changes, no viewtool contract break (existing addRelationshipsToJSON signature intact; new overload only adds a parameter that defaults via delegation), no VTL-visible contract removal
  • M-1 through M-4 — no column type changes, no push-publishing bundle format changes; REST response shape is unchanged (same fields returned, just language-fallback resolution differs); no OSGi interface changes

The label "AI: Safe To Rollback" has been applied.

@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Jun 15, 2026
…editor

Follow-up to the Translate Manually save fix. When viewing a contentlet in the
new Edit Contentlet, the relationship field only listed related items that had a
version in the viewed language; items existing only in other languages were
silently dropped, and multilingual related items could be duplicated.
Relationships are stored per identifier in the tree table, so all related items
must be listed regardless of the viewed language, each exactly once, preserving
the saved order.

Mirrors how the legacy edit screen (ContentletAjax) resolved related content:
one working version per related identifier, language-agnostic, in tree_order.

- ContentResource.getContent opts into a new languageFallback flag when adding
  relationships; frontend page render, GraphQL and pull keep per-language
  filtering (flag defaults off).
- ContentUtils.addRelationships and ContentHelper.addRelationshipsToJSON gain
  default-off overloads carrying the flag (existing signatures preserved); the
  flag is threaded into both the main and self-relationship call sites and is
  NOT propagated into the nested depth 2/3 calls.
- New ContentHelper.resolveRelatedLanguageAgnostic resolves one row per related
  identifier (dedup by identifier preserving tree_order), preferring the viewed
  language version and falling back to any available version, then filters by
  READ permission so the fallback cannot surface unreadable content.

Adds ContentUtilsTest coverage (fallback listing, multilingual no-duplicate,
order preservation) and a ContentResourceV1 Postman case.

fixes: #35862

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

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bedrock Review — deepseek.v3.2

[🔴 Critical] dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java:6729 — Missing null check for fieldValue before instanceof String. If fieldValue is null, instanceof will return false, and the else if block will be skipped, potentially leaving relationships unresolved. This could cause silent data loss.

[🟠 High] dotCMS/src/main/java/com/dotcms/util/RelationshipUtil.java:106 — Missing null check for identifier. If identifier is null, identifier.getId() will throw a NullPointerException. The if condition should be if (identifier != null && UtilMethods.isSet(identifier.getId())).

[🟡 Medium] dotCMS/src/main/java/com/dotcms/rest/ContentHelper.java:849 — Potential performance issue: distinctByIdentifier uses putIfAbsent, which is fine, but iterates over all relatedRows. If relatedRows is large (e.g., many language versions), this could be inefficient. Consider using a LinkedHashMap and a Set for deduplication if performance becomes a concern.

[🟡 Medium] dotCMS/src/main/java/com/dotcms/rest/ContentHelper.java:864 — Catching DotContentletStateException | DotSecurityException is too broad. DotSecurityException should be caught separately and possibly re-thrown or handled differently, as it indicates a permission issue, not just a missing version.

[🟡 Medium] dotCMS/src/main/java/com/dotcms/rest/ContentHelper.java:870 — The fallback logic (versionToShow != null ? versionToShow : related) may return a contentlet in a different language than requested. This is intentional per the PR description, but could cause confusion if callers expect the returned list to be homogeneous in language. Ensure this is documented.

[🟡 Medium] dotCMS/src/main/java/com/dotcms/rest/ContentHelper.java:875APILocator.getPermissionAPI().filterCollection(...) is called after the fallback resolution. This is correct for security, but note that the related contentlet used for fallback may have already been filtered for READ permission in the earlier getRelatedContent call. This is fine, but ensures no double-filtering overhead.

[🟡 Medium] dotCMS/src/main/java/com/dotcms/util/RelationshipUtil.java:117 — Logging at WARN level for a missing identifier may be too noisy. Consider DEBUG or INFO since this could be a normal scenario (e.g., a relationship to a since-deleted contentlet). However, the PR description indicates this is for issue #35862, so a WARN may be appropriate for unexpected missing data.

[🟡 Medium] dotCMS/src/main/java/com/dotcms/rest/api/v1/content/ContentResource.java:415 — The new languageFallback parameter is hardcoded to true. This changes the behavior of the getContent endpoint. Ensure this is intentional and documented, as it may affect existing clients expecting the old behavior. The PR description states this is for the content editor read path only, which seems correct.

[🟡 Medium] dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/content/util/ContentUtils.java:830 — The new overloaded method addRelationships with languageFallback parameter is added, but the existing method calls the new one with false. This is fine, but ensure all existing callers are aware of the new default behavior (which remains unchanged).

[🟡 Medium] dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java:5170 — Test method getContentletRelationships_relationshipFieldAsString_resolvesWithLanguageFallback uses TestDataUtils.getSpanishLanguage() which may not be defined in the test base class. Ensure the test data utility is available and the language exists.

[🟡 Medium] dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/util/ContentUtilsTest.java:1388 — Test method test_add_relationships_languageFallback_listsAllRelatedRegardlessOfLanguage uses TestDataUtils.getSpanishLanguage() similarly. Verify its availability.

[🟡 Medium] dotcms-integration/src/test/java/com/dotcms/util/RelationshipUtilTest.java:195 — Test method test_filterContentlet_fallsBackToAnyLanguage_whenNoVersionInTargetLanguage uses TestDataUtils.getSpanishLanguage(). Ensure it's available.

[🟡 Medium] dotcms-postman/src/main/resources/postman/ContentResourceV1.postman_collection.json:2240 — New Postman tests added for issue #35862. Ensure the collection variables (fallbackLangId, fallbackChildId, fallbackParentId) are properly set and cleared between test runs to avoid interdependencies.


Run: #27591617065 · tokens: in: 11559 · out: 1119 · total: 12678

@erickgonzalez erickgonzalez added this pull request to the merge queue Jun 16, 2026
Merged via the queue into main with commit a1a2370 Jun 16, 2026
99 of 102 checks passed
@erickgonzalez erickgonzalez deleted the issue-35862-relationship-translate-manually-fallback branch June 16, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Bug: Relationship field fails to save on "Translate Manually" when related contentlet has no target-language version

2 participants