Skip to content

Validate each document tab form separately [WHIT-3214]#11436

Open
eYinka wants to merge 8 commits into
mainfrom
validate-doc-tab-separately
Open

Validate each document tab form separately [WHIT-3214]#11436
eYinka wants to merge 8 commits into
mainfrom
validate-doc-tab-separately

Conversation

@eYinka
Copy link
Copy Markdown
Contributor

@eYinka eYinka commented May 8, 2026

What

Move the edition validation off of the edition model on to the configurable form object, and validate each form independently. Make sure that all forms are validated before the edition can be published.

In the scope of this implementation:

  • Errors for the form are rendered to the user at the top of the page and next to the invalid form field, with a link from the top of the page to the invalid form
  • Any edition tab’s form can be saved regardless of the validity of the other tabs
  • All edition forms are validated before the “Publish” button is displayed to the user, and any errors are displayed to the user on the summary page.
  • Only a globally valid edition is updated in Publishing API
  • Users can see a hint text letting them know draft previews may be out of date if an edition has invalid tabs

Why

  • Accelerates future development of configurable document types with a more appropriate abstraction
  • Decouples form validation and attributes from the Edition model

Visual Changes

image

Testing

Experiencing the full benefit of this work is tricky, because you will almost never have a saved multi-tab edition in bad state unless you manually manipulate the data.
Currently, the only way to access dynamic tabs is by saving the document tab first, which means the draft is fully valid before any subsequent updates can be made. Therefore, it's not inherently clear that we are scoping validations per tab.

For anyone that wants to give it a go locally, an easy way is to configure a document type (like topical event) with "send_change_history": true, then:

  • Redraft a published topical event - this should surface the change history form on the document tab.
  • Without filling the change history form, try to save another tab (i.e Social media links) with invalid data. You should only see errors related to social media tab and nothing about the invalid change history should be displayed. Also you will be able to see a summary of the invalid tabs on the edition summary page.
  • Alternatively, you can grab a draft edition and manipulate the data by doing the following:
# Grab the edition
edition = Edition.find(INSERT_EDITION_ID)

# Manipulate the block content by setting the body to nil and storing an invalid social media link
new_content = edition.translation[:block_content].merge("body" => nil, "social_media_links" => [{ "url" => nil, "social_media_service_name" => "Facebook" }])

# Persist the data
edition.translation.update_column(:block_content, new_content)

Future Improvements:

  • When all document types have been migrated to using config-driven tab forms, we should be able to remove validate_block_content and scoped_block_content from has_block_content.rb.
    TabForm will then be the single point for handling all block_content validation. For now, it's fine to keep the validation in both places because not all tabs are currently fully config-driven, and edition.valid? is called in several places that don't go through TabForm.
  • Add title and summary to the documents schema of all config driven document types
  • In the spirit of maintaining consistency, enforce adding a label to each form in document type configurations.
  • Maintain a single way to access the documents tab. Currently, it is accessible via /standard-editions/:id/edit and /standard-editions/:id/edit?current_tab=documents. The latter is necessary for validating only the documents tab while the former will assume full edition validation.

JIRA


⚠️ This repo is Continuously Deployed: make sure you follow the guidance ⚠️

This application is owned by the Whitehall Experience team. Please let us know in #govuk-whitehall-experience-tech when you raise any PRs.

Follow these steps if you are doing a Rails upgrade.

@eYinka eYinka force-pushed the validate-doc-tab-separately branch 13 times, most recently from 8644b69 to afd0dfb Compare May 14, 2026 15:15
eYinka added 3 commits May 14, 2026 17:39
In Standard Edition model, we have these association checks:
- `organisation_association_enabled`
- `worldwide_organisation_association_required?`
- `world_location_association_required?`

And they are responsible for enabling validations on their associated fields each time an edition is updated.
They now return false if the field in question doesn't belong to the current tab context (i.e If you're on a different tab that doesn't include the associated fields).

I've added `current_tab_context_includes_field?` to centralise that check.
Introducing a form object that validates a single tab's fields in isolation. It separates block_content fields (which are validated via a scoped BlockContent) from edition attribute fields.

Edition attribute fields in this context are fields that are meant to be validated on the edition. Rather than duplicating code to represent them specially for standard edition, we handle their validation  by running `edition.valid?` under a temporary tab context, then cherry-picking only that tab's errors for displaying to the user later.

This is the core of the per-tab validation feature and should be hopefully sufficient for what we need.

**Note:** The inclusion of `ADDITIONAL_DEFAULT_TAB_FIELDS` brings a question for future work.
We should be looking at including `title` and `summary` in the document's tab schema of each configurable document type.
We needed a way to validate only a part of an edition, in the case of config-driven form tabs i.e when a user is saving data on a particular tab, they should only see errors related to that tab.

Consequently, we will need to skip the full edition validation when patching up that chunk of the edition. Rails allows us to specify options like `validate: false` to do that.
This commit prepares the save handler to accept such options.
@eYinka eYinka force-pushed the validate-doc-tab-separately branch 2 times, most recently from 7fec067 to e20c4bf Compare May 14, 2026 16:50
@eYinka eYinka marked this pull request as ready for review May 14, 2026 16:51
eYinka added 5 commits May 15, 2026 14:57
When a tab context is present (i.e. user is saving a non-document tab), validate only that tab's fields via TabForm form object instead of running full edition validation. On success, we call save with `validate: false`. On failure, we simply copy tab errors onto `@edition.errors` so that the existing error summary component displays them correctly.

For the non-tab pages, fall back to the parent controller's handler and run full validation.
This will be useful in future commit to iterate over a configurable document's form tabs
Now, we compute which tabs are invalid and surface them through to the sidebar component.
The sidebar will list every invalid tab as links rather than a flat list of errors for StandardEditions.
I've also added a hint text to the preview edition component to let publishers know that the preview of an edition "may be out of date" if any tab is invalid; as we will not send an edition in bad state to publishing API.
The Document tab nav item was only highlighted when current_tab was nil. Passing `?current_tab=documents` (e.g. from an error link) left it non-highlighted. I've added `on_default_tab` which is true when `current_tab` is either nil (implicitly defaults to documents tab at the moment) or equals `edition.default_tab`, so both forms of "on the default tab" are handled.
We have agreed to only send a globally valid edition to Publishing API. In that sense, the Edition Publisher and Edition Scheduler services now collect each tab's failure reasons (e.g. "Social media accounts tab is invalid") so publishers see useful messages rather than a generic one.

Consequently, the Draft Edition Updater service skips pushing to Publishing API if any tab is invalid.
@eYinka eYinka force-pushed the validate-doc-tab-separately branch from e20c4bf to eb62998 Compare May 15, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant