Skip to content

Commit 131c6b2

Browse files
authoredNov 2, 2022
Contentful: Migrate away from legacy Compose (#12133)
* Add set of Contentful-related migrations that migrate us away from the "Legacy Compose" pattern. This is based on https://www.contentful.com/developers/docs/compose/upgrade-to-customizable-compose-content-model/ plus investigations by @maureenlholland. The migrations are kept separate so they can be applied individually and progress monitored. Also, it makes sense to 'commit' each step separately, rather than risk locking/race issues with the Contentful API's datastore. (This is an assumption on my part - maybe we _could_ do all this in one go.) First one: Adds new fields to any page type that previously used Legacy Compose (In our case that's just the pagePageResourceCenter content type.) Second one: Populates data, drawing from the Compose:Page entry and setting it on the pagePageResourceCenter entries. Third one: cleanup, unpublishing and deleting the Compose:Page entries and then removing it as a content type entirely * Update backend Python code to match updated page Schema now we have dropped legacy Compose Note that the Connect:Homepage approach still leaves us some niggles that we should ideally get past soon, too * Improve ordering of fields for pagePageResourceCenter via Contentful migration Example of it running: Update Content Type pagePageResourceCenter Move field title after field name Move field slug after field title Move field seo after field slug Publish Content Type pagePageResourceCenter Update Content Type pagePageResourceCenter Migration successful * Add ADR for how we're handling Contentful migrations * Update README for Contentful migrations approach * Update README and ADR with typo fixes * Typo fixups, following code review * Add Contentful migration to make Title and Slug on Resource Center pages required * Add Contentful migration to force-publish all Resource Center pages
1 parent 6becad8 commit 131c6b2

22 files changed

+4483
-124
lines changed
 

‎.editorconfig

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ insert_final_newline = true
1010
trim_trailing_whitespace = true
1111
indent_style = space
1212

13-
[*.{py,js,css,scss,sh,groovy}]
13+
[*.{py,js,cjs,css,scss,sh,groovy}]
1414
indent_size = 4
1515

1616
[*.{json,html,svg,yml}]

‎.eslintrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
* License, v. 2.0. If a copy of the MPL was not distributed with this
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
6-
6+
/* eslint-env es6 */
77
module.exports = {
88
env: {
99
browser: true,
1010
commonjs: true
1111
},
1212
extends: ['eslint:recommended', 'plugin:json/recommended', 'prettier'],
13+
ignorePatterns: ['contentful_migrations/migrations/*.cjs'],
1314
rules: {
1415
// Require strict mode directive in top level functions
1516
// https://eslint.org/docs/rules/strict

‎bedrock/contentful/api.py

+7-23
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from rich_text_renderer.text_renderers import BaseInlineRenderer
1818

1919
from bedrock.contentful.constants import (
20-
COMPOSE_MAIN_PAGE_TYPE,
2120
CONTENT_TYPE_PAGE_GENERAL,
2221
CONTENT_TYPE_PAGE_RESOURCE_CENTER,
2322
)
@@ -479,17 +478,8 @@ def _get_preview_image_from_fields(self, fields):
479478
return f"https:{preview_image_url}"
480479

481480
def _get_info_data__slug_title_blurb(self, entry_fields, seo_fields):
482-
483-
if self.page.content_type.id == COMPOSE_MAIN_PAGE_TYPE:
484-
# This means we're dealing with a Compose-structured setup,
485-
# and the slug lives not on the Entry, nor the SEO object
486-
# but just on the top-level Compose `page`
487-
slug = self.page.fields().get("slug")
488-
else:
489-
# Non-Compose pages
490-
slug = entry_fields.get("slug", "home") # TODO: check if we can use a better fallback
491-
492-
title = getattr(self.page, "title", "")
481+
slug = entry_fields.get("slug", "home") # TODO: check if we can use a better fallback
482+
title = entry_fields.get("title", "")
493483
title = entry_fields.get("preview_title", title)
494484
blurb = entry_fields.get("preview_blurb", "")
495485

@@ -542,7 +532,6 @@ def _get_info_data__locale(self, page_type, entry_fields, entry_obj):
542532
return {"locale": locale}
543533

544534
def get_info_data(self, entry_obj, seo_obj=None):
545-
# TODO, need to enable connectors
546535
entry_fields = entry_obj.fields()
547536
if seo_obj:
548537
seo_fields = seo_obj.fields()
@@ -591,20 +580,15 @@ def get_info_data(self, entry_obj, seo_obj=None):
591580
return data
592581

593582
def get_content(self):
594-
# Check if it is a page or a connector, or a Compose page type
595-
583+
# Check if it is a page or a connector
596584
entry_type = self.page.content_type.id
597585
seo_obj = None
598-
if entry_type == COMPOSE_MAIN_PAGE_TYPE:
599-
# Contentful Compose page, linking to content and SEO models
600-
entry_obj = self.page.content # The page with the actual content
601-
seo_obj = self.page.seo # The SEO model
602-
# Note that the slug lives on self.page, not the seo_obj.
603-
elif entry_type.startswith("page"):
604-
entry_obj = self.page
605-
elif entry_type == "connectHomepage":
586+
if entry_type == "connectHomepage":
606587
# Legacy - TODO: remove me once we're no longer using Connect: Homepage
607588
entry_obj = self.page.fields()["entry"]
589+
elif entry_type.startswith("page"): # WARNING: this requires a consistent naming of page types in Contentful, too
590+
entry_obj = self.page
591+
seo_obj = self.page.seo
608592
else:
609593
raise ValueError(f"{entry_type} is not a recognized page type")
610594

‎bedrock/contentful/constants.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
6-
# Which models do we want to sync?
7-
8-
9-
COMPOSE_MAIN_PAGE_TYPE = "page"
105
MAX_MESSAGES_PER_QUEUE_POLL = 10
116

127
# Specific content types we need to target in DB lookups
@@ -15,15 +10,13 @@
1510
CONTENT_TYPE_PAGE_GENERAL = "pageGeneral"
1611

1712
DEFAULT_CONTENT_TYPES = ",".join(
18-
# Soon, we'll only need to sync the Compose-driven `page` type, but
19-
# until then we also still have homepages set with the Connect pattern
2013
[
21-
CONTENT_TYPE_CONNECT_HOMEPAGE, # The Connect-based approach, used for the homepage
22-
COMPOSE_MAIN_PAGE_TYPE, # General Compose Page type - the related `content` type's name is what we store in the DB
14+
CONTENT_TYPE_CONNECT_HOMEPAGE, # The Connect-based approach, currently used for the homepage
15+
CONTENT_TYPE_PAGE_RESOURCE_CENTER, # New-era Compose page with a dedicated type
2316
]
2417
)
2518

26-
CONTENT_CLASSIFICATION_VPN = "VPN" # Matches string in Contenful for VPN as `product`
19+
CONTENT_CLASSIFICATION_VPN = "VPN" # Matches string in Contentful for VPN as `product`
2720

2821
ARTICLE_CATEGORY_LABEL = "category" # for URL-param filtering
2922

‎bedrock/contentful/management/commands/update_contentful.py

-8
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
ACTION_SAVE,
2626
ACTION_UNARCHIVE,
2727
ACTION_UNPUBLISH,
28-
COMPOSE_MAIN_PAGE_TYPE,
2928
CONTENT_TYPE_CONNECT_HOMEPAGE,
3029
MAX_MESSAGES_PER_QUEUE_POLL,
3130
)
@@ -362,13 +361,6 @@ def _refresh_from_contentful(self) -> Tuple[int, int, int]:
362361
error_count += 1
363362
continue
364363

365-
# Compose-authored pages have a page_type of `page`
366-
# but really we want the entity the Compose page references
367-
if ctype == COMPOSE_MAIN_PAGE_TYPE:
368-
# TODO: make this standard when we _only_ have Compose pages,
369-
# because they all have a parent type of COMPOSE_MAIN_PAGE_TYPE
370-
ctype = page_data["page_type"]
371-
372364
hash = data_hash(page_data)
373365
_info = page_data["info"]
374366

‎bedrock/contentful/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def get_entries_by_type(
6060
order_by (str, optional): Sorting key for the queryset. Defaults to "last_modified".
6161
6262
Returns:
63-
QuerySet[ContentfulEntry]: the main ContenfulEntry models, not just their JSON data
63+
QuerySet[ContentfulEntry]: the main ContentfulEntry models, not just their JSON data
6464
"""
6565

6666
kwargs = dict(

‎bedrock/contentful/tests/test_contentful_api.py

+38-36
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
get_client,
4444
)
4545
from bedrock.contentful.constants import (
46-
COMPOSE_MAIN_PAGE_TYPE,
4746
CONTENT_TYPE_CONNECT_HOMEPAGE,
4847
CONTENT_TYPE_PAGE_RESOURCE_CENTER,
4948
)
@@ -681,7 +680,7 @@ def test__render_list():
681680

682681
@pytest.fixture
683682
def basic_contentful_page(rf):
684-
"""Naive reusable fixutre for setting up a ContentfulPage
683+
"""Naive reusable fixture for setting up a ContentfulPage
685684
Note that it does NOTHING with set_current_request / thread-locals
686685
"""
687686
with patch("bedrock.contentful.api.set_current_request"):
@@ -868,84 +867,90 @@ def test_ContentfulPage__get_info_data__locale(
868867

869868

870869
@pytest.mark.parametrize(
871-
"page_title, page_type, page_fields, entry_fields, seo_fields, expected",
870+
"page_type, legacy_connect_page_fields, entry_fields, seo_fields, expected",
872871
(
873872
(
874-
"test page one",
875-
COMPOSE_MAIN_PAGE_TYPE,
876-
{"slug": "compose-main-page-slug"},
877-
{},
878-
{},
873+
CONTENT_TYPE_PAGE_RESOURCE_CENTER,
874+
{}, # Connect-type fields
879875
{
880-
"slug": "compose-main-page-slug",
876+
"slug": "vrc-main-page-slug",
877+
"title": "test page one",
878+
}, # fields from the page itself
879+
{}, # SEO object's fields
880+
{
881+
"slug": "vrc-main-page-slug",
881882
"title": "test page one",
882883
"blurb": "",
883884
},
884885
),
885886
(
886-
"",
887-
COMPOSE_MAIN_PAGE_TYPE,
888-
{"slug": "compose-main-page-slug"},
887+
CONTENT_TYPE_PAGE_RESOURCE_CENTER,
889888
{},
889+
{
890+
"slug": "vrc-main-page-slug",
891+
"title": "",
892+
},
890893
{},
891894
{
892-
"slug": "compose-main-page-slug",
895+
"slug": "vrc-main-page-slug",
893896
"title": "",
894897
"blurb": "",
895898
},
896899
),
897900
(
898-
"",
899-
COMPOSE_MAIN_PAGE_TYPE,
900-
{"slug": "compose-main-page-slug"},
901+
CONTENT_TYPE_PAGE_RESOURCE_CENTER,
902+
{},
901903
{
902904
"preview_title": "preview title",
903905
"preview_blurb": "preview blurb",
906+
"slug": "vrc-main-page-slug",
907+
"title": "",
904908
},
905909
{},
906910
{
907-
"slug": "compose-main-page-slug",
911+
"slug": "vrc-main-page-slug",
908912
"title": "preview title",
909913
"blurb": "preview blurb",
910914
},
911915
),
912916
(
913-
"",
914-
COMPOSE_MAIN_PAGE_TYPE,
915-
{"slug": "compose-main-page-slug"},
917+
CONTENT_TYPE_PAGE_RESOURCE_CENTER,
918+
{},
916919
{
917920
"preview_title": "preview title",
918921
"preview_blurb": "preview blurb",
922+
"slug": "vrc-main-page-slug",
923+
"title": "",
919924
},
920925
{"description": "seo description"},
921926
{
922-
"slug": "compose-main-page-slug",
927+
"slug": "vrc-main-page-slug",
923928
"title": "preview title",
924929
"blurb": "seo description",
925930
},
926931
),
927932
(
928-
"page title",
929933
CONTENT_TYPE_CONNECT_HOMEPAGE,
930-
{},
931934
{
932-
"slug": "homepage-slug",
935+
"slug": "homepage-slug", # This will be ignored
936+
},
937+
{
933938
"preview_title": "preview title",
934939
"preview_blurb": "preview blurb",
935940
},
936941
{}, # SEO fields not present for non-Compose pages
937942
{
938-
"slug": "homepage-slug",
943+
"slug": "home", # ie, there is no way to set the slug using Connect:Homepage
939944
"title": "preview title",
940945
"blurb": "preview blurb",
941946
},
942947
),
943948
(
944-
"page title",
945949
CONTENT_TYPE_CONNECT_HOMEPAGE,
946-
{},
947950
{
948951
# no slug field, so will fall back to default of 'home'
952+
},
953+
{
949954
"preview_title": "preview title",
950955
"preview_blurb": "preview blurb",
951956
},
@@ -962,26 +967,21 @@ def test_ContentfulPage__get_info_data__locale(
962967
"compose page with slug, no title, no blurb",
963968
"compose page with slug, title from entry, blurb from entry",
964969
"compose page with slug, no title, blurb from seo",
965-
"Non-Compose page with slug, title, blurb from entry",
970+
"Non-Compose page with title, blurb from entry + PROOF SLUG IS NOT SET",
966971
"Non-Compose page with default slug, title, blurb from entry",
967972
],
968973
)
969974
def test_ContentfulPage__get_info_data__slug_title_blurb(
970975
basic_contentful_page,
971-
page_title,
972976
page_type,
973-
page_fields,
977+
legacy_connect_page_fields,
974978
entry_fields,
975979
seo_fields,
976980
expected,
977981
):
978982
basic_contentful_page.page = Mock()
979983
basic_contentful_page.page.content_type.id = page_type
980-
basic_contentful_page.page.fields = Mock(return_value=page_fields)
981-
if page_title:
982-
basic_contentful_page.page.title = page_title
983-
else:
984-
basic_contentful_page.page.title = ""
984+
basic_contentful_page.page.fields = Mock(return_value=legacy_connect_page_fields)
985985

986986
assert (
987987
basic_contentful_page._get_info_data__slug_title_blurb(
@@ -1049,6 +1049,7 @@ def test_ContentfulPage__get_info_data__category_tags_classification(
10491049
{
10501050
"dummy": "seo fields",
10511051
"preview_image": "https://example.com/test-seo.png",
1052+
"description": "Test SEO description comes through",
10521053
},
10531054
{
10541055
"title": "test title",
@@ -1068,6 +1069,7 @@ def test_ContentfulPage__get_info_data__category_tags_classification(
10681069
"seo": {
10691070
"dummy": "seo fields",
10701071
"image": "https://example.com/test-seo.png",
1072+
"description": "Test SEO description comes through",
10711073
},
10721074
},
10731075
),
@@ -1169,11 +1171,11 @@ def test_ContentfulPage__get_info_data(
11691171
{
11701172
"dummy": "seo fields",
11711173
"preview_image": "https://example.com/test-seo.png",
1174+
"description": "Test SEO description comes through",
11721175
},
11731176
)
11741177
in mock__get_preview_image_from_fields.call_args_list
11751178
)
1176-
11771179
else:
11781180
assert mock__get_preview_image_from_fields.call_count == 1
11791181
mock__get_preview_image_from_fields.assert_called_once_with(entry_obj__fields)

0 commit comments

Comments
 (0)
Please sign in to comment.