Skip to content

Scoped the content authoring API moderation policy to entity creation so drafts can be published.#245

Merged
AlexSkrypnyk merged 4 commits into
developfrom
feature/fix-editor-publish
Jul 1, 2026
Merged

Scoped the content authoring API moderation policy to entity creation so drafts can be published.#245
AlexSkrypnyk merged 4 commits into
developfrom
feature/fix-editor-publish

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jul 1, 2026

Copy link
Copy Markdown
Member

Checklist before requesting a review

  • Subject includes ticket number as [#123] Verb in past tense.
  • Ticket number #123 added to description
  • Added context in Changed section
  • Self-reviewed code and commented in commented complex areas.
  • Added tests for fix/feature.
  • Relevant tests run and passed locally.

Note: no associated issue number for this change. The two checklist items referencing a ticket number are not applicable.

Changed

  1. Added an isNew() guard to ModerationPolicyHook::entityPresave() so the policy that coerces moderation state only fires on entity creation (authoring), not on subsequent saves — allowing a reviewer to publish a draft authored through the API without the hook reverting the state back to draft.
  2. Installed the node_access schema in the kernel test setUp() so a second save() call (simulating the publish update) can execute cleanly inside the kernel test environment.
  3. Added a new kernel test testApiPagePublishAfterAuthoringIsHonoured() that creates a page as an API actor, asserts it is coerced to draft, then updates the moderation state to published and asserts the hook no longer reverts it.
  4. Added a Behat feature content_moderation_publish.feature that logs in as a user with both use content authoring api and editorial transition permissions, creates a draft page, publishes it through the editorial UI, and asserts the published state via a new FeatureContext step.
  5. Added contentShouldBePublished() to FeatureContext — a Then step that reloads the node from storage (bypassing the stale entity cache) and asserts both the moderation_state field and the isPublished() flag.

Screenshots

N/A — PHP hook, kernel test, and Behat feature; no template, CSS, or JS changes.

Before / After

BEFORE (every save triggers the policy)
+--------------------------------------+
| API actor creates page               |
|   entityPresave fires                |
|   -> moderation_state = draft  ✓    |
+--------------------------------------+
          |
          v
+--------------------------------------+
| Reviewer opens edit form, selects   |
| "Published", presses Save            |
|   entityPresave fires again          |
|   -> moderation_state = draft  ✗    |
|   (publish silently reverted)        |
+--------------------------------------+

AFTER (policy scoped to isNew() only)
+--------------------------------------+
| API actor creates page               |
|   entityPresave fires, isNew()=true  |
|   -> moderation_state = draft  ✓    |
+--------------------------------------+
          |
          v
+--------------------------------------+
| Reviewer opens edit form, selects   |
| "Published", presses Save            |
|   entityPresave fires, isNew()=false |
|   -> policy skipped                  |
|   -> moderation_state = published ✓ |
+--------------------------------------+

Summary by CodeRabbit

  • New Features

    • Added support for verifying that content is published after editorial updates in the content moderation flow.
    • Improved publishing behavior for API-authored content so later editorial transitions to Published are respected.
  • Bug Fixes

    • Fixed an issue where publishing changes could be overridden after initial content creation.
    • Strengthened moderation checks to ensure published content stays published when saved from the editorial interface.

The 'ModerationPolicyHook' ran on every entity save, so it also clobbered a reviewer's later publish: any account holding 'use content authoring api' could create a draft but never publish it - the moderation state was silently coerced back to 'draft' on the update. The policy governs authoring, which is creation, so it now only applies when the entity is new; a subsequent publish (an update) is left untouched. Added a kernel test covering publish-after-authoring and a Behat scenario that publishes a draft through the editorial UI.

The kernel test setup now installs the 'node_access' schema so a second (update) save can run.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a guard in ModerationPolicyHook::entityPresave() so moderation-state enforcement applies only on entity creation, not subsequent updates. Adds a kernel test covering the publish-after-authoring case, plus a Behat step and feature verifying content publication through the editorial UI.

Changes

Moderation policy publish fix

Layer / File(s) Summary
Guard moderation policy to creation only
web/modules/custom/do_content_api/src/Hook/ModerationPolicyHook.php
Adds an early return when the entity is not new so moderation state is only forced during authoring, not on later updates.
Kernel test for publish-after-authoring
web/modules/custom/do_content_api/tests/src/Kernel/Hook/ModerationPolicyHookTest.php
Installs node_access schema and adds a test verifying a page authored via API starts as draft and honours a later update to published.
Behat publish step and feature
tests/behat/bootstrap/FeatureContext.php, tests/behat/features/content_moderation_publish.feature
Adds a contentShouldBePublished Then-step that reloads a node and asserts it is published, and a feature scenario exercising publishing a draft through the editorial UI.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • drevops/website#238: Introduces the original ModerationPolicyHook::entityPresave() behavior that this PR refines with the isNew() guard and extended tests.

Suggested labels: Needs review

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: limiting the moderation policy to entity creation so later draft publishes are honored.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fix-editor-publish

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/modules/custom/do_content_api/tests/src/Kernel/Hook/ModerationPolicyHookTest.php`:
- Around line 180-204: Add a companion kernel test in ModerationPolicyHookTest
next to testApiPagePublishAfterAuthoringIsHonoured that covers the media path in
ModerationPolicyHook::entityPresave(). Create an API user with the content
authoring permission, create a media entity in draft as an authored save, then
update the same entity to published and assert the later save keeps the
published moderation_state and published status. Use the existing entityPresave
node/media branching as the target behavior to verify.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 964be520-6d30-4159-8763-d5be82222310

📥 Commits

Reviewing files that changed from the base of the PR and between 2d134fe and f216a6e.

📒 Files selected for processing (4)
  • tests/behat/bootstrap/FeatureContext.php
  • tests/behat/features/content_moderation_publish.feature
  • web/modules/custom/do_content_api/src/Hook/ModerationPolicyHook.php
  • web/modules/custom/do_content_api/tests/src/Kernel/Hook/ModerationPolicyHookTest.php

Comment on lines +180 to +204
/**
* Tests that an API actor can publish a page it previously authored.
*/
public function testApiPagePublishAfterAuthoringIsHonoured(): void {
$api_user = $this->createUser(['use content authoring api']);
$this->assertNotFalse($api_user);
$this->setCurrentUser($api_user);

// Authoring (create) is forced to draft by the policy.
$node = Node::create([
'type' => 'civictheme_page',
'title' => '[TEST] Authored then published',
'moderation_state' => 'draft',
]);
$node->save();
$this->assertSame('draft', $node->get('moderation_state')->value);

// A later publish is an update, not authoring, so it must be honoured.
$node->set('moderation_state', 'published');
$node->save();

$this->assertSame('published', $node->get('moderation_state')->value);
$this->assertTrue($node->isPublished());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing test coverage for media update-after-authoring.

Given the guard in ModerationPolicyHook::entityPresave() (Line 53-55 of that file) applies to both the node and media branches, consider adding a companion test asserting the expected behavior when an API-authored media entity's moderation_state is changed on a subsequent update — this exercises the concern raised in ModerationPolicyHook.php.

🧰 Tools
🪛 PHPMD (2.15.0)

[error] 189-193: Avoid using static access to class '\Drupal\node\Entity\Node' in method 'testApiPagePublishAfterAuthoringIsHonoured'. (undefined)

(StaticAccess)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/modules/custom/do_content_api/tests/src/Kernel/Hook/ModerationPolicyHookTest.php`
around lines 180 - 204, Add a companion kernel test in ModerationPolicyHookTest
next to testApiPagePublishAfterAuthoringIsHonoured that covers the media path in
ModerationPolicyHook::entityPresave(). Create an API user with the content
authoring permission, create a media entity in draft as an authored save, then
update the same entity to published and assert the later save keeps the
published moderation_state and published status. Use the existing entityPresave
node/media branching as the target behavior to verify.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d5ff521. Added testApiMediaUpdateAfterAuthoringIsHonoured, which authors a media (coerced to published on create) then updates it to draft and asserts the later change is honoured - covering the media branch of the isNew guard alongside the node test.

@github-actions

This comment has been minimized.

@codecov-commenter

codecov-commenter commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.16%. Comparing base (2d134fe) to head (d5ff521).
⚠️ Report is 3 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #245      +/-   ##
===========================================
+ Coverage    89.05%   89.16%   +0.10%     
===========================================
  Files           15       15              
  Lines          201      203       +2     
===========================================
+ Hits           179      181       +2     
  Misses          22       22              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Code coverage (threshold: 80%)

  Classes: 50.00% (7/14)
  Methods: 73.33% (22/30)
  Lines:   93.18% (355/381)
Per-class coverage
Drupal\do_content_api\EventSubscriber\JsonApiWriteGateSubscriber
  Methods:  66.67% ( 2/ 3)   Lines:  88.89% (  8/  9)
Drupal\do_content_api\Hook\EntityCreateAccessHook
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  9/  9)
Drupal\do_content_api\Hook\ModerationPolicyHook
  Methods:  50.00% ( 1/ 2)   Lines:  93.75% ( 15/ 16)
Drupal\do_content_api\Routing\RouteSubscriber
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  6/  6)
Drupal\do_feed\FeedUrlBuilder
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 18/ 18)
Drupal\do_feed\Form\FeedSettingsForm
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
Drupal\do_feed\Hook\EntityDeleteHook
  Methods:  50.00% ( 1/ 2)   Lines:  92.31% ( 12/ 13)
Drupal\do_feed\Hook\EntityPresaveHook
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 54/ 54)
Drupal\do_feed\Hook\PreprocessParagraphHook
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 14/ 14)
Drupal\do_feed\Hook\PreprocessViewsViewRowRssHook
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  3/  3)
Drupal\do_feed\Hook\ViewsPreViewHook
  Methods:  50.00% ( 1/ 2)   Lines:  96.43% ( 27/ 28)

@AlexSkrypnyk AlexSkrypnyk merged commit ff94049 into develop Jul 1, 2026
9 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/fix-editor-publish branch July 1, 2026 11:05
@AlexSkrypnyk AlexSkrypnyk added the Needs review Pull request needs a review from assigned developers label Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs review Pull request needs a review from assigned developers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants