@financial-times/cp-content-pipeline-schema 1.1.1 → 1.2.1

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 (47) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/generated/index.d.ts +2 -0
  3. package/lib/model/CapiResponse.d.ts +2 -0
  4. package/lib/model/CapiResponse.js +12 -0
  5. package/lib/model/CapiResponse.js.map +1 -1
  6. package/lib/model/Image.js +6 -3
  7. package/lib/model/Image.js.map +1 -1
  8. package/lib/model/Picture.d.ts +2 -1
  9. package/lib/model/Picture.js +4 -2
  10. package/lib/model/Picture.js.map +1 -1
  11. package/lib/model/Picture.test.js +5 -4
  12. package/lib/model/Picture.test.js.map +1 -1
  13. package/lib/model/Topper.test.js +6 -2
  14. package/lib/model/Topper.test.js.map +1 -1
  15. package/lib/model/schemas/capi/base-schema.d.ts +3 -0
  16. package/lib/model/schemas/capi/base-schema.js +1 -0
  17. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  18. package/lib/model/schemas/capi/index.d.ts +1 -0
  19. package/lib/model/schemas/capi/live-blog-package.d.ts +3 -0
  20. package/lib/model/schemas/capi/live-blog-package.js +1 -0
  21. package/lib/model/schemas/capi/live-blog-package.js.map +1 -1
  22. package/lib/model/schemas/capi/video.d.ts +1 -0
  23. package/lib/resolvers/content-tree/references/ImageSet.js +2 -1
  24. package/lib/resolvers/content-tree/references/ImageSet.js.map +1 -1
  25. package/lib/resolvers/content-tree/references/LayoutImage.js +2 -1
  26. package/lib/resolvers/content-tree/references/LayoutImage.js.map +1 -1
  27. package/lib/resolvers/content-tree/references/ScrollyImage.js +2 -1
  28. package/lib/resolvers/content-tree/references/ScrollyImage.js.map +1 -1
  29. package/lib/resolvers/content.d.ts +1 -0
  30. package/lib/resolvers/content.js +1 -0
  31. package/lib/resolvers/content.js.map +1 -1
  32. package/lib/resolvers/index.d.ts +1 -0
  33. package/package.json +1 -1
  34. package/src/generated/index.ts +2 -0
  35. package/src/model/CapiResponse.ts +17 -0
  36. package/src/model/Image.ts +6 -3
  37. package/src/model/Picture.test.ts +10 -4
  38. package/src/model/Picture.ts +9 -2
  39. package/src/model/Topper.test.ts +6 -2
  40. package/src/model/schemas/capi/base-schema.ts +1 -0
  41. package/src/model/schemas/capi/live-blog-package.ts +1 -0
  42. package/src/resolvers/content-tree/references/ImageSet.ts +3 -1
  43. package/src/resolvers/content-tree/references/LayoutImage.ts +3 -0
  44. package/src/resolvers/content-tree/references/ScrollyImage.ts +3 -1
  45. package/src/resolvers/content.ts +1 -0
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/typedefs/content.graphql +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/cp-content-pipeline-schema",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -529,6 +529,7 @@ export type LiveBlogPackage = Content & {
529
529
  readonly id: Scalars['String']['output'];
530
530
  readonly liveBlogPosts?: Maybe<ReadonlyArray<Maybe<Content>>>;
531
531
  readonly mainImage?: Maybe<Image>;
532
+ readonly pinnedPost?: Maybe<Content>;
532
533
  readonly publishedDate: Scalars['String']['output'];
533
534
  readonly realtime?: Maybe<Scalars['Boolean']['output']>;
534
535
  readonly standfirst?: Maybe<Scalars['String']['output']>;
@@ -1569,6 +1570,7 @@ export type LiveBlogPackageResolvers<ContextType = QueryContext, ParentType exte
1569
1570
  id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
1570
1571
  liveBlogPosts?: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Content']>>>, ParentType, ContextType>;
1571
1572
  mainImage?: Resolver<Maybe<ResolversTypes['Image']>, ParentType, ContextType>;
1573
+ pinnedPost?: Resolver<Maybe<ResolversTypes['Content']>, ParentType, ContextType>;
1572
1574
  publishedDate?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
1573
1575
  realtime?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
1574
1576
  standfirst?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@@ -537,6 +537,23 @@ export class CapiResponse {
537
537
  return []
538
538
  }
539
539
 
540
+ async pinnedPost(): Promise<CapiResponse | null> {
541
+ if ('pinnedPosts' in this.capiData) {
542
+ const pinnedPosts = this.capiData.pinnedPosts || []
543
+ const liveBlogPosts = await this.liveBlogPosts()
544
+
545
+ const pinnedPostId = pinnedPosts[0]
546
+ const pinnedPostIndex = liveBlogPosts.findIndex(
547
+ (post) => post.id() === pinnedPostId
548
+ )
549
+
550
+ if (pinnedPostIndex !== -1) {
551
+ return liveBlogPosts.splice(pinnedPostIndex, 1)[0]
552
+ }
553
+ }
554
+ return null
555
+ }
556
+
540
557
  isContainedInPackage(): boolean {
541
558
  return 'containedIn' in this.capiData
542
559
  ? (this.capiData.containedIn?.length ?? 0) > 0
@@ -126,7 +126,7 @@ export class CAPIImage implements Image {
126
126
  return resolutions.map((dpr) => ({
127
127
  url: imageServiceUrl({
128
128
  id,
129
- url: this.url(),
129
+ url: this.capiImage.binaryUrl,
130
130
  systemCode: this.#systemCode,
131
131
  width: maxAllowedWidth,
132
132
  dpr,
@@ -137,7 +137,10 @@ export class CAPIImage implements Image {
137
137
  }
138
138
 
139
139
  url() {
140
- return this.capiImage.binaryUrl
140
+ return imageServiceUrl({
141
+ url: this.capiImage.binaryUrl,
142
+ systemCode: this.#systemCode,
143
+ })
141
144
  }
142
145
 
143
146
  async dimensions() {
@@ -149,7 +152,7 @@ export class CAPIImage implements Image {
149
152
  }
150
153
  try {
151
154
  const imageMetadata = await this.#dataSources.origami.getImageMetadata(
152
- this.url()
155
+ this.capiImage.binaryUrl
153
156
  )
154
157
  return imageMetadata
155
158
  } catch (error) {
@@ -2,6 +2,8 @@ import { QueryContext } from '..'
2
2
  import { ImageSet } from '../types/internal-content'
3
3
  import { Picture } from './Picture'
4
4
 
5
+ const isLiveBlog = false
6
+
5
7
  describe('Picture model', () => {
6
8
  const mockImageSet = {
7
9
  apiUrl: 'https://api.ft.com/content/image-set',
@@ -35,9 +37,9 @@ describe('Picture model', () => {
35
37
 
36
38
  describe('finding the standard image', () => {
37
39
  it('uses the StandardInline image if it exists', () => {
38
- const picture = new Picture(mockImageSet, mockContext)
40
+ const picture = new Picture(mockImageSet, isLiveBlog, mockContext)
39
41
  expect(picture.standard().url()).toEqual(
40
- 'https://cloudfront.com/standard-inline.jpg'
42
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fcloudfront.com%2Fstandard-inline.jpg?source=cp-content-pipeline&fit=scale-down&quality=highest&width=700&dpr=1'
41
43
  )
42
44
  })
43
45
 
@@ -73,9 +75,13 @@ describe('Picture model', () => {
73
75
  type: 'http://www.ft.com/ontology/content/ImageSet',
74
76
  } as ImageSet
75
77
 
76
- const picture = new Picture(mockWithoutStandardInline, mockContext)
78
+ const picture = new Picture(
79
+ mockWithoutStandardInline,
80
+ isLiveBlog,
81
+ mockContext
82
+ )
77
83
  expect(picture.standard().url()).toEqual(
78
- 'https://cloudfront.com/desktop.jpg'
84
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fcloudfront.com%2Fdesktop.jpg?source=cp-content-pipeline&fit=scale-down&quality=highest&width=700&dpr=1'
79
85
  )
80
86
  })
81
87
  })
@@ -14,7 +14,11 @@ function assertDefined<T>(
14
14
  }
15
15
 
16
16
  export class Picture {
17
- constructor(private imageSet: ImageSet, private context: QueryContext) {}
17
+ constructor(
18
+ private imageSet: ImageSet,
19
+ private isLiveBlog: boolean,
20
+ private context: QueryContext
21
+ ) {}
18
22
 
19
23
  images(): Image[] {
20
24
  return this.imageSet.members.map(
@@ -36,7 +40,10 @@ export class Picture {
36
40
 
37
41
  async layoutWidth(): Promise<LayoutWidth> {
38
42
  //TODO: actually work out the types
39
- if (this.imageSet.members.length === 3) return 'full-grid'
43
+
44
+ if (!this.isLiveBlog && this.imageSet.members.length === 3) {
45
+ return 'full-grid'
46
+ }
40
47
 
41
48
  const dimensions = await this.standard().dimensions()
42
49
 
@@ -7,6 +7,7 @@ import conceptIds from '@financial-times/n-concept-ids'
7
7
 
8
8
  import type { QueryContext } from '..'
9
9
  import type { Image as CAPIImage } from '../types/internal-content'
10
+ import imageServiceUrl from '../helpers/imageService'
10
11
 
11
12
  const context = {} as unknown as QueryContext
12
13
  describe('produces the correct types', () => {
@@ -325,7 +326,7 @@ describe('produces correct images', () => {
325
326
  const topper = new Topper(capiResponse, context)
326
327
  const fallback = topper.fallbackImage()
327
328
  expect(fallback?.url()).toEqual(
328
- 'https://cloudfront.com/topper-standard.png'
329
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fcloudfront.com%2Ftopper-standard.png?source=cp-content-pipeline&fit=scale-down&quality=highest&width=700&dpr=1'
329
330
  )
330
331
  })
331
332
  it('uses the main image as fallback if no lead images', () => {
@@ -334,7 +335,10 @@ describe('produces correct images', () => {
334
335
  const topper = new Topper(capiResponse, context)
335
336
  const fallback = topper.fallbackImage()
336
337
  expect(fallback?.url()).toEqual(
337
- (baseCapiObject.mainImage as CAPIImage).binaryUrl
338
+ imageServiceUrl({
339
+ url: (baseCapiObject.mainImage as CAPIImage).binaryUrl,
340
+ systemCode: 'cp-content-pipeline',
341
+ })
338
342
  )
339
343
  })
340
344
  })
@@ -182,6 +182,7 @@ export const baseContentSchema = z.object({
182
182
  promotionalStandfirst: z.string().optional(),
183
183
  })
184
184
  .optional(),
185
+ pinnedPosts: z.string().array(),
185
186
  contains: z
186
187
  .object({
187
188
  id: z.string(),
@@ -10,6 +10,7 @@ const liveBlogPackageContentSchema = baseContentSchema.pick({
10
10
  summary: true,
11
11
  alternativeTitles: true,
12
12
  contains: true,
13
+ pinnedPosts: true,
13
14
  realtime: true,
14
15
  })
15
16
 
@@ -5,6 +5,8 @@ import { ImageSetResolvers } from '../../../generated'
5
5
 
6
6
  export const ImageSet = {
7
7
  picture(parent, _args, context) {
8
+ const isLiveBlog = parent.contentApiData?.type() === 'LiveBlogPost'
9
+
8
10
  const imageSet = parent.contentApiData
9
11
  ?.embeds()
10
12
  ?.find(
@@ -12,6 +14,6 @@ export const ImageSet = {
12
14
  uuidFromUrl(embed.id) === uuidFromUrl(parent.reference.id)
13
15
  )
14
16
 
15
- return imageSet ? new Picture(imageSet, context) : null
17
+ return imageSet ? new Picture(imageSet, isLiveBlog, context) : null
16
18
  },
17
19
  } satisfies ImageSetResolvers
@@ -3,6 +3,8 @@ import { LayoutImageResolvers } from '../../../generated'
3
3
 
4
4
  export const LayoutImage = {
5
5
  picture(parent, _args, context): Picture {
6
+ const isLiveBlog = parent.contentApiData?.type() === 'LiveBlogPost'
7
+
6
8
  return new Picture(
7
9
  {
8
10
  id: 'layout-imageset',
@@ -21,6 +23,7 @@ export const LayoutImage = {
21
23
  },
22
24
  ],
23
25
  },
26
+ isLiveBlog,
24
27
  context
25
28
  )
26
29
  },
@@ -5,6 +5,8 @@ import { ScrollyImageResolvers } from '../../../generated'
5
5
 
6
6
  export const ScrollyImage = {
7
7
  picture(parent, _args, context) {
8
+ const isLiveBlog = parent.contentApiData?.type() === 'LiveBlogPost'
9
+
8
10
  const imageSet = parent.contentApiData
9
11
  ?.embeds()
10
12
  ?.find(
@@ -12,6 +14,6 @@ export const ScrollyImage = {
12
14
  uuidFromUrl(embed.id) === uuidFromUrl(parent.reference.id)
13
15
  )
14
16
 
15
- return imageSet ? new Picture(imageSet, context) : null
17
+ return imageSet ? new Picture(imageSet, isLiveBlog, context) : null
16
18
  },
17
19
  } satisfies ScrollyImageResolvers
@@ -69,6 +69,7 @@ const resolvers = {
69
69
 
70
70
  LiveBlogPackage: {
71
71
  liveBlogPosts: (parent) => parent.liveBlogPosts(),
72
+ pinnedPost: (parent) => parent.pinnedPost(),
72
73
  realtime: (parent) => parent.realtime(),
73
74
  },
74
75