Skip to content

Implement StandardEdition Parent/Child relationships with semi-independent publishing workflows [WHIT-3341] - #11431

Draft
ChrisBAshton wants to merge 22 commits into
mainfrom
spike-independently-editable-standardedition-children
Draft

Implement StandardEdition Parent/Child relationships with semi-independent publishing workflows [WHIT-3341]#11431
ChrisBAshton wants to merge 22 commits into
mainfrom
spike-independently-editable-standardedition-children

Conversation

@ChrisBAshton

@ChrisBAshton ChrisBAshton commented May 7, 2026

Copy link
Copy Markdown
Contributor

What

This PR implements a generic multi-page architecture and applies it to Topical Events and their 'About' pages (dependent on alphagov/publishing-api#3860).

Child documents have largely independent publishing workflows, by design - this allows publishers to make changes to their child documents without having to do everything through the parent. They can't be totally independent though, otherwise we could end up with orphaned children, live children with unpublished parents and so on. So we've encoded four simple, sensible rules that constrain the lifecycles and help keep states predictable and valid. See Mural for some of the thinking.

Rules

Rule 1: New child drafts

A child document can only be created on a parent in a pre-publication state.

Rule 2: Prevent orphaned children

A parent draft may not be discarded while it has associated child documents whose only edition is linked to that parent draft.

We could offer to drop the draft documents automatically when dropping the parent. But the simpler solution for now is to raise a validation error and force the publisher to drop the child documents manually first.

Rule 3: Child publishability

A draft child may only be published if its parent document’s live_edition is "published" and the parent edition it is linked to is not in a pre-publication state.

The simplest implementation, applied here, is to force the publisher to publish the parent (draft) document, then publish the child document.

In future, I expect we'd offer some sort of "publish all (or a subset of) draft children with the parent" feature in future, where the operation order would be that the parent is published first, thereby allowing said children to be published in turn. However, whilst we can surface that kind of UX on the Publish / Force-Publish pages, we can't do the equivalent for schedule-publishing until we tackle WHIT-1887.

Rule 4: Parent visibility ceiling

A child document may not be more publicly visible than its parent document.

Parent Allowed child states
published published / withdrawn / unpublished
withdrawn withdrawn / unpublished
unpublished unpublished

Implementation wise, we could auto-cascade such that if you unpublish the parent, you unpublish its children. But it gets messy if there exists a draft child - do you auto-discard the draft in order to unpublish it? Etc. So to begin with at least, we will force the publisher to manually withdraw/unpublish (as appropriate) all of a document's children before withdrawing/unpublishing the parent document. This simplified interpretation can be summarised as "You cannot withdraw/unpublish a parent while any children remain more publicly visible than it."

Out of scope (future tickets)

  • Limiting the number of About pages that can be created.
  • (if deemed necessary) Hard-coding the slug to "/about", rather than having it determined by title.
  • Redirect locations. Currently when publishing a Child document, you're redirected to the Document Search screen, but should probably be redirected to the Parent summary screen instead? Same with the cancel link on the "New child document" form.
  • Hide Topical Event About pages from Whitehall search (if needed)
  • Offer option to - or just go ahead and action - deletion of 'new' child document when deleting its draft parent. (See Rule 2)
  • Offer option to publish all or a subset of child pages at the same time as the parent (see Rule 3)
  • "Through the parent" publishing, e.g. Publications & HTML Attachments, where the child document is 'part of the whole' and has a shared change history, shared last_updated timestamp, can't be edited independently, etc. Architecturally this should be possible by writing some additional constraints and config options on top of what we have here.

Why

Whitehall has committed to migrating Topical Events in their entirety, to our config-driven 'StandardEdition' model. Unlike the already migrated content types, Topical Events aren't standalone content items - many of them have corresponding 'About' pages too, which need migrating.

Looking more widely, there are other microsite-like and multi-part document structures we hope to consolidate into StandardEdition in future, including:

  • Publications (and HTML attachments)
  • Plan for Change pages (and their children)
  • Organisations (and their Corporate Information Pages)
  • Travel Advice (summary and individual sections)
  • Manuals (and manual sections)

So we've had to implement a multi-part architecture that can scale to accommodate all of those content types and their requirements. As in the 'out of scope' section, we're likely to need to add some configuration options and support 'top-down' / 'through-the-parent' publishing for some of them. We're also likely to need to introduce the concept of 'ordered' child pages (e.g. for HTML attachments). None of what we have built here locks us out from those future iterations.

Jira: https://gov-uk.atlassian.net/browse/WHIT-3341

Screenshots

We have a new "Child documents" section (hidden behind a feature flag) on Topical Event document summary pages:

Screenshot 2026-05-14 at 16 07 25

Creating a new child document takes you to a screen where you can choose from the subset of allowed child document types:

Screenshot 2026-05-14 at 16 08 26

You're taken to a StandardEdition new document form as normal - the only difference being the additional call-out reinforcing that this is being created as a child document of the given parent:

Screenshot 2026-05-14 at 16 08 43

The newly created document is again, just a StandardEdition - but with a callout at the top of the page, linking it back to the parent. Note that the publish/force-publish button is deliberately hidden, since we need the parent to be published before we allow the 'first publish' of the child (subsequent publishes are unrestricted).

(Note also the warning about the same title/slug - a growing problem and a symptom of the fact our global uniqueness check logic isn't compatible with consolidating onto StandardEdition, as we check document_type and slug - in this case 'StandardEdition' and 'About' - and unnecessarily add a --2/--3/... suffix, as the base path prefix here is actually unique. This will be tackled in WHIT-3371.)

Screenshot 2026-05-14 at 16 09 30 note validation error and lack of publish button

The child document now appears on the parent document summary screen:

Screenshot 2026-05-14 at 16 10 18

After publishing the parent, the callout text at the top of the child document is now simplified, and we can see the publish actions in the sidebar:

Screenshot 2026-05-14 at 16 12 16 after publishing parent

After publishing the child document, it is visible in Whitehall Search. We may want to change this (out of scope for this PR):

Screenshot 2026-05-14 at 16 14 35

⚠️ 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.

@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch 11 times, most recently from 5731b5c to 62dc978 Compare May 8, 2026 16:29
@ChrisBAshton ChrisBAshton changed the title Spike independently editable StandardEdition Parent/Child relationships [WHIT-3341] Implement StandardEdition Parent/Child relationships with semi-independent publishing workflows [WHIT-3341] May 8, 2026
@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch 18 times, most recently from 8fc1b77 to 05818d2 Compare May 14, 2026 15:21
Starts a feature file describing creating a child document from the
parent.

Over the next few commits, we'll introduce the agreed business
rules so that we can keep the documents in a reasonable synchronised
state (avoiding orphaned children, or children that are live when
the parent is unpublished, and so on).

NB: I used the same design for surfacing Child Documents on the
document summary page, as exists for listing corporate information
pages on Organisations, since it seems useful to surface the state
of the child documents here too. It uses the same 'fake edition
filter' approach as that.
A child document can only be created on a parent in a pre-publication state.
We'll use this to implement Rule 2.
Rule 2 is to disallow publishers from discarding a draft parent if
it contains a draft child that has never been published (in order
to avoid creating an orphaned child).
But before we do that, we need to make sure if the publisher deletes the child, that
validation error doesn't happen! This commit accomplishes that.
A parent draft may not be discarded while it has associated child documents whose only edition is linked to that parent draft.

We could offer to drop the draft documents automatically when dropping the parent. But the simpler solution for now is to raise a validation error and force the publisher to drop the child documents manually first.
A draft child may only be published if its parent document’s live_edition is "published" and its associated parent edition is not in a pre-publication state.

The simplest implementation is to force the publisher to publish
the parent (draft) document, then publish the child document. But I
expect before too long we'll offer some sort of "publish all (or a
subset of) draft children with the parent" feature in future, where
the operation order would be that the parent is published first,
thereby allowing said children to be published in turn.
A child document may not be more publicly visible than its parent document.

| Parent      | Allowed child states                |
|-------------|-------------------------------------|
| published   | published / withdrawn / unpublished |
| withdrawn   | withdrawn / unpublished             |
| unpublished | unpublished                         |

Implementation wise, we **could** auto-cascade such that if you unpublish the parent, you unpublish its children. But it gets messy if there exists a draft child - do you auto-discard the draft in order to unpublish it? Etc. So to begin with at least, we will force the publisher to manually withdraw/unpublish (as appropriate) all of a document's children before withdrawing/unpublishing the parent document. This simplified interpretation can be summarised as "You cannot withdraw/unpublish a parent while any children remain more publicly visible than it."
@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch from 89cf99f to 7fb592c Compare May 21, 2026 12:00
ChrisBAshton added a commit that referenced this pull request May 21, 2026
Resolves #11431 (comment)

Agreed this is a better place. I think I forgot this controller
inherits from EditionsController so can hook into the update
method!
@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch from 7fb592c to 0cfe91e Compare May 21, 2026 12:19
ChrisBAshton added a commit that referenced this pull request May 21, 2026
Resolves #11431 (comment)

Suggestion was to use ChildDocumentsController but that would
involve conditionally routing some document creation to that
controller and others to StandardEditionController. So have
gone for the latter instead, since only StandardEdition supports
child documents - so still a better place.

Have also taken on the suggestion of testing the behaviour via
`create` rather than testing a private method directly.
@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch from 0cfe91e to 2e107cc Compare May 21, 2026 13:21
ChrisBAshton added a commit that referenced this pull request May 21, 2026
ChrisBAshton added a commit that referenced this pull request May 21, 2026
Resolves #11431 (comment)

Suggestion was to use ChildDocumentsController but that would
involve conditionally routing some document creation to that
controller and others to StandardEditionController. So have
gone for the latter instead, since only StandardEdition supports
child documents - so still a better place.

Have also taken on the suggestion of testing the behaviour via
`create` rather than testing a private method directly.
ChrisBAshton added a commit that referenced this pull request May 21, 2026
@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch from 682c7e2 to 6a8fd44 Compare May 21, 2026 14:19
Resolves #11431 (comment)

Suggestion was to use ChildDocumentsController but that would
involve conditionally routing some document creation to that
controller and others to StandardEditionController. So have
gone for the latter instead, since only StandardEdition supports
child documents - so still a better place.

Have also taken on the suggestion of testing the behaviour via
`create` rather than testing a private method directly.
@ChrisBAshton
ChrisBAshton force-pushed the spike-independently-editable-standardedition-children branch from 6a8fd44 to 62b6b42 Compare May 21, 2026 14:28
@lauraghiorghisor-tw

lauraghiorghisor-tw commented May 22, 2026

Copy link
Copy Markdown
Contributor

I very much anticipate this for modelling classic parent/child relationships only

I do wonder if that's going to be the case though 😅 It might just be that at that point extending this relationship makes most sense. Either way, I agree with only for now, because trying to cater for all types in this implementation, from the start, is too much.

I like the idea of Rule 0 for clarity 👍

ChrisBAshton added a commit that referenced this pull request Jun 11, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 15, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 15, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 15, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 15, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 15, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 20, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
ChrisBAshton added a commit that referenced this pull request Jun 24, 2026
As introduced in draft PR #11431. Sometimes we just want a generic
Whitehall error, or a way of catching multiple Whitehall errors,
without accidentally swallowing unexpected errors like NoMethodError
etc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants