From 8d340896378cddb96d35bb40d7d91a3765b06835 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Tue, 13 Jan 2026 10:40:36 +0000 Subject: [PATCH] feat(BOOK-609): Using embedded url for assets instead of href since that is not updated when the asset is updated) * feat(BOOK-609): Updated refs handling for assets inside content pages Approved-by: Linus Flood --- .../lib/components/JsonToHtml/JsonToHtml.tsx | 7 +- .../components/JsonToHtml/renderOptions.tsx | 20 ++++- .../Fragments/Blocks/Content.graphql.ts | 10 ++- .../Fragments/Sidebar/Content.graphql.ts | 6 +- .../lib/graphql/Fragments/SysAsset.graphql.ts | 17 ++++- .../lib/graphql/Fragments/Video.graphql.ts | 1 + .../contentstack/collectionPage/utils.ts | 42 +++-------- .../routers/contentstack/contentPage/utils.ts | 74 +++++++++---------- .../routers/contentstack/loyaltyPage/query.ts | 6 +- .../routers/contentstack/loyaltyPage/utils.ts | 27 ++++--- .../contentstack/promoCampaignPage/utils.ts | 29 +++++--- .../contentstack/schemas/blocks/content.ts | 11 ++- .../contentstack/schemas/blocks/sysAsset.ts | 17 +++-- .../contentstack/schemas/sidebar/content.ts | 21 +++--- .../routers/contentstack/schemas/system.ts | 2 + .../lib/routers/contentstack/schemas/video.ts | 1 + .../routers/contentstack/startPage/query.ts | 7 +- .../routers/contentstack/startPage/utils.ts | 29 ++------ packages/trpc/lib/utils/generateTag.ts | 14 ++-- 19 files changed, 194 insertions(+), 147 deletions(-) diff --git a/packages/design-system/lib/components/JsonToHtml/JsonToHtml.tsx b/packages/design-system/lib/components/JsonToHtml/JsonToHtml.tsx index 91fb27179..07c56e0b8 100644 --- a/packages/design-system/lib/components/JsonToHtml/JsonToHtml.tsx +++ b/packages/design-system/lib/components/JsonToHtml/JsonToHtml.tsx @@ -16,9 +16,10 @@ export type Node = { export type Embeds = | { __typename: Exclude - system?: { uid: string } | undefined | null - url?: string | undefined | null - title?: string | undefined | null + system?: { uid: string } | null + url?: string | null + permanent_url?: string | null + title?: string | null } | { __typename: "ImageContainer" diff --git a/packages/design-system/lib/components/JsonToHtml/renderOptions.tsx b/packages/design-system/lib/components/JsonToHtml/renderOptions.tsx index 9818f9490..d56c103fb 100644 --- a/packages/design-system/lib/components/JsonToHtml/renderOptions.tsx +++ b/packages/design-system/lib/components/JsonToHtml/renderOptions.tsx @@ -404,13 +404,29 @@ export const renderOptions: RenderOptions = { ) } } - } else if (node.attrs["display-type"] === "link" && node.attrs.href) { + } else if (node.attrs["display-type"] === "link") { + const asset = embeds?.[node?.attrs?.["asset-uid"] as string] + + // Decision note 2026-01-07: + // Content team sometimes updates assets without updating the RTE reference. + // The `permanent_url` should be used on the asset in Contentstack. + // If this is not provided, we should not render the link. + const rawUrl = + asset?.node && "permanent_url" in asset.node + ? asset.node.permanent_url + : null + if (!rawUrl) { + return null + } + const { className, ...props } = extractPossibleAttributes(node.attrs) + + const cleanUrl = rawUrl.split("?")[0] return ( { - switch (block.__typename) { - case CollectionPageEnum.ContentStack.blocks.VideoCard: - if (block.video_card?.video.sourceConnection.edges[0]) { - connections.push( - block.video_card.video.sourceConnection.edges[0].node.system - ) - } - break - default: - break - } - }) - } - - return connections -} - -export function getConnections({ collection_page }: CollectionPageRefs) { - const connections: System["system"][] = [collection_page.system] if (collection_page.blocks) { collection_page.blocks.forEach((block) => { const typeName = block.__typename @@ -148,6 +125,11 @@ export function getConnections({ collection_page }: CollectionPageRefs) { if (block.video_card?.system) { connections.push(block.video_card.system) } + if (block.video_card?.video.sourceConnection.edges[0]) { + assetConnections.push( + block.video_card.video.sourceConnection.edges[0].node + ) + } break case CollectionPageEnum.ContentStack.blocks.DynamicContent: break @@ -158,5 +140,5 @@ export function getConnections({ collection_page }: CollectionPageRefs) { }) } - return connections + return { connections, assetConnections } } diff --git a/packages/trpc/lib/routers/contentstack/contentPage/utils.ts b/packages/trpc/lib/routers/contentstack/contentPage/utils.ts index 55a5a424c..037e0f743 100644 --- a/packages/trpc/lib/routers/contentstack/contentPage/utils.ts +++ b/packages/trpc/lib/routers/contentstack/contentPage/utils.ts @@ -76,8 +76,7 @@ export function generatePageTags( validatedData: ContentPageRefs, lang: Lang ): string[] { - const connections = getConnections(validatedData) - const assetConnections = getConnectionsFromAssets(validatedData) + const { connections, assetConnections } = getConnections(validatedData) return [ generateTagsFromSystem(lang, connections), generateTagsFromAssetSystem(assetConnections), @@ -85,40 +84,15 @@ export function generatePageTags( ].flat() } -export function getConnectionsFromAssets({ content_page }: ContentPageRefs) { - const connections: AssetSystem["system"][] = [] - - if (content_page.hero_video?.sourceConnection.edges[0]) { - connections.push( - content_page.hero_video.sourceConnection.edges[0].node.system - ) - } - - if (content_page.blocks) { - content_page.blocks.forEach((block) => { - switch (block.__typename) { - case ContentPageEnum.ContentStack.blocks.VideoCard: - if (block.video_card?.video.sourceConnection.edges[0]) { - connections.push( - block.video_card.video.sourceConnection.edges[0].node.system - ) - } - break - case ContentPageEnum.ContentStack.blocks.Video: - if (block.video?.sourceConnection.edges[0]) { - connections.push(block.video.sourceConnection.edges[0].node.system) - } - break - default: - break - } - }) - } - return connections -} - export function getConnections({ content_page }: ContentPageRefs) { const connections: System["system"][] = [content_page.system] + const assetConnections: AssetSystem[] = [] + + if (content_page.hero_video?.sourceConnection.edges[0]) { + assetConnections.push( + content_page.hero_video.sourceConnection.edges[0].node + ) + } if (content_page.blocks) { content_page.blocks.forEach((block) => { @@ -130,10 +104,14 @@ export function getConnections({ content_page }: ContentPageRefs) { } break case ContentPageEnum.ContentStack.blocks.Content: - { - if (block?.content?.length) { - connections.push(...block.content) - } + if (block?.content?.length) { + block.content.forEach((contentBlock) => { + if ("system" in contentBlock) { + assetConnections.push(contentBlock) + } else { + connections.push(contentBlock) + } + }) } break case ContentPageEnum.ContentStack.blocks.CardsGrid: @@ -172,8 +150,16 @@ export function getConnections({ content_page }: ContentPageRefs) { if (block.video_card) { connections.push(block.video_card.system) } + if (block.video_card?.video.sourceConnection.edges[0]) { + assetConnections.push( + block.video_card.video.sourceConnection.edges[0].node + ) + } break case ContentPageEnum.ContentStack.blocks.Video: + if (block.video?.sourceConnection.edges[0]) { + assetConnections.push(block.video.sourceConnection.edges[0].node) + } break case ContentPageEnum.ContentStack.blocks.Jotform: if (block.jotform) { @@ -192,8 +178,14 @@ export function getConnections({ content_page }: ContentPageRefs) { const typeName = block.__typename switch (typeName) { case ContentPageEnum.ContentStack.sidebar.Content: - if (block.content.length) { - connections.push(...block.content.filter((c) => !!c)) + if (block.content?.length) { + block.content.forEach((contentBlock) => { + if ("system" in contentBlock) { + assetConnections.push(contentBlock) + } else { + connections.push(contentBlock) + } + }) } break case ContentPageEnum.ContentStack.sidebar.JoinLoyaltyContact: @@ -222,5 +214,5 @@ export function getConnections({ content_page }: ContentPageRefs) { } }) } - return connections + return { connections, assetConnections } } diff --git a/packages/trpc/lib/routers/contentstack/loyaltyPage/query.ts b/packages/trpc/lib/routers/contentstack/loyaltyPage/query.ts index 04b0db2e1..1bbb69d22 100644 --- a/packages/trpc/lib/routers/contentstack/loyaltyPage/query.ts +++ b/packages/trpc/lib/routers/contentstack/loyaltyPage/query.ts @@ -11,6 +11,7 @@ import { contentstackExtendedProcedureUID } from "../../../procedures" import { generateRefsResponseTag, generateTag, + generateTagsFromAssetSystem, generateTagsFromSystem, } from "../../../utils/generateTag" import { loyaltyPageRefsSchema, loyaltyPageSchema } from "./output" @@ -64,10 +65,13 @@ export const loyaltyPageQueryRouter = router({ metricsGetLoyaltyPageRefs.success() - const connections = getConnections(validatedLoyaltyPageRefs.data) + const { connections, assetConnections } = getConnections( + validatedLoyaltyPageRefs.data + ) const tags = [ generateTagsFromSystem(lang, connections), + generateTagsFromAssetSystem(assetConnections), generateTag(lang, validatedLoyaltyPageRefs.data.loyalty_page.system.uid), ].flat() diff --git a/packages/trpc/lib/routers/contentstack/loyaltyPage/utils.ts b/packages/trpc/lib/routers/contentstack/loyaltyPage/utils.ts index c9595d977..6fb09d990 100644 --- a/packages/trpc/lib/routers/contentstack/loyaltyPage/utils.ts +++ b/packages/trpc/lib/routers/contentstack/loyaltyPage/utils.ts @@ -1,10 +1,11 @@ import { LoyaltyPageEnum } from "../../../enums/loyaltyPage" import type { LoyaltyPageRefs } from "../../../types/loyaltyPage" -import type { System } from "../schemas/system" +import type { AssetSystem, System } from "../schemas/system" export function getConnections({ loyalty_page }: LoyaltyPageRefs) { const connections: System["system"][] = [loyalty_page.system] + const assetConnections: AssetSystem[] = [] if (loyalty_page.blocks) { loyalty_page.blocks.forEach((block) => { @@ -16,9 +17,13 @@ export function getConnections({ loyalty_page }: LoyaltyPageRefs) { break case LoyaltyPageEnum.ContentStack.blocks.Content: if (block?.content?.length) { - // TS has trouble infering the filtered types - // @ts-ignore - connections.push(...block.content) + block.content.forEach((contentBlock) => { + if ("system" in contentBlock) { + assetConnections.push(contentBlock) + } else { + connections.push(contentBlock) + } + }) } break case LoyaltyPageEnum.ContentStack.blocks.DynamicContent: @@ -41,10 +46,14 @@ export function getConnections({ loyalty_page }: LoyaltyPageRefs) { loyalty_page.sidebar.forEach((block) => { switch (block?.__typename) { case LoyaltyPageEnum.ContentStack.sidebar.Content: - if (block.content.length) { - // TS has trouble infering the filtered types - // @ts-ignore - connections.push(...block.content) + if (block?.content?.length) { + block.content.forEach((contentBlock) => { + if ("system" in contentBlock) { + assetConnections.push(contentBlock) + } else { + connections.push(contentBlock) + } + }) } break case LoyaltyPageEnum.ContentStack.sidebar.JoinLoyaltyContact: @@ -59,5 +68,5 @@ export function getConnections({ loyalty_page }: LoyaltyPageRefs) { }) } - return connections + return { connections, assetConnections } } diff --git a/packages/trpc/lib/routers/contentstack/promoCampaignPage/utils.ts b/packages/trpc/lib/routers/contentstack/promoCampaignPage/utils.ts index e74bb8360..408c7b93f 100644 --- a/packages/trpc/lib/routers/contentstack/promoCampaignPage/utils.ts +++ b/packages/trpc/lib/routers/contentstack/promoCampaignPage/utils.ts @@ -2,44 +2,53 @@ import { PromoCampaignPageEnum, type PromoCampaignPageRefs, } from "../../../types/promoCampaignPage" -import { generateTag, generateTagsFromSystem } from "../../../utils/generateTag" +import { + generateTag, + generateTagsFromAssetSystem, + generateTagsFromSystem, +} from "../../../utils/generateTag" import type { Lang } from "@scandic-hotels/common/constants/language" -import type { System } from "../schemas/system" +import type { AssetSystem, System } from "../schemas/system" export function generatePageTags( validatedData: PromoCampaignPageRefs, lang: Lang ): string[] { - const connections = getConnections(validatedData) + const { connections, assetConnections } = getConnections(validatedData) return [ generateTagsFromSystem(lang, connections), + generateTagsFromAssetSystem(assetConnections), generateTag(lang, validatedData.promo_campaign_page.system.uid), ].flat() } export function getConnections({ promo_campaign_page }: PromoCampaignPageRefs) { const connections: System["system"][] = [promo_campaign_page.system] + const assetConnections: AssetSystem[] = [] if (promo_campaign_page.blocks) { promo_campaign_page.blocks.forEach((block) => { switch (block.__typename) { - case PromoCampaignPageEnum.ContentStack.blocks.Accordion: { + case PromoCampaignPageEnum.ContentStack.blocks.Accordion: if (block.accordion.length) { connections.push(...block.accordion.filter((c) => !!c)) } break - } case PromoCampaignPageEnum.ContentStack.blocks.Content: - { - if (block?.content?.length) { - connections.push(...block.content) - } + if (block?.content?.length) { + block.content.forEach((contentBlock) => { + if ("system" in contentBlock) { + assetConnections.push(contentBlock) + } else { + connections.push(contentBlock) + } + }) } break } }) } - return connections + return { connections, assetConnections } } diff --git a/packages/trpc/lib/routers/contentstack/schemas/blocks/content.ts b/packages/trpc/lib/routers/contentstack/schemas/blocks/content.ts index 57e94a5da..3d69b2b3d 100644 --- a/packages/trpc/lib/routers/contentstack/schemas/blocks/content.ts +++ b/packages/trpc/lib/routers/contentstack/schemas/blocks/content.ts @@ -73,12 +73,15 @@ export const contentRefsSchema = z.object({ .nullish() .transform((data) => { return data?.content?.embedded_itemsConnection.edges - .filter(({ node }) => node.__typename !== ContentEnum.blocks.SysAsset) .map(({ node }) => { - if ("system" in node) { - return node.system + switch (node.__typename) { + case ContentEnum.blocks.SysAsset: + return node.system && (node.permanent_url || node.url) + ? { system: node.system, url: node.permanent_url || node.url } + : null + default: + return node.system } - return null }) .filter((node) => !!node) }), diff --git a/packages/trpc/lib/routers/contentstack/schemas/blocks/sysAsset.ts b/packages/trpc/lib/routers/contentstack/schemas/blocks/sysAsset.ts index 6fb69d59b..2fb1f18c5 100644 --- a/packages/trpc/lib/routers/contentstack/schemas/blocks/sysAsset.ts +++ b/packages/trpc/lib/routers/contentstack/schemas/blocks/sysAsset.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { ContentEnum } from "../../../../types/content" +import { assetSystemSchema } from "../system" // SysAsset is used for several different assets in ContentStack, such as images, pdf-files, etc. // It is a generic asset type that can represent any file type. @@ -22,15 +23,21 @@ export const sysAssetSchema = z.object({ // the exact same structure, that's why systemSchema // is not used as that correlates to the // EntrySystemField type - system: z - .object({ - uid: z.string(), - }) - .nullish(), + system: assetSystemSchema.nullish(), title: z.string().nullish(), url: z.string().nullish(), + permanent_url: z + .string() + .nullish() + .transform((val) => (val === "Permanent URL Not Defined!" ? null : val)), // ContentStack returns this string when permanent_url is not defined }) export const sysAssetRefsSchema = z.object({ __typename: z.literal(ContentEnum.blocks.SysAsset), + url: z.string().nullish(), + permanent_url: z + .string() + .nullish() + .transform((val) => (val === "Permanent URL Not Defined!" ? null : val)), // ContentStack returns this string when permanent_url is not defined + system: assetSystemSchema.nullish(), }) diff --git a/packages/trpc/lib/routers/contentstack/schemas/sidebar/content.ts b/packages/trpc/lib/routers/contentstack/schemas/sidebar/content.ts index 4528463d6..1e1b770c0 100644 --- a/packages/trpc/lib/routers/contentstack/schemas/sidebar/content.ts +++ b/packages/trpc/lib/routers/contentstack/schemas/sidebar/content.ts @@ -56,11 +56,6 @@ const actualRefs = z.discriminatedUnion("__typename", [ ...rawLinkRefsUnionSchema.options, ]) -type Ref = typeof actualRefs._type -type Refs = { - node: Ref -} - export const contentRefsSchema = z.object({ content: z .object({ @@ -78,9 +73,17 @@ export const contentRefsSchema = z.object({ }), }) .transform((data) => { - const filtered = data.content.embedded_itemsConnection.edges.filter( - (block) => block.node.__typename !== ContentEnum.blocks.SysAsset - ) as unknown as Refs[] // TS issues with filtered arrays - return filtered.map((block) => block.node.system) + return data?.content?.embedded_itemsConnection.edges + .map(({ node }) => { + switch (node.__typename) { + case ContentEnum.blocks.SysAsset: + return node.system && (node.permanent_url || node.url) + ? { system: node.system, url: node.permanent_url || node.url } + : null + default: + return node.system + } + }) + .filter((node) => !!node) }), }) diff --git a/packages/trpc/lib/routers/contentstack/schemas/system.ts b/packages/trpc/lib/routers/contentstack/schemas/system.ts index 4683e708f..349eafacc 100644 --- a/packages/trpc/lib/routers/contentstack/schemas/system.ts +++ b/packages/trpc/lib/routers/contentstack/schemas/system.ts @@ -19,4 +19,6 @@ export const assetSystemSchema = z.object({ export interface AssetSystem { system: z.output + url?: string | null + permanent_url?: string | null } diff --git a/packages/trpc/lib/routers/contentstack/schemas/video.ts b/packages/trpc/lib/routers/contentstack/schemas/video.ts index 8218196b5..692a0f4e7 100644 --- a/packages/trpc/lib/routers/contentstack/schemas/video.ts +++ b/packages/trpc/lib/routers/contentstack/schemas/video.ts @@ -78,6 +78,7 @@ export const videoRefSchema = z.object({ z.object({ node: z.object({ system: assetSystemSchema, + url: z.string(), }), }) ), diff --git a/packages/trpc/lib/routers/contentstack/startPage/query.ts b/packages/trpc/lib/routers/contentstack/startPage/query.ts index 9e6c0a99c..f9123fa74 100644 --- a/packages/trpc/lib/routers/contentstack/startPage/query.ts +++ b/packages/trpc/lib/routers/contentstack/startPage/query.ts @@ -15,7 +15,7 @@ import { generateTagsFromSystem, } from "../../../utils/generateTag" import { startPageRefsSchema, startPageSchema } from "./output" -import { getConnections, getConnectionsFromAssets } from "./utils" +import { getConnections } from "./utils" import type { z } from "zod" @@ -75,8 +75,9 @@ export const startPageQueryRouter = router({ metricsGetStartPage.start() - const connections = getConnections(validatedRefsData.data) - const assetConnections = getConnectionsFromAssets(validatedRefsData.data) + const { connections, assetConnections } = getConnections( + validatedRefsData.data + ) const tags = [ generateTagsFromSystem(lang, connections), diff --git a/packages/trpc/lib/routers/contentstack/startPage/utils.ts b/packages/trpc/lib/routers/contentstack/startPage/utils.ts index 7ca407efd..e2fb577a0 100644 --- a/packages/trpc/lib/routers/contentstack/startPage/utils.ts +++ b/packages/trpc/lib/routers/contentstack/startPage/utils.ts @@ -17,29 +17,9 @@ export namespace StartPageEnum { export interface StartPageRefs extends z.output {} -export function getConnectionsFromAssets({ start_page }: StartPageRefs) { - const connections: AssetSystem["system"][] = [] - - if (start_page.blocks) { - start_page.blocks.forEach((block) => { - switch (block.__typename) { - case StartPageEnum.ContentStack.blocks.VideoCard: - if (block.video_card?.video.sourceConnection.edges[0]) { - connections.push( - block.video_card.video.sourceConnection.edges[0].node.system - ) - } - break - default: - break - } - }) - } - return connections -} - export function getConnections({ start_page }: StartPageRefs) { const connections: System["system"][] = [start_page.system] + const assetConnections: AssetSystem[] = [] if (start_page.blocks) { start_page.blocks.forEach((block) => { @@ -75,6 +55,11 @@ export function getConnections({ start_page }: StartPageRefs) { if (block.video_card?.system) { connections.push(block.video_card.system) } + if (block.video_card?.video.sourceConnection.edges[0]) { + assetConnections.push( + block.video_card.video.sourceConnection.edges[0].node + ) + } break } default: @@ -84,5 +69,5 @@ export function getConnections({ start_page }: StartPageRefs) { }) } - return connections + return { connections, assetConnections } } diff --git a/packages/trpc/lib/utils/generateTag.ts b/packages/trpc/lib/utils/generateTag.ts index eaa72e5c4..fcdd9791b 100644 --- a/packages/trpc/lib/utils/generateTag.ts +++ b/packages/trpc/lib/utils/generateTag.ts @@ -88,11 +88,15 @@ export function generateTagsFromSystem( }) } -export function generateTagsFromAssetSystem( - connections: AssetSystem["system"][] -) { - return connections.map((system) => { - return generateTag(Lang.en, system.content_type_uid, system.uid) +export function generateTagsFromAssetSystem(connections: AssetSystem[]) { + return connections.map(({ system, url, permanent_url }) => { + let affix = system.uid + + if (permanent_url || url) { + affix += `:${permanent_url || url}` + } + + return generateTag(Lang.en, system.content_type_uid, affix) }) }