Implement StandardEdition Parent/Child relationships with semi-independent publishing workflows [WHIT-3341]#11431
Draft
ChrisBAshton wants to merge 19 commits into
Draft
Conversation
5731b5c to
62dc978
Compare
f30dcce to
8fc1b77
Compare
We're going to start modelling the concept of "parent" and "child"
documents, e.g. Topical Event pages (parent) and Topical Event
About pages (child). Parent/child docs are defined as having:
- A shared base path prefix (e.g. `/topical-events/foo` and
`/topical-events/foo/about`)
- Interlinked publishing workflows, e.g. we may want to publish
both parent and child at the same time, we may need to consolidate
their change notes, coordinate their 'last updated' time, etc.
That behaviour is TBC and is likely to be config-driven.
We could have modelled this in several different ways, each with
their own pros and cons:
1. Document -> Document
- Simplest to model. No ongoing updates required when we create
new editions of either parent or child.
- But lossy. No way of knowing which edition of the parent a
child was created on, etc.
2. Edition -> Document
- Reasonably simple to model. Parent-centric view of child
document creation. Every time a new edition of the parent is
created, we create a new row in ParentChildRelationships, but
otherwise we don't need to have equivalent callbacks when
creating a new edition of the Child.
3. Edition -> Edition
- Maximally flexible, but difficult to comprehend. Rows need
creating whenever a parent or child edition is created, and
kept in sync when discarding drafts etc,
4. Document -> Edition
- Not really any advantage here. Lossy _and_ overly complicated.
Approach 1 and 3 were spiked in #11026. We've gone for Approach 2
here.
Note that we will one day likely need an 'ordering' attribute too,
for having ordered HTML attachments on a Publication. But that is
YAGNI for now.
This will make querying a document's parent or child much easier. See subsequent commits.
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."
8fc1b77 to
05818d2
Compare
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.