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
This commit is contained in:
Erik Tiekstra
2026-01-13 10:40:36 +00:00
parent 6ae4c7c805
commit 8d34089637
19 changed files with 194 additions and 147 deletions

View File

@@ -25,7 +25,7 @@ import { PromoCampaignPageLink } from "../PageLink/PromoCampaignPageLink.graphql
import { StartPageLink } from "../PageLink/StartPageLink.graphql"
import { PromoCampaignPageRef } from "../PromoCampaignPage/Ref.graphql"
import { StartPageRef } from "../StartPage/Ref.graphql"
import { SysAsset } from "../SysAsset.graphql"
import { SysAsset, SysAssetRef } from "../SysAsset.graphql"
export const Content_ContentPage = gql`
fragment Content_ContentPage on ContentPageBlocksContent {
@@ -80,6 +80,7 @@ export const Content_ContentPageRefs = gql`
edges {
node {
__typename
...SysAssetRef
...ImageContainerRef
...AccountPageRef
...CampaignOverviewPageRef
@@ -99,6 +100,7 @@ export const Content_ContentPageRefs = gql`
}
}
}
${SysAssetRef}
${ImageContainerRef}
${AccountPageRef}
${CampaignOverviewPageRef}
@@ -167,6 +169,7 @@ export const Content_LoyaltyPageRefs = gql`
edges {
node {
__typename
...SysAssetRef
...AccountPageRef
...CampaignOverviewPageRef
...CampaignPageRef
@@ -185,6 +188,7 @@ export const Content_LoyaltyPageRefs = gql`
}
}
}
${SysAssetRef}
${AccountPageRef}
${CampaignOverviewPageRef}
${CampaignPageRef}
@@ -450,6 +454,7 @@ export const Content_PromoCampaignPage = gql`
edges {
node {
__typename
...SysAsset
...AccountPageLink
...CampaignOverviewPageLink
...CampaignPageLink
@@ -469,6 +474,7 @@ export const Content_PromoCampaignPage = gql`
}
}
}
${SysAsset}
${AccountPageLink}
${CampaignOverviewPageLink}
${CampaignPageLink}
@@ -491,6 +497,7 @@ export const Content_PromoCampaignPageRefs = gql`
edges {
node {
__typename
...SysAssetRef
...AccountPageRef
...CampaignOverviewPageRef
...CampaignPageRef
@@ -509,6 +516,7 @@ export const Content_PromoCampaignPageRefs = gql`
}
}
}
${SysAssetRef}
${AccountPageRef}
${CampaignOverviewPageRef}
${CampaignPageRef}

View File

@@ -25,7 +25,7 @@ import { PromoCampaignPageLink } from "../PageLink/PromoCampaignPageLink.graphql
import { StartPageLink } from "../PageLink/StartPageLink.graphql"
import { PromoCampaignPageRef } from "../PromoCampaignPage/Ref.graphql"
import { StartPageRef } from "../StartPage/Ref.graphql"
import { SysAsset } from "../SysAsset.graphql"
import { SysAsset, SysAssetRef } from "../SysAsset.graphql"
export const ContentSidebar_ContentPage = gql`
fragment ContentSidebar_ContentPage on ContentPageSidebarContent {
@@ -80,6 +80,7 @@ export const ContentSidebar_ContentPageRefs = gql`
edges {
node {
__typename
...SysAssetRef
...ImageContainerRef
...AccountPageRef
...CampaignOverviewPageRef
@@ -99,6 +100,7 @@ export const ContentSidebar_ContentPageRefs = gql`
}
}
}
${SysAssetRef}
${ImageContainerRef}
${AccountPageRef}
${CampaignOverviewPageRef}
@@ -167,6 +169,7 @@ export const ContentSidebar_LoyaltyPageRefs = gql`
edges {
node {
__typename
...SysAssetRef
...ImageContainerRef
...AccountPageRef
...CampaignOverviewPageRef
@@ -186,6 +189,7 @@ export const ContentSidebar_LoyaltyPageRefs = gql`
}
}
}
${SysAssetRef}
${ImageContainerRef}
${AccountPageRef}
${CampaignOverviewPageRef}

View File

@@ -1,5 +1,7 @@
import { gql } from "graphql-tag"
import { AssetSystem } from "./System.graphql"
export const SysAsset = gql`
fragment SysAsset on SysAsset {
content_type
@@ -10,9 +12,22 @@ export const SysAsset = gql`
}
metadata
system {
uid
...AssetSystem
}
title
url
permanent_url
}
${AssetSystem}
`
export const SysAssetRef = gql`
fragment SysAssetRef on SysAsset {
system {
...AssetSystem
}
url
permanent_url
}
${AssetSystem}
`

View File

@@ -39,6 +39,7 @@ export const VideoRef = gql`
system {
...AssetSystem
}
url
}
}
}

View File

@@ -84,8 +84,7 @@ export function generatePageTags(
validatedData: CollectionPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
const assetConnections = getConnectionsFromAssets(validatedData)
const { connections, assetConnections } = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTagsFromAssetSystem(assetConnections),
@@ -93,38 +92,16 @@ export function generatePageTags(
].flat()
}
export function getConnectionsFromAssets({
collection_page,
}: CollectionPageRefs) {
const connections: AssetSystem["system"][] = []
export function getConnections({ collection_page }: CollectionPageRefs) {
const connections: System["system"][] = [collection_page.system]
const assetConnections: AssetSystem[] = []
if (collection_page.hero_video?.sourceConnection.edges[0]) {
connections.push(
collection_page.hero_video.sourceConnection.edges[0].node.system
assetConnections.push(
collection_page.hero_video.sourceConnection.edges[0].node
)
}
if (collection_page.blocks) {
collection_page.blocks.forEach((block) => {
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 }
}

View File

@@ -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 }
}

View File

@@ -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()

View File

@@ -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 }
}

View File

@@ -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 }
}

View File

@@ -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)
}),

View File

@@ -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(),
})

View File

@@ -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)
}),
})

View File

@@ -19,4 +19,6 @@ export const assetSystemSchema = z.object({
export interface AssetSystem {
system: z.output<typeof assetSystemSchema>
url?: string | null
permanent_url?: string | null
}

View File

@@ -78,6 +78,7 @@ export const videoRefSchema = z.object({
z.object({
node: z.object({
system: assetSystemSchema,
url: z.string(),
}),
})
),

View File

@@ -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),

View File

@@ -17,29 +17,9 @@ export namespace StartPageEnum {
export interface StartPageRefs extends z.output<typeof startPageRefsSchema> {}
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 }
}

View File

@@ -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)
})
}