Implement StandardEdition Parent/Child relationships with semi-independent publishing workflows [WHIT-3341]#11431
Draft
ChrisBAshton wants to merge 22 commits into
Draft
Conversation
5731b5c to
62dc978
Compare
8fc1b77 to
05818d2
Compare
We need to recreate the ParentChildRelationship when a new edition of a Parent is created. We don't need to do the same when a new edition of a _Child_ is created, since we reference the child by its Document ID rather than its Edition ID.
This method looks up the document types that are allowed to be a child of the given document, based on its configuration. We will call this method when rendering the child document creation form.
We'll direct publishers to this page when they choose to create a child document on the Parent document they're in. See subsequent commits.
- Adds Topical Event About Page config - Adds `allowed_child_document_types` to Topical Event - Tightens up schema validation to allow no unexpected properties in `settings` - Extracts enum values for document type, so we can avoid duplicating the list and we can validate both settings.publishing_api_document_type and allowed_child_document_types[n].document_type use only valid document types. - Removes unnecessary unit test that was basically just testing the implementation of JSON schema enum validation. Note that, as things currently stand, Publishing API rejects us sending organisations as part of details or links for Topical Event About pages - so we've deliberately omitted them here, as per the legacy experience.
Non StandardEdition editions always return false here, as we only support Parent/Child relationships on StandardEdition constructs. StandardEdition editions explicitly support child documents if we've provided a list of `allowed_child_document_types` in their config.
We'll set the latter on topical event about pages. This is because the base path prefix will be determined by the parent. 'is_child_document' as a property won't be used for the time being. This is more as a signal that the absence of a 'base_path_prefix' property is NOT a mistake, where otherwise it would be impossible to tell without looking at the `allowed_child_document_types` value of every other document type. In future we may find it useful to be able to look up the `is_child_document` boolean.
Note: Topical Event About pages (legacy) have a hard-coded slug
of "/about". This commit fixes the prefix so that it will come
out as "/government/topical-events/{the-topical-event}/{name-of-doc}",
but the {name-of-doc} bit is currently still dynamic. We can look
at that in a future commit if it's deemed essential.
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."
89cf99f to
7fb592c
Compare
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!
7fb592c to
0cfe91e
Compare
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.
0cfe91e to
2e107cc
Compare
ChrisBAshton
added a commit
that referenced
this pull request
May 21, 2026
As per #11431 (review) This is more descriptive.
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
As per #11431 (review) This is more descriptive.
682c7e2 to
6a8fd44
Compare
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.
As per #11431 (review) This is more descriptive.
6a8fd44 to
62b6b42
Compare
Contributor
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 👍 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Rule 2: Prevent orphaned children
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
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
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)
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:
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:
Creating a new child document takes you to a screen where you can choose from the subset of allowed child document types:
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:
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_typeandslug- 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.)The child document now appears on the parent document summary screen:
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:
After publishing the child document, it is visible in Whitehall Search. We may want to change this (out of scope for this PR):
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.