From 0ad4117ff589b4503cdb0d41e95da1496a47b4c4 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Mon, 1 Sep 2025 11:29:14 +0100 Subject: [PATCH 1/8] Add product element type and decoders Co-authored-by: Oliver Abrahams --- .../gu/contentapi/json/CirceDecoders.scala | 2 + .../gu/contentapi/json/CirceEncoders.scala | 2 + models/src/main/thrift/content/v1.thrift | 44 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala b/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala index 60fabb2..54544a1 100644 --- a/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala +++ b/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala @@ -238,4 +238,6 @@ object CirceDecoders { implicit val pillarsResponseDecoder: Decoder[PillarsResponse] = deriveDecoder implicit val embedReachDecoder: Decoder[EmbedReach] = deriveDecoder implicit val linkElementFieldsDecoder: Decoder[LinkElementFields] = deriveDecoder + implicit val statisticDecoder: Decoder[Statistic] = deriveDecoder + implicit val productElementFieldsDecoder: Decoder[ProductElementFields] = deriveDecoder } diff --git a/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala b/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala index d713773..5fce2e8 100644 --- a/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala +++ b/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala @@ -187,6 +187,8 @@ object CirceEncoders { implicit val pillarsResponseEncoder: Encoder[PillarsResponse] = deriveEncoder implicit val embedReachEncoder: Encoder[EmbedReach] = deriveEncoder implicit val linkElementFieldsEncoder: Encoder[LinkElementFields] = deriveEncoder + implicit val statisticEncoder: Encoder[Statistic] = deriveEncoder + implicit val productElementFieldsEncoder: Encoder[ProductElementFields] = deriveEncoder def genDateTimeEncoder(truncate: Boolean = true): Encoder[CapiDateTime] = Encoder.instance[CapiDateTime] { capiDateTime => val dateTime: OffsetDateTime = OffsetDateTime.parse(capiDateTime.iso8601) diff --git a/models/src/main/thrift/content/v1.thrift b/models/src/main/thrift/content/v1.thrift index 0f04ce8..1516a2e 100644 --- a/models/src/main/thrift/content/v1.thrift +++ b/models/src/main/thrift/content/v1.thrift @@ -152,6 +152,8 @@ enum ElementType { TIMELINE = 24 LINK = 25 + + PRODUCT = 26 } enum TagType { @@ -961,6 +963,46 @@ struct TimelineElementFields { 1: required list sections; } +struct Statistic { + 1: optional string name, + + 2: optional string value, +} + +struct ProductElementFields { + 1: optional string productName; + + 2: optional string brandName; + + 3: optional string primaryHeading; + + 4: optional string secondaryHeading; + + 5: optional string starRating; + + 6: optional string primaryProductUrl; + + 7: optional string primaryCta; + + 8: optional string primaryRetailer; + + 9: optional string primaryPrice; + + 10: optional string secondaryProductUrl; + + 11: optional string secondaryCta; + + 12: optional string secondaryRetailer; + + 13: optional string secondaryPrice; + + 14: optional list statistics; + + 15: optional CartoonImage image; + + 16: optional list content; +} + struct BlockElement { 1: required ElementType type @@ -1023,6 +1065,8 @@ struct BlockElement { 27: optional TimelineElementFields timelineTypeData 28: optional LinkElementFields linkTypeData + + 29: optional ProductElementFields productTypeData } struct MembershipPlaceholder { From e5ad069e9776589f860480dbf0619b63574852eb Mon Sep 17 00:00:00 2001 From: ollie_abrahams Date: Tue, 9 Sep 2025 13:30:45 +0100 Subject: [PATCH 2/8] add Product Display Type --- models/src/main/thrift/content/v1.thrift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/models/src/main/thrift/content/v1.thrift b/models/src/main/thrift/content/v1.thrift index 1516a2e..58e582a 100644 --- a/models/src/main/thrift/content/v1.thrift +++ b/models/src/main/thrift/content/v1.thrift @@ -911,6 +911,11 @@ enum ListType { MULTI_BYLINE = 4, } +enum ProductDisplayType { + INLINE_WITH_PRODUCT_CARD = 0, + INLINE_ONLY = 1, +} + struct ListItem { 1: required list elements = []; @@ -1001,6 +1006,8 @@ struct ProductElementFields { 15: optional CartoonImage image; 16: optional list content; + + 17: required ProductDisplayType displayType; } struct BlockElement { From a460af86a6fa31e5766595c55c4adaa2ea0bb174 Mon Sep 17 00:00:00 2001 From: Charley_Campbell Date: Thu, 23 Oct 2025 09:09:54 +0100 Subject: [PATCH 3/8] Updating model to be inline with the upstream model in flexible-model --- models/src/main/thrift/content/v1.thrift | 78 ++++++++++++++++++------ 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/models/src/main/thrift/content/v1.thrift b/models/src/main/thrift/content/v1.thrift index 1516a2e..de6f0ae 100644 --- a/models/src/main/thrift/content/v1.thrift +++ b/models/src/main/thrift/content/v1.thrift @@ -963,44 +963,86 @@ struct TimelineElementFields { 1: required list sections; } -struct Statistic { +enum ProductDisplayType { + INLINE_WITH_PRODUCT_CARD = 0, + INLINE_ONLY = 1, +} + +struct ProductCTA { + 1: optional string url; + + 2: optional string text; + + 3: optional string retailer; + + 4: optional string price; +} + +struct ProductCustomAttribute { 1: optional string name, 2: optional string value, } -struct ProductElementFields { - 1: optional string productName; +struct ProductImage { + /** Caption of the image */ + 1: optional string caption; - 2: optional string brandName; + /** Display credit for the image */ + 2: optional bool displayCredit; - 3: optional string primaryHeading; + /** Source of the image */ + 3: optional string source; - 4: optional string secondaryHeading; + /** Caption of the image */ + 4: optional string photographer; + + /** Alt text of the image */ + 5: optional string alt; - 5: optional string starRating; + /** The id of the image in the media api */ + 6: optional string mediaId; - 6: optional string primaryProductUrl; + /** The url of the image file */ + 7: optional string file; - 7: optional string primaryCta; + /** Suppliers reference of the image */ + 8: optional string suppliersReference; - 8: optional string primaryRetailer; + /** Type of the image */ + 9: optional string imageType; - 9: optional string primaryPrice; + /** height for the image */ + 10: optional i32 height; - 10: optional string secondaryProductUrl; + /** width for the image */ + 11: optional i32 width; + + /** Credit for the image */ + 12: optional string credit; + +} + +struct ProductElementFields { + 1: optional string productName; + + 2: optional string brandName; + + 3: optional string primaryHeading; + + 4: optional string secondaryHeading; - 11: optional string secondaryCta; + 5: required ProductDisplayType displayType; - 12: optional string secondaryRetailer; + 6: optional string starRating; - 13: optional string secondaryPrice; + 7: optional list productCtas; - 14: optional list statistics; + 8: optional list customAttributes; - 15: optional CartoonImage image; + 9: optional ProductImage image; - 16: optional list content; + 10: optional list content; } struct BlockElement { From c216dea41677359840ff30ed1bfed421887d2507 Mon Sep 17 00:00:00 2001 From: Charley_Campbell Date: Thu, 23 Oct 2025 10:03:11 +0100 Subject: [PATCH 4/8] change to Decoder and Encoder with updated model --- .../src/main/scala/com/gu/contentapi/json/CirceDecoders.scala | 4 +++- .../src/main/scala/com/gu/contentapi/json/CirceEncoders.scala | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala b/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala index 54544a1..d8f0395 100644 --- a/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala +++ b/json/src/main/scala/com/gu/contentapi/json/CirceDecoders.scala @@ -238,6 +238,8 @@ object CirceDecoders { implicit val pillarsResponseDecoder: Decoder[PillarsResponse] = deriveDecoder implicit val embedReachDecoder: Decoder[EmbedReach] = deriveDecoder implicit val linkElementFieldsDecoder: Decoder[LinkElementFields] = deriveDecoder - implicit val statisticDecoder: Decoder[Statistic] = deriveDecoder + implicit val productCustomAttributeDecoder: Decoder[ProductCustomAttribute] = deriveDecoder + implicit val productCTADecoder: Decoder[ProductCTA] = deriveDecoder + implicit val productImageDecoder: Decoder[ProductImage] = deriveDecoder implicit val productElementFieldsDecoder: Decoder[ProductElementFields] = deriveDecoder } diff --git a/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala b/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala index 5fce2e8..0142917 100644 --- a/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala +++ b/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala @@ -187,7 +187,9 @@ object CirceEncoders { implicit val pillarsResponseEncoder: Encoder[PillarsResponse] = deriveEncoder implicit val embedReachEncoder: Encoder[EmbedReach] = deriveEncoder implicit val linkElementFieldsEncoder: Encoder[LinkElementFields] = deriveEncoder - implicit val statisticEncoder: Encoder[Statistic] = deriveEncoder + implicit val productCustomAttributeEncoder: Encoder[ProductCustomAttribute] = deriveEncoder + implicit val productCTAEncoder: Encoder[ProductCTA] = deriveEncoder + implicit val ProductImageEncoder: Encoder[ProductImage] = deriveEncoder implicit val productElementFieldsEncoder: Encoder[ProductElementFields] = deriveEncoder def genDateTimeEncoder(truncate: Boolean = true): Encoder[CapiDateTime] = Encoder.instance[CapiDateTime] { capiDateTime => From 2c065a43f288baf7e25e9b57daf4c8fb6a21e915 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Oct 2025 16:43:45 +0000 Subject: [PATCH 5/8] Make first letter of encoder lower case --- json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala b/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala index 0142917..03e646c 100644 --- a/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala +++ b/json/src/main/scala/com/gu/contentapi/json/CirceEncoders.scala @@ -189,7 +189,7 @@ object CirceEncoders { implicit val linkElementFieldsEncoder: Encoder[LinkElementFields] = deriveEncoder implicit val productCustomAttributeEncoder: Encoder[ProductCustomAttribute] = deriveEncoder implicit val productCTAEncoder: Encoder[ProductCTA] = deriveEncoder - implicit val ProductImageEncoder: Encoder[ProductImage] = deriveEncoder + implicit val productImageEncoder: Encoder[ProductImage] = deriveEncoder implicit val productElementFieldsEncoder: Encoder[ProductElementFields] = deriveEncoder def genDateTimeEncoder(truncate: Boolean = true): Encoder[CapiDateTime] = Encoder.instance[CapiDateTime] { capiDateTime => From 7c2cb485096c3e78b125efc5c47248d90e4f5fd9 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Oct 2025 16:45:43 +0000 Subject: [PATCH 6/8] Add PRODUCT_CARD_ONLY to ProductDisplayType --- models/src/main/thrift/content/v1.thrift | 1 + 1 file changed, 1 insertion(+) diff --git a/models/src/main/thrift/content/v1.thrift b/models/src/main/thrift/content/v1.thrift index 2ce2199..ff17891 100644 --- a/models/src/main/thrift/content/v1.thrift +++ b/models/src/main/thrift/content/v1.thrift @@ -914,6 +914,7 @@ enum ListType { enum ProductDisplayType { INLINE_WITH_PRODUCT_CARD = 0, INLINE_ONLY = 1, + PRODUCT_CARD_ONLY = 2, } struct ListItem { From 7d4cd261d6b7f52baafe102ed2aef57f4826d056 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Oct 2025 16:48:27 +0000 Subject: [PATCH 7/8] Consistent indentation --- models/src/main/thrift/content/v1.thrift | 49 ++++++++++++------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/models/src/main/thrift/content/v1.thrift b/models/src/main/thrift/content/v1.thrift index ff17891..383b9db 100644 --- a/models/src/main/thrift/content/v1.thrift +++ b/models/src/main/thrift/content/v1.thrift @@ -986,42 +986,41 @@ struct ProductCustomAttribute { } struct ProductImage { - /** Caption of the image */ - 1: optional string caption; + /** Caption of the image */ + 1: optional string caption; - /** Display credit for the image */ - 2: optional bool displayCredit; + /** Display credit for the image */ + 2: optional bool displayCredit; - /** Source of the image */ - 3: optional string source; + /** Source of the image */ + 3: optional string source; - /** Caption of the image */ - 4: optional string photographer; + /** Caption of the image */ + 4: optional string photographer; - /** Alt text of the image */ - 5: optional string alt; - - /** The id of the image in the media api */ - 6: optional string mediaId; + /** Alt text of the image */ + 5: optional string alt; - /** The url of the image file */ - 7: optional string file; + /** The id of the image in the media api */ + 6: optional string mediaId; - /** Suppliers reference of the image */ - 8: optional string suppliersReference; + /** The url of the image file */ + 7: optional string file; - /** Type of the image */ - 9: optional string imageType; + /** Suppliers reference of the image */ + 8: optional string suppliersReference; - /** height for the image */ - 10: optional i32 height; + /** Type of the image */ + 9: optional string imageType; - /** width for the image */ - 11: optional i32 width; + /** height for the image */ + 10: optional i32 height; - /** Credit for the image */ - 12: optional string credit; + /** width for the image */ + 11: optional i32 width; + /** Credit for the image */ + 12: optional string credit; } struct ProductElementFields { From 631973a42689e23e4fefb80ceb68fd05b5af1a27 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Oct 2025 16:48:52 +0000 Subject: [PATCH 8/8] Use BlockElement type for nested content --- models/src/main/thrift/content/v1.thrift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/src/main/thrift/content/v1.thrift b/models/src/main/thrift/content/v1.thrift index 383b9db..3a8d6db 100644 --- a/models/src/main/thrift/content/v1.thrift +++ b/models/src/main/thrift/content/v1.thrift @@ -1042,7 +1042,7 @@ struct ProductElementFields { 9: optional ProductImage image; - 10: optional list content; + 10: optional list content; } struct BlockElement {