@financial-times/cp-content-pipeline-schema 2.9.2 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/generated/index.d.ts +87 -2
  3. package/lib/model/CapiResponse.d.ts +6 -0
  4. package/lib/model/CapiResponse.js +5 -0
  5. package/lib/model/CapiResponse.js.map +1 -1
  6. package/lib/model/FlourishSource.d.ts +22 -0
  7. package/lib/model/FlourishSource.js +89 -0
  8. package/lib/model/FlourishSource.js.map +1 -0
  9. package/lib/model/FlourishSource.test.d.ts +1 -0
  10. package/lib/model/FlourishSource.test.js +67 -0
  11. package/lib/model/FlourishSource.test.js.map +1 -0
  12. package/lib/model/LeadFlourish.d.ts +13 -0
  13. package/lib/model/LeadFlourish.js +35 -0
  14. package/lib/model/LeadFlourish.js.map +1 -0
  15. package/lib/model/LeadFlourish.test.d.ts +1 -0
  16. package/lib/model/LeadFlourish.test.js +62 -0
  17. package/lib/model/LeadFlourish.test.js.map +1 -0
  18. package/lib/model/Topper.d.ts +4 -1
  19. package/lib/model/Topper.js +15 -0
  20. package/lib/model/Topper.js.map +1 -1
  21. package/lib/model/Topper.test.js +21 -0
  22. package/lib/model/Topper.test.js.map +1 -1
  23. package/lib/model/schemas/capi/article.d.ts +28 -0
  24. package/lib/model/schemas/capi/article.js +1 -0
  25. package/lib/model/schemas/capi/article.js.map +1 -1
  26. package/lib/model/schemas/capi/base-schema.d.ts +41 -0
  27. package/lib/model/schemas/capi/base-schema.js +8 -1
  28. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  29. package/lib/model/schemas/capi/content-package.d.ts +28 -0
  30. package/lib/model/schemas/capi/content-package.js +1 -0
  31. package/lib/model/schemas/capi/content-package.js.map +1 -1
  32. package/lib/model/schemas/capi/live-blog-package.d.ts +5 -0
  33. package/lib/model/schemas/capi/placeholder.d.ts +5 -0
  34. package/lib/resolvers/content-tree/references/Flourish.d.ts +8 -2
  35. package/lib/resolvers/content-tree/references/Flourish.js +15 -40
  36. package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
  37. package/lib/resolvers/content-tree/references/Flourish.test.js +0 -30
  38. package/lib/resolvers/content-tree/references/Flourish.test.js.map +1 -1
  39. package/lib/resolvers/index.d.ts +36 -9
  40. package/lib/resolvers/index.js +2 -0
  41. package/lib/resolvers/index.js.map +1 -1
  42. package/lib/resolvers/leadFlourish.d.ts +16 -0
  43. package/lib/resolvers/leadFlourish.js +28 -0
  44. package/lib/resolvers/leadFlourish.js.map +1 -0
  45. package/lib/resolvers/topper.d.ts +23 -9
  46. package/lib/resolvers/topper.js +6 -0
  47. package/lib/resolvers/topper.js.map +1 -1
  48. package/package.json +1 -1
  49. package/queries/article.graphql +26 -0
  50. package/src/generated/index.ts +93 -2
  51. package/src/model/CapiResponse.ts +6 -0
  52. package/src/model/FlourishSource.test.ts +93 -0
  53. package/src/model/FlourishSource.ts +103 -0
  54. package/src/model/LeadFlourish.test.ts +71 -0
  55. package/src/model/LeadFlourish.ts +30 -0
  56. package/src/model/Topper.test.ts +26 -0
  57. package/src/model/Topper.ts +18 -0
  58. package/src/model/schemas/capi/article.ts +1 -0
  59. package/src/model/schemas/capi/base-schema.ts +8 -0
  60. package/src/model/schemas/capi/content-package.ts +1 -0
  61. package/src/resolvers/content-tree/references/Flourish.test.ts +2 -49
  62. package/src/resolvers/content-tree/references/Flourish.ts +15 -59
  63. package/src/resolvers/index.ts +2 -0
  64. package/src/resolvers/leadFlourish.ts +31 -0
  65. package/src/resolvers/topper.ts +10 -0
  66. package/src/types/internal-content.d.ts +2 -0
  67. package/tsconfig.tsbuildinfo +1 -1
  68. package/typedefs/leadFlouish.graphql +29 -0
  69. package/typedefs/topper.graphql +35 -0
@@ -21,6 +21,7 @@ const Topper = z.object({
21
21
  backgroundBox: z.boolean().optional(),
22
22
  textShadow: z.boolean().optional(),
23
23
  layout: z.string(),
24
+ layoutWidth: z.string().optional(),
24
25
  })
25
26
 
26
27
  const BaseImage = z.object({
@@ -88,6 +89,12 @@ const AlternativeImage = z.object({
88
89
  promotionalImage: BaseImage,
89
90
  })
90
91
 
92
+ export const LeadFlourish = z.object({
93
+ id: z.string(),
94
+ type: z.string(),
95
+ description: z.string(),
96
+ })
97
+
91
98
  const ClipSource = z.object({
92
99
  audioCodec: z.string().optional(),
93
100
  binaryUrl: z.string().optional(),
@@ -261,6 +268,7 @@ export const baseMediaSchema = z.object({
261
268
  mainImage: MainImage.optional(),
262
269
  leadImages: LeadImage.array().optional(),
263
270
  alternativeImages: AlternativeImage.optional(),
271
+ leadFlourish: LeadFlourish.optional(),
264
272
  embeds: z.array(z.union([ImageSet, ClipSet])).optional(),
265
273
  dataSource: DataSource.array().optional(),
266
274
  })
@@ -44,6 +44,7 @@ const contentPackageMetadataSchema = baseMetadataSchema.pick({
44
44
  const contentPackageMediaSchema = baseMediaSchema.pick({
45
45
  mainImage: true,
46
46
  leadImages: true,
47
+ leadFlourish: true,
47
48
  })
48
49
 
49
50
  export const contentPackageSchema = contentPackageContentSchema
@@ -42,7 +42,7 @@ describe('Flourish Model', () => {
42
42
  /https:\/\/public.flourish.studio\/.*\/.*\/thumbnail/
43
43
  )
44
44
 
45
- expect(decodeURIComponent(fallbackResponse.url)).toMatch(
45
+ expect(decodeURIComponent(fallbackResponse.url as string)).toMatch(
46
46
  flourishImageUrlRegex
47
47
  )
48
48
  })
@@ -66,7 +66,7 @@ describe('Flourish Model', () => {
66
66
  /https:\/\/public.flourish.studio\/.*\/example-id/
67
67
  )
68
68
 
69
- expect(decodeURIComponent(fallbackResponse.url)).toMatch(
69
+ expect(decodeURIComponent(fallbackResponse.url as string)).toMatch(
70
70
  flourishImageUrlRegex
71
71
  )
72
72
  })
@@ -92,53 +92,6 @@ describe('Flourish Model', () => {
92
92
  encodeURIComponent(flourishImageUrl)
93
93
  )
94
94
  })
95
- describe('the flourish timestamp is present', () => {
96
- it('The Flourish cache buster is appended to the flourish image url', async () => {
97
- const reference: ContentTree.Flourish = {
98
- type: 'flourish',
99
- id: 'example-id',
100
- flourishType: 'example-type',
101
- layoutWidth: 'grid',
102
- timestamp: '2022-01-06T14:41:01.102Z',
103
- description: 'Example description',
104
- }
105
- const fallbackResponse = await Flourish.fallbackImage(
106
- { reference },
107
- {},
108
- context
109
- )
110
-
111
- const flourishImageCacheBuster =
112
- '?cacheBuster=2022-01-06T14:41:01.102Z'
113
-
114
- expect(fallbackResponse.url).toContain(
115
- encodeURIComponent(flourishImageCacheBuster)
116
- )
117
- })
118
- })
119
- describe('the flourish timestamp is missing', () => {
120
- it('The Flourish cache buster is not appended to the flourish image url', async () => {
121
- const reference: ContentTree.Flourish = {
122
- type: 'flourish',
123
- id: 'example-id',
124
- flourishType: 'example-type',
125
- layoutWidth: 'grid',
126
- timestamp: '',
127
- description: 'Example description',
128
- }
129
- const fallbackResponse = await Flourish.fallbackImage(
130
- { reference },
131
- {},
132
- context
133
- )
134
-
135
- const flourishImageCacheBuster = '?cacheBuster='
136
-
137
- expect(fallbackResponse.url).not.toContain(
138
- encodeURIComponent(flourishImageCacheBuster)
139
- )
140
- })
141
- })
142
95
  })
143
96
  })
144
97
  })
@@ -3,37 +3,32 @@ import {
3
3
  FlourishFallbackResolvers,
4
4
  FlourishResolvers,
5
5
  } from '../../../generated'
6
- import { QueryContext } from '../../..'
7
- import isError from '../../../helpers/isError'
8
- import { OperationalError } from '@dotcom-reliability-kit/errors'
6
+ import { FlourishSource } from '../../../model/FlourishSource'
9
7
 
10
8
  export const Flourish = {
11
9
  async fallbackImage(parent, _args, context) {
12
- const type = parent.reference.flourishType
13
- const timestamp =
14
- typeof parent.reference.timestamp === 'string'
15
- ? parent.reference.timestamp
16
- : false
17
-
18
- const flourishUrl = `https://public.flourish.studio/${type}/${
19
- parent.reference.id
20
- }/thumbnail${timestamp ? '?cacheBuster=' + timestamp : ''}`
21
-
22
- const imageMetadata = await getImageMetadata(context, flourishUrl)
23
-
24
- const width = imageMetadata.width
25
- const height = imageMetadata.height
10
+ if (!parent.reference) {
11
+ return {}
12
+ }
13
+ const flourishData = {
14
+ id: parent.reference.id,
15
+ type: parent.reference.flourishType,
16
+ description: parent.reference.description,
17
+ }
18
+ const flourishSource = new FlourishSource(flourishData, context)
19
+ const width = await flourishSource.width()
20
+ const height = await flourishSource.height()
26
21
 
27
22
  const imageServiceWrappedUrl = imageServiceUrl({
28
- url: flourishUrl,
23
+ url: flourishSource.flourishUrl(),
29
24
  systemCode: context.systemCode ?? 'cp-content-pipeline',
30
25
  width,
31
26
  })
32
27
 
33
28
  return {
34
29
  url: imageServiceWrappedUrl,
35
- type: 'image',
36
- format: 'standard',
30
+ type: flourishSource.type(),
31
+ format: flourishSource.format(),
37
32
  sourceSet: [],
38
33
  width,
39
34
  height,
@@ -56,42 +51,3 @@ export const FlourishFallback = {
56
51
  return parent.url ?? null
57
52
  },
58
53
  } satisfies FlourishFallbackResolvers
59
-
60
- type ImageMetadata = {
61
- width: number
62
- height: number
63
- }
64
-
65
- const getImageMetadata = async (
66
- context: QueryContext,
67
- flourishUrl: string
68
- ): Promise<ImageMetadata> => {
69
- const DEFAULT_WIDTH = 2626
70
- const DEFAULT_HEIGHT = 1459
71
-
72
- try {
73
- const imageMetadata = await context.dataSources.origami.getImageMetadata(
74
- flourishUrl
75
- )
76
-
77
- return {
78
- width: imageMetadata?.width || DEFAULT_WIDTH,
79
- height: imageMetadata?.height || DEFAULT_HEIGHT,
80
- }
81
- } catch (error) {
82
- if (isError(error)) {
83
- context.logger.warn({
84
- event: 'RECOVERABLE_ERROR',
85
- error: new OperationalError({
86
- code: 'FLOURISH_IMAGE_METADATA_ERROR',
87
- message: `Error getting image dimensions for Flourish fallback image ${flourishUrl}`,
88
- cause: error,
89
- }),
90
- })
91
- }
92
- return {
93
- width: DEFAULT_WIDTH,
94
- height: DEFAULT_HEIGHT,
95
- }
96
- }
97
- }
@@ -10,6 +10,7 @@ import { default as scalars } from './scalars'
10
10
  import { default as teaser } from './teaser'
11
11
  import { default as topper } from './topper'
12
12
  import { default as person } from './person'
13
+ import { default as leadFlourish } from './leadFlourish'
13
14
  import { resolvers as references } from './content-tree/references'
14
15
  import { Resolvers } from '../generated'
15
16
 
@@ -27,6 +28,7 @@ const resolvers = {
27
28
  ...teaser,
28
29
  ...topper,
29
30
  ...person,
31
+ ...leadFlourish,
30
32
  } satisfies Resolvers
31
33
 
32
34
  export default resolvers
@@ -0,0 +1,31 @@
1
+ import { LeadFlourishResolvers, FlourishSourceResolvers } from '../generated'
2
+
3
+ const resolvers = {
4
+ LeadFlourish: {
5
+ type: (flourish) => flourish.type(),
6
+ id: (flourish) => flourish.id(),
7
+ description: (flourish) => flourish.description(),
8
+ fallbackImage: (flourish) => flourish.fallbackImage(),
9
+ },
10
+ FlourishSource: {
11
+ url: async (parent) => {
12
+ const url = await parent.url()
13
+ return url
14
+ },
15
+ height: async (parent) => {
16
+ const height = await parent.height()
17
+ return height
18
+ },
19
+ width: async (parent) => {
20
+ const width = await parent.width()
21
+ return width
22
+ },
23
+ type: (parent) => parent.type(),
24
+ format: (parent) => parent.format(),
25
+ },
26
+ } satisfies {
27
+ LeadFlourish: LeadFlourishResolvers
28
+ FlourishSource: FlourishSourceResolvers
29
+ }
30
+
31
+ export default resolvers
@@ -5,6 +5,7 @@ import {
5
5
  TopperWithImagesResolvers,
6
6
  TopperWithPackageResolvers,
7
7
  TopperWithThemeResolvers,
8
+ TopperWithFlourishResolvers,
8
9
  PodcastTopperResolvers,
9
10
  TopperWithHeadshotResolvers,
10
11
  BasicTopperResolvers,
@@ -101,6 +102,13 @@ const resolvers = {
101
102
  images: (topper) => topper.images(),
102
103
  },
103
104
 
105
+ TopperWithFlourish: {
106
+ ...topperResolvers,
107
+ layout: (topper) => topper.layout(),
108
+ layoutWidth: (topper) => topper.layoutWidth(),
109
+ leadFlourish: (topper) => topper.leadFlourish(),
110
+ },
111
+
104
112
  FullBleedTopper: {
105
113
  ...topperResolvers,
106
114
  brandConcept: (topper) => topper.brandConcept(),
@@ -119,10 +127,12 @@ const resolvers = {
119
127
  fallbackImage: (topper) => topper.fallbackImage(),
120
128
  images: (topper) => topper.images(),
121
129
  },
130
+
122
131
  } satisfies {
123
132
  Topper: TopperResolvers
124
133
  TopperWithImages: TopperWithImagesResolvers
125
134
  TopperWithTheme: TopperWithThemeResolvers
135
+ TopperWithFlourish: TopperWithFlourishResolvers
126
136
  TopperWithBrand: TopperWithBrandResolvers
127
137
  TopperWithHeadshot: TopperWithHeadshotResolvers
128
138
  TopperWithPackage: TopperWithPackageResolvers
@@ -11,6 +11,7 @@ import {
11
11
  ClipSet,
12
12
  MainImage,
13
13
  LeadImage,
14
+ LeadFlourish,
14
15
  capiNotificationResponse,
15
16
  } from '../model/schemas/capi/base-schema'
16
17
 
@@ -40,6 +41,7 @@ type Annotation = z.infer<typeof Annotation>
40
41
  type Image = z.infer<typeof Image>
41
42
  type LeadImage = z.infer<typeof LeadImage>
42
43
  type ImageSet = z.infer<typeof ImageSet>
44
+ type LeadFlourish = z.infer<typeof LeadFlourish>
43
45
  type MainImage = z.infer<typeof MainImage>
44
46
  type Clip = z.infer<typeof Clip>
45
47
  type ClipSet = z.infer<typeof ClipSet>