@financial-times/cp-content-pipeline-schema 3.7.2 → 3.8.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.
- package/CHANGELOG.md +19 -0
- package/lib/datasources/capi.d.ts +4 -4
- package/lib/datasources/capi.js +4 -4
- package/lib/datasources/capi.js.map +1 -1
- package/lib/datasources/capi.test.js +3 -3
- package/lib/datasources/capi.test.js.map +1 -1
- package/lib/fixtures/dummyContext.js +2 -2
- package/lib/fixtures/dummyContext.js.map +1 -1
- package/lib/generated/index.d.ts +41 -23
- package/lib/model/Byline.d.ts +2 -2
- package/lib/model/Byline.js.map +1 -1
- package/lib/model/{CapiResponse.d.ts → Content.d.ts} +13 -12
- package/lib/model/{CapiResponse.js → Content.js} +14 -7
- package/lib/model/Content.js.map +1 -0
- package/lib/model/{CapiResponse.test.js → Content.test.js} +56 -15
- package/lib/model/Content.test.js.map +1 -0
- package/lib/model/LeadFlourish.d.ts +2 -2
- package/lib/model/LeadFlourish.js.map +1 -1
- package/lib/model/LeadFlourish.test.js +3 -3
- package/lib/model/LeadFlourish.test.js.map +1 -1
- package/lib/model/{CapiList.d.ts → List.d.ts} +5 -5
- package/lib/model/{CapiList.js → List.js} +5 -5
- package/lib/model/List.js.map +1 -0
- package/lib/model/RichText.d.ts +2 -2
- package/lib/model/RichText.js.map +1 -1
- package/lib/model/Topper.d.ts +2 -2
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/Topper.test.js +22 -22
- package/lib/model/Topper.test.js.map +1 -1
- package/lib/model/schemas/capi/article.d.ts +5 -1
- package/lib/model/schemas/capi/article.js +1 -0
- package/lib/model/schemas/capi/article.js.map +1 -1
- package/lib/model/schemas/capi/audio.d.ts +5 -1
- package/lib/model/schemas/capi/audio.js +1 -0
- package/lib/model/schemas/capi/audio.js.map +1 -1
- package/lib/model/schemas/capi/base-schema.d.ts +5 -0
- package/lib/model/schemas/capi/base-schema.js +1 -0
- package/lib/model/schemas/capi/base-schema.js.map +1 -1
- package/lib/model/schemas/capi/content-package.d.ts +5 -1
- package/lib/model/schemas/capi/content-package.js +1 -0
- package/lib/model/schemas/capi/content-package.js.map +1 -1
- package/lib/model/schemas/capi/custom-code-component.d.ts +3 -0
- package/lib/model/schemas/capi/index.d.ts +28 -5
- package/lib/model/schemas/capi/live-blog-package.d.ts +5 -1
- package/lib/model/schemas/capi/live-blog-package.js +1 -0
- package/lib/model/schemas/capi/live-blog-package.js.map +1 -1
- package/lib/model/schemas/capi/placeholder.d.ts +5 -1
- package/lib/model/schemas/capi/placeholder.js +1 -0
- package/lib/model/schemas/capi/placeholder.js.map +1 -1
- package/lib/model/schemas/capi/video.d.ts +5 -1
- package/lib/model/schemas/capi/video.js +1 -0
- package/lib/model/schemas/capi/video.js.map +1 -1
- package/lib/resolvers/content-tree/references/CustomCodeComponent.js.map +1 -1
- package/lib/resolvers/content-tree/references/Recommended.d.ts +1 -1
- package/lib/resolvers/content-tree/references/index.d.ts +2 -2
- package/lib/resolvers/content-tree/tagMappings.d.ts +2 -2
- package/lib/resolvers/content-tree/tagMappings.js.map +1 -1
- package/lib/resolvers/content-tree/updateTreeWithReferenceIds.d.ts +2 -2
- package/lib/resolvers/content-tree/updateTreeWithReferenceIds.js.map +1 -1
- package/lib/resolvers/content.d.ts +251 -235
- package/lib/resolvers/content.js +27 -0
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/core.d.ts +4 -4
- package/lib/resolvers/core.js +12 -2
- package/lib/resolvers/core.js.map +1 -1
- package/lib/resolvers/index.d.ts +276 -260
- package/lib/resolvers/list.d.ts +9 -9
- package/lib/resolvers/teaser.d.ts +13 -13
- package/package.json +1 -1
- package/queries/article.graphql +19 -0
- package/src/datasources/capi.test.ts +3 -3
- package/src/datasources/capi.ts +6 -9
- package/src/fixtures/dummyContext.ts +2 -2
- package/src/generated/index.ts +41 -23
- package/src/model/Byline.ts +2 -2
- package/src/model/{CapiResponse.test.ts → Content.test.ts} +65 -17
- package/src/model/{CapiResponse.ts → Content.ts} +24 -17
- package/src/model/LeadFlourish.test.ts +6 -5
- package/src/model/LeadFlourish.ts +2 -5
- package/src/model/{CapiList.ts → List.ts} +5 -5
- package/src/model/RichText.ts +2 -2
- package/src/model/Topper.test.ts +23 -26
- package/src/model/Topper.ts +2 -5
- package/src/model/schemas/capi/article.ts +1 -0
- package/src/model/schemas/capi/audio.ts +1 -0
- package/src/model/schemas/capi/base-schema.ts +1 -0
- package/src/model/schemas/capi/content-package.ts +1 -0
- package/src/model/schemas/capi/live-blog-package.ts +1 -0
- package/src/model/schemas/capi/placeholder.ts +1 -0
- package/src/model/schemas/capi/video.ts +1 -0
- package/src/resolvers/content-tree/references/CustomCodeComponent.ts +2 -2
- package/src/resolvers/content-tree/references/index.ts +2 -2
- package/src/resolvers/content-tree/tagMappings.ts +2 -2
- package/src/resolvers/content-tree/updateTreeWithReferenceIds.ts +2 -2
- package/src/resolvers/content.ts +27 -0
- package/src/resolvers/core.ts +12 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +19 -1
- package/lib/model/CapiList.js.map +0 -1
- package/lib/model/CapiResponse.js.map +0 -1
- package/lib/model/CapiResponse.test.js.map +0 -1
- /package/lib/model/{CapiResponse.test.d.ts → Content.test.d.ts} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Content } from './Content'
|
|
2
2
|
import { baseCapiObject } from '../fixtures/capiObject'
|
|
3
3
|
import cloneDeep from 'clone-deep'
|
|
4
4
|
import context from '../fixtures/dummyContext'
|
|
5
5
|
|
|
6
|
-
describe('
|
|
6
|
+
describe('Content', () => {
|
|
7
7
|
describe('Content ID', () => {
|
|
8
8
|
test('returns the UUID', () => {
|
|
9
9
|
const article = cloneDeep(baseCapiObject)
|
|
10
|
-
const capiResponse = new
|
|
10
|
+
const capiResponse = new Content(article, context)
|
|
11
11
|
|
|
12
12
|
expect(capiResponse.id()).toEqual('3071cae8-a27c-4ab4-a0d4-7bb00df1d477')
|
|
13
13
|
})
|
|
@@ -17,7 +17,7 @@ describe('CAPI response', () => {
|
|
|
17
17
|
test(`uses first entry in types if there's no type`, () => {
|
|
18
18
|
const article = cloneDeep(baseCapiObject)
|
|
19
19
|
delete article.type
|
|
20
|
-
const capiResponseArticle = new
|
|
20
|
+
const capiResponseArticle = new Content(article, context)
|
|
21
21
|
|
|
22
22
|
expect(capiResponseArticle.type()).toEqual('Article')
|
|
23
23
|
})
|
|
@@ -28,11 +28,8 @@ describe('CAPI response', () => {
|
|
|
28
28
|
...baseCapiObject,
|
|
29
29
|
type: 'http://www.ft.com/ontology/content/LiveBlogPackage',
|
|
30
30
|
})
|
|
31
|
-
const capiResponseArticle = new
|
|
32
|
-
const capiResponseLiveBlogPackage = new
|
|
33
|
-
liveBlogPackage,
|
|
34
|
-
context
|
|
35
|
-
)
|
|
31
|
+
const capiResponseArticle = new Content(article, context)
|
|
32
|
+
const capiResponseLiveBlogPackage = new Content(liveBlogPackage, context)
|
|
36
33
|
|
|
37
34
|
expect(capiResponseArticle.type()).toEqual('Article')
|
|
38
35
|
expect(capiResponseLiveBlogPackage.type()).toEqual('LiveBlogPackage')
|
|
@@ -43,7 +40,7 @@ describe('CAPI response', () => {
|
|
|
43
40
|
...baseCapiObject,
|
|
44
41
|
type: 'http://www.ft.com/ontology/content/Unknown',
|
|
45
42
|
})
|
|
46
|
-
const capiResponse = new
|
|
43
|
+
const capiResponse = new Content(unknown, context)
|
|
47
44
|
expect(() => capiResponse.type()).toThrowError(
|
|
48
45
|
'Content type is invalid: http://www.ft.com/ontology/content/Unknown'
|
|
49
46
|
)
|
|
@@ -53,7 +50,7 @@ describe('CAPI response', () => {
|
|
|
53
50
|
describe('Publish timestamp', () => {
|
|
54
51
|
test('generates a timestamp from the published date', () => {
|
|
55
52
|
const article = cloneDeep(baseCapiObject)
|
|
56
|
-
const capiResponse = new
|
|
53
|
+
const capiResponse = new Content(article, context)
|
|
57
54
|
|
|
58
55
|
expect(capiResponse.publishedTimestamp()).toEqual(1712052009935)
|
|
59
56
|
})
|
|
@@ -62,7 +59,7 @@ describe('CAPI response', () => {
|
|
|
62
59
|
describe('Modified timestamp', () => {
|
|
63
60
|
test('generates a timestamp from the last modified date', () => {
|
|
64
61
|
const article = cloneDeep(baseCapiObject)
|
|
65
|
-
const capiResponse = new
|
|
62
|
+
const capiResponse = new Content(article, context)
|
|
66
63
|
|
|
67
64
|
expect(capiResponse.modifiedTimestamp()).toEqual(1712061296789)
|
|
68
65
|
})
|
|
@@ -92,7 +89,7 @@ describe('CAPI response', () => {
|
|
|
92
89
|
],
|
|
93
90
|
})
|
|
94
91
|
|
|
95
|
-
const capiResponse = new
|
|
92
|
+
const capiResponse = new Content(liveBlogPackage, context)
|
|
96
93
|
const liveBlogPosts = await capiResponse.liveBlogPosts()
|
|
97
94
|
expect(liveBlogPosts[0]?.id()).toEqual(
|
|
98
95
|
'00000000-0000-0000-0000-000000000002'
|
|
@@ -122,7 +119,7 @@ describe('CAPI response', () => {
|
|
|
122
119
|
}
|
|
123
120
|
}),
|
|
124
121
|
})
|
|
125
|
-
const capiResponse = new
|
|
122
|
+
const capiResponse = new Content(liveBlogPackage, context)
|
|
126
123
|
|
|
127
124
|
test('returns all posts when no argument specified', async () => {
|
|
128
125
|
const liveBlogPostsConnection =
|
|
@@ -245,7 +242,7 @@ describe('CAPI response', () => {
|
|
|
245
242
|
'http://www.ft.com/thing/724b5e36-6d45-4cf1-b1c2-3f676b21f21b',
|
|
246
243
|
],
|
|
247
244
|
})
|
|
248
|
-
const capiResponse = new
|
|
245
|
+
const capiResponse = new Content(article, context)
|
|
249
246
|
|
|
250
247
|
expect(capiResponse.isPartnerContent()).toBe(true)
|
|
251
248
|
})
|
|
@@ -254,7 +251,7 @@ describe('CAPI response', () => {
|
|
|
254
251
|
...baseCapiObject,
|
|
255
252
|
publication: null,
|
|
256
253
|
})
|
|
257
|
-
const capiResponse = new
|
|
254
|
+
const capiResponse = new Content(article, context)
|
|
258
255
|
|
|
259
256
|
expect(capiResponse.isPartnerContent()).toBe(false)
|
|
260
257
|
})
|
|
@@ -268,7 +265,7 @@ describe('CAPI response', () => {
|
|
|
268
265
|
const timingOutContext = cloneDeep(context)
|
|
269
266
|
timingOutContext.dataSources.capi.getPerson = () =>
|
|
270
267
|
Promise.reject(new DOMException(undefined, 'TimeoutError'))
|
|
271
|
-
const capiResponse = new
|
|
268
|
+
const capiResponse = new Content(article, timingOutContext)
|
|
272
269
|
|
|
273
270
|
const authors = await capiResponse.authors()
|
|
274
271
|
const primaryAuthor = await capiResponse.primaryAuthor()
|
|
@@ -277,4 +274,55 @@ describe('CAPI response', () => {
|
|
|
277
274
|
expect(primaryAuthor?.['person'].id).toBe(authorAnnotation.id)
|
|
278
275
|
})
|
|
279
276
|
})
|
|
277
|
+
|
|
278
|
+
describe('isFTEdit', () => {
|
|
279
|
+
test('identifies articles that are part of an FT Edit edition', () => {
|
|
280
|
+
const article = cloneDeep({
|
|
281
|
+
...baseCapiObject,
|
|
282
|
+
internalAnalyticsTags: 'ftedit',
|
|
283
|
+
})
|
|
284
|
+
const capiResponse = new Content(article, context)
|
|
285
|
+
|
|
286
|
+
expect(capiResponse.isFTEdit()).toBe(true)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('identifies articles that are not part of an FT Edit edition', () => {
|
|
290
|
+
const article = cloneDeep({
|
|
291
|
+
...baseCapiObject,
|
|
292
|
+
internalAnalyticsTags: 'test',
|
|
293
|
+
})
|
|
294
|
+
const capiResponse = new Content(article, context)
|
|
295
|
+
|
|
296
|
+
expect(capiResponse.isFTEdit()).toBe(false)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('ignores typos', () => {
|
|
300
|
+
const article = cloneDeep({
|
|
301
|
+
...baseCapiObject,
|
|
302
|
+
internalAnalyticsTags: 'ft edit',
|
|
303
|
+
})
|
|
304
|
+
const capiResponse = new Content(article, context)
|
|
305
|
+
|
|
306
|
+
expect(capiResponse.isFTEdit()).toBe(false)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('handles multiple values', () => {
|
|
310
|
+
const article = cloneDeep({
|
|
311
|
+
...baseCapiObject,
|
|
312
|
+
internalAnalyticsTags: 'test,ftedit,test2',
|
|
313
|
+
})
|
|
314
|
+
const capiResponse = new Content(article, context)
|
|
315
|
+
|
|
316
|
+
expect(capiResponse.isFTEdit()).toBe(true)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
test('handles missing `internalAnalyticsTags` property', () => {
|
|
320
|
+
const article = cloneDeep({
|
|
321
|
+
...baseCapiObject,
|
|
322
|
+
})
|
|
323
|
+
const capiResponse = new Content(article, context)
|
|
324
|
+
|
|
325
|
+
expect(capiResponse.isFTEdit()).toBe(false)
|
|
326
|
+
})
|
|
327
|
+
})
|
|
280
328
|
})
|
|
@@ -100,18 +100,18 @@ const baselineContentSchema = z.object({
|
|
|
100
100
|
|
|
101
101
|
type BaselineContent = z.infer<typeof baselineContentSchema>
|
|
102
102
|
|
|
103
|
-
export class
|
|
103
|
+
export class Content {
|
|
104
104
|
constructor(
|
|
105
105
|
private capiData: ContentTypeSchemas,
|
|
106
106
|
private context: QueryContext,
|
|
107
|
-
private packageContainer?:
|
|
107
|
+
private packageContainer?: Content
|
|
108
108
|
) {}
|
|
109
109
|
|
|
110
110
|
static fromJSON(
|
|
111
111
|
content: unknown,
|
|
112
112
|
context: QueryContext,
|
|
113
|
-
packageContainer?:
|
|
114
|
-
):
|
|
113
|
+
packageContainer?: Content
|
|
114
|
+
): Content {
|
|
115
115
|
// if the content doesn't meet the baseline schema, something's gone
|
|
116
116
|
// unexpectedly very wrong. throwing an non-operational error is the
|
|
117
117
|
// right thing to do in that case, so we call .parse not .safeParse
|
|
@@ -160,11 +160,7 @@ export class CapiResponse {
|
|
|
160
160
|
|
|
161
161
|
// we can "safely" cast content to the schema return types here. if the type is
|
|
162
162
|
// incorrect, we've at least logged that error.
|
|
163
|
-
return new
|
|
164
|
-
content as ContentTypeSchemas,
|
|
165
|
-
context,
|
|
166
|
-
packageContainer
|
|
167
|
-
)
|
|
163
|
+
return new Content(content as ContentTypeSchemas, context, packageContainer)
|
|
168
164
|
}
|
|
169
165
|
|
|
170
166
|
body() {
|
|
@@ -456,7 +452,7 @@ export class CapiResponse {
|
|
|
456
452
|
if ('title' in this.capiData) {
|
|
457
453
|
const clone = cloneDeep(this.capiData)
|
|
458
454
|
clone.title = title
|
|
459
|
-
return new
|
|
455
|
+
return new Content(clone, this.context, this.packageContainer)
|
|
460
456
|
}
|
|
461
457
|
return this
|
|
462
458
|
}
|
|
@@ -540,7 +536,7 @@ export class CapiResponse {
|
|
|
540
536
|
|
|
541
537
|
teaser() {
|
|
542
538
|
const clone = cloneDeep(this.capiData)
|
|
543
|
-
return new
|
|
539
|
+
return new Content(clone, this.context, this.packageContainer)
|
|
544
540
|
}
|
|
545
541
|
|
|
546
542
|
isEditorsChoice() {
|
|
@@ -710,7 +706,7 @@ export class CapiResponse {
|
|
|
710
706
|
async contains({
|
|
711
707
|
surroundingArticles,
|
|
712
708
|
fromId,
|
|
713
|
-
}: Partial<ContentPackageContainsArgs> = {}): Promise<
|
|
709
|
+
}: Partial<ContentPackageContainsArgs> = {}): Promise<Content[] | null> {
|
|
714
710
|
let edges
|
|
715
711
|
if (fromId && surroundingArticles) {
|
|
716
712
|
const afterId = await this.containsConnection({
|
|
@@ -734,7 +730,7 @@ export class CapiResponse {
|
|
|
734
730
|
transformer?: (
|
|
735
731
|
contains: LiveBlogPackage['contains']
|
|
736
732
|
) => LiveBlogPackage['contains']
|
|
737
|
-
): Promise<Connection<
|
|
733
|
+
): Promise<Connection<Content>> {
|
|
738
734
|
if ('contains' in this.capiData) {
|
|
739
735
|
const { contains: containsUntransformed } = this.capiData
|
|
740
736
|
const contains = transformer
|
|
@@ -790,7 +786,7 @@ export class CapiResponse {
|
|
|
790
786
|
|
|
791
787
|
const edges = results
|
|
792
788
|
.filter(
|
|
793
|
-
(result): result is PromiseFulfilledResult<
|
|
789
|
+
(result): result is PromiseFulfilledResult<Content> =>
|
|
794
790
|
result.status === 'fulfilled'
|
|
795
791
|
)
|
|
796
792
|
.map((result) => {
|
|
@@ -849,7 +845,7 @@ export class CapiResponse {
|
|
|
849
845
|
|
|
850
846
|
async liveBlogPosts(
|
|
851
847
|
args?: LiveBlogPackageLiveBlogPostsArgs
|
|
852
|
-
): Promise<
|
|
848
|
+
): Promise<Content[] | []> {
|
|
853
849
|
const containsConnections = await this.containsConnection(
|
|
854
850
|
{
|
|
855
851
|
first: args?.count ?? undefined,
|
|
@@ -865,7 +861,7 @@ export class CapiResponse {
|
|
|
865
861
|
|
|
866
862
|
async liveBlogPostsConnection(
|
|
867
863
|
args: ConnectionArguments
|
|
868
|
-
): Promise<Connection<
|
|
864
|
+
): Promise<Connection<Content>> {
|
|
869
865
|
return this.containsConnection(args, this.handleLiveBlogPosts.bind(this))
|
|
870
866
|
}
|
|
871
867
|
|
|
@@ -881,7 +877,7 @@ export class CapiResponse {
|
|
|
881
877
|
return false
|
|
882
878
|
}
|
|
883
879
|
|
|
884
|
-
async pinnedPost(): Promise<
|
|
880
|
+
async pinnedPost(): Promise<Content | null> {
|
|
885
881
|
if ('pinnedPosts' in this.capiData) {
|
|
886
882
|
const pinnedPosts = this.capiData.pinnedPosts
|
|
887
883
|
const pinnedPostId = pinnedPosts[0]
|
|
@@ -934,6 +930,17 @@ export class CapiResponse {
|
|
|
934
930
|
return false
|
|
935
931
|
}
|
|
936
932
|
|
|
933
|
+
isFTEdit(): boolean {
|
|
934
|
+
if (
|
|
935
|
+
'internalAnalyticsTags' in this.capiData &&
|
|
936
|
+
this.capiData.internalAnalyticsTags
|
|
937
|
+
) {
|
|
938
|
+
return this.capiData.internalAnalyticsTags.includes('ftedit')
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return false
|
|
942
|
+
}
|
|
943
|
+
|
|
937
944
|
design(): Design {
|
|
938
945
|
if ('design' in this.capiData && this.capiData.design.theme) {
|
|
939
946
|
return { theme: this.capiData.design.theme }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LeadFlourish } from './LeadFlourish'
|
|
2
2
|
import { FlourishSource } from './FlourishSource'
|
|
3
|
-
import {
|
|
3
|
+
import { Content } from './Content'
|
|
4
4
|
import { baseCapiObject } from '../fixtures/capiObject'
|
|
5
5
|
import type { QueryContext } from '..'
|
|
6
6
|
import cloneDeep from 'clone-deep'
|
|
@@ -13,7 +13,6 @@ const leadFlourishData = {
|
|
|
13
13
|
type: 'test-type',
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
describe('LeadFlourish', () => {
|
|
18
17
|
let leadFlourish: LeadFlourish
|
|
19
18
|
|
|
@@ -27,15 +26,17 @@ describe('LeadFlourish', () => {
|
|
|
27
26
|
}
|
|
28
27
|
jest.spyOn(FlourishSource, 'getImageMetadata').mockResolvedValue({
|
|
29
28
|
width: 800,
|
|
30
|
-
height: 600
|
|
29
|
+
height: 600,
|
|
31
30
|
})
|
|
32
|
-
const capiResponse = new
|
|
31
|
+
const capiResponse = new Content(clonedBase, context)
|
|
33
32
|
leadFlourish = new LeadFlourish(capiResponse, context)
|
|
34
33
|
})
|
|
35
34
|
|
|
36
35
|
describe('fallbackImage', () => {
|
|
37
36
|
it('should instantiate a new FlourishSource', async () => {
|
|
38
|
-
await expect(leadFlourish.fallbackImage()).resolves.toBeInstanceOf(
|
|
37
|
+
await expect(leadFlourish.fallbackImage()).resolves.toBeInstanceOf(
|
|
38
|
+
FlourishSource
|
|
39
|
+
)
|
|
39
40
|
})
|
|
40
41
|
})
|
|
41
42
|
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Content } from './Content'
|
|
2
2
|
import type { QueryContext } from '..'
|
|
3
3
|
import { FlourishSource } from './FlourishSource'
|
|
4
4
|
|
|
5
5
|
export class LeadFlourish {
|
|
6
6
|
#systemCode: string
|
|
7
|
-
constructor(
|
|
8
|
-
private capiResponse: CapiResponse,
|
|
9
|
-
private context: QueryContext
|
|
10
|
-
) {
|
|
7
|
+
constructor(private capiResponse: Content, private context: QueryContext) {
|
|
11
8
|
this.#systemCode = context.systemCode ?? 'cp-content-pipeline'
|
|
12
9
|
}
|
|
13
10
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { BaseError, OperationalError } from '@dotcom-reliability-kit/errors'
|
|
2
2
|
import { QueryContext } from '..'
|
|
3
3
|
import { uuidFromUrl } from '../helpers/metadata'
|
|
4
|
-
import { List, listSchema } from './schemas/capi/list'
|
|
4
|
+
import { List as ListSchema, listSchema } from './schemas/capi/list'
|
|
5
5
|
import flattenFormattedZodIssues from '../helpers/flatten-formatted-zod-errors'
|
|
6
6
|
|
|
7
|
-
export class
|
|
8
|
-
static fromJSON(list: unknown, context: QueryContext):
|
|
7
|
+
export class List {
|
|
8
|
+
static fromJSON(list: unknown, context: QueryContext): List {
|
|
9
9
|
const result = listSchema.safeParse(list)
|
|
10
10
|
|
|
11
11
|
if (!result.success) {
|
|
@@ -31,10 +31,10 @@ export class CapiList {
|
|
|
31
31
|
)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
return new
|
|
34
|
+
return new List(list as ListSchema, context)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
constructor(private list:
|
|
37
|
+
constructor(private list: ListSchema, private context: QueryContext) {}
|
|
38
38
|
|
|
39
39
|
async items() {
|
|
40
40
|
if (!this.list.items) return []
|
package/src/model/RichText.ts
CHANGED
|
@@ -3,7 +3,7 @@ import extractText from '../resolvers/content-tree/extractText'
|
|
|
3
3
|
import updateTreeWithReferenceIds from '../resolvers/content-tree/updateTreeWithReferenceIds'
|
|
4
4
|
import { LiteralUnionScalarValues } from '../resolvers/literal-union'
|
|
5
5
|
import { RichTextSource } from '../resolvers/scalars'
|
|
6
|
-
import {
|
|
6
|
+
import { Content } from './Content'
|
|
7
7
|
import {
|
|
8
8
|
commonTagMappings,
|
|
9
9
|
articleTagMappings,
|
|
@@ -16,7 +16,7 @@ export class RichText {
|
|
|
16
16
|
constructor(
|
|
17
17
|
public source: LiteralUnionScalarValues<typeof RichTextSource>,
|
|
18
18
|
private value: string | null,
|
|
19
|
-
private contentApiData?:
|
|
19
|
+
private contentApiData?: Content
|
|
20
20
|
) {}
|
|
21
21
|
|
|
22
22
|
raw() {
|
package/src/model/Topper.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Topper } from './Topper'
|
|
2
2
|
import { Person } from './Person'
|
|
3
3
|
import { baseCapiObject } from '../fixtures/capiObject'
|
|
4
|
-
import {
|
|
4
|
+
import { Content } from './Content'
|
|
5
5
|
import { predicates } from './Concept'
|
|
6
6
|
import cloneDeep from 'clone-deep'
|
|
7
7
|
import conceptIds from '@financial-times/n-concept-ids'
|
|
@@ -18,7 +18,7 @@ const context = {} as unknown as QueryContext
|
|
|
18
18
|
describe('produces the correct types', () => {
|
|
19
19
|
it('defaults when there is no topper, is normal headline, is paper background', () => {
|
|
20
20
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
21
|
-
const capiResponse = new
|
|
21
|
+
const capiResponse = new Content(clonedBase, context)
|
|
22
22
|
const topper = new Topper(capiResponse, context)
|
|
23
23
|
|
|
24
24
|
const desired = 'BasicTopper'
|
|
@@ -47,7 +47,7 @@ describe('produces the correct types', () => {
|
|
|
47
47
|
|
|
48
48
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
49
49
|
clonedBase.topper = splitTopper
|
|
50
|
-
const capiResponse = new
|
|
50
|
+
const capiResponse = new Content(clonedBase, context)
|
|
51
51
|
const topper = new Topper(capiResponse, context)
|
|
52
52
|
|
|
53
53
|
const desiredType = 'SplitTextTopper'
|
|
@@ -76,7 +76,7 @@ describe('produces the correct types', () => {
|
|
|
76
76
|
|
|
77
77
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
78
78
|
clonedBase.topper = splitTopper
|
|
79
|
-
const capiResponse = new
|
|
79
|
+
const capiResponse = new Content(clonedBase, context)
|
|
80
80
|
const topper = new Topper(capiResponse, context)
|
|
81
81
|
|
|
82
82
|
const desired = 'FullBleedTopper'
|
|
@@ -105,7 +105,7 @@ describe('produces the correct types', () => {
|
|
|
105
105
|
|
|
106
106
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
107
107
|
clonedBase.topper = splitTopper
|
|
108
|
-
const capiResponse = new
|
|
108
|
+
const capiResponse = new Content(clonedBase, context)
|
|
109
109
|
const topper = new Topper(capiResponse, context)
|
|
110
110
|
|
|
111
111
|
const desired = 'FullBleedTopper'
|
|
@@ -125,7 +125,7 @@ describe('produces the correct types', () => {
|
|
|
125
125
|
type: '',
|
|
126
126
|
types: [],
|
|
127
127
|
})
|
|
128
|
-
const capiResponse = new
|
|
128
|
+
const capiResponse = new Content(clonedBase, context)
|
|
129
129
|
const topper = new Topper(capiResponse, context)
|
|
130
130
|
|
|
131
131
|
const desired = 'OpinionTopper'
|
|
@@ -155,7 +155,7 @@ describe('produces the correct types', () => {
|
|
|
155
155
|
type: '',
|
|
156
156
|
types: [],
|
|
157
157
|
})
|
|
158
|
-
const capiResponse = new
|
|
158
|
+
const capiResponse = new Content(clonedBase, context)
|
|
159
159
|
const topper = new Topper(capiResponse, context)
|
|
160
160
|
|
|
161
161
|
const desired = 'BrandedTopper'
|
|
@@ -186,7 +186,7 @@ describe('produces the correct types', () => {
|
|
|
186
186
|
layoutWidth: 'full-width',
|
|
187
187
|
backgroundColour: 'paper',
|
|
188
188
|
}
|
|
189
|
-
const capiResponse = new
|
|
189
|
+
const capiResponse = new Content(clonedBase, context)
|
|
190
190
|
const topper = new Topper(capiResponse, context)
|
|
191
191
|
|
|
192
192
|
const desired = 'TopperWithFlourish'
|
|
@@ -204,7 +204,7 @@ describe('produces the correct types', () => {
|
|
|
204
204
|
it('live blog topper, is full bleed, large headline, paper background', () => {
|
|
205
205
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
206
206
|
clonedBase.type = 'http://www.ft.com/ontology/content/LiveBlogPackage'
|
|
207
|
-
const capiResponse = new
|
|
207
|
+
const capiResponse = new Content(clonedBase, context)
|
|
208
208
|
const topper = new Topper(capiResponse, context)
|
|
209
209
|
|
|
210
210
|
const desired = 'FullBleedTopper'
|
|
@@ -235,7 +235,7 @@ describe('it retrieves the correct headline', () => {
|
|
|
235
235
|
}
|
|
236
236
|
clonedBase.topper = topperData
|
|
237
237
|
|
|
238
|
-
const capiResponse = new
|
|
238
|
+
const capiResponse = new Content(clonedBase, context)
|
|
239
239
|
const topper = new Topper(capiResponse, context)
|
|
240
240
|
|
|
241
241
|
const desired = topperData.headline
|
|
@@ -247,7 +247,7 @@ describe('it retrieves the correct headline', () => {
|
|
|
247
247
|
it('when there is one headline', () => {
|
|
248
248
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
249
249
|
|
|
250
|
-
const capiResponse = new
|
|
250
|
+
const capiResponse = new Content(clonedBase, context)
|
|
251
251
|
const topper = new Topper(capiResponse, context)
|
|
252
252
|
|
|
253
253
|
const desired = clonedBase.title
|
|
@@ -268,7 +268,7 @@ describe('produces the correct intro', () => {
|
|
|
268
268
|
clonedBase.standfirst =
|
|
269
269
|
'Decision follows 10 days of pressure in wake of controversial mini-Budget'
|
|
270
270
|
|
|
271
|
-
const capiResponse = new
|
|
271
|
+
const capiResponse = new Content(clonedBase, context)
|
|
272
272
|
const topper = new Topper(capiResponse, context)
|
|
273
273
|
|
|
274
274
|
const desired = {
|
|
@@ -286,7 +286,7 @@ describe('produces the correct intro', () => {
|
|
|
286
286
|
'Decision follows 10 days of pressure in wake of controversial mini-Budget'
|
|
287
287
|
clonedBase.standfirst = standfirst
|
|
288
288
|
|
|
289
|
-
const capiResponse = new
|
|
289
|
+
const capiResponse = new Content(clonedBase, context)
|
|
290
290
|
const topper = new Topper(capiResponse, context)
|
|
291
291
|
|
|
292
292
|
const desired = {
|
|
@@ -302,7 +302,7 @@ describe('produces the correct intro', () => {
|
|
|
302
302
|
describe('produces the correct layout', () => {
|
|
303
303
|
it('defaults to branded if there is no layout', () => {
|
|
304
304
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
305
|
-
const capiResponse = new
|
|
305
|
+
const capiResponse = new Content(clonedBase, context)
|
|
306
306
|
const topper = new Topper(capiResponse, context)
|
|
307
307
|
const desired = 'branded'
|
|
308
308
|
const actual = topper.layout()
|
|
@@ -335,7 +335,7 @@ describe('produces the correct background colour', () => {
|
|
|
335
335
|
|
|
336
336
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
337
337
|
clonedBase.topper = splitTopper
|
|
338
|
-
const capiResponse = new
|
|
338
|
+
const capiResponse = new Content(clonedBase, context)
|
|
339
339
|
const topper = new Topper(capiResponse, context)
|
|
340
340
|
|
|
341
341
|
expect(topper.backgroundColour()).toEqual(expected)
|
|
@@ -371,10 +371,7 @@ describe('produces correct images', () => {
|
|
|
371
371
|
},
|
|
372
372
|
],
|
|
373
373
|
}
|
|
374
|
-
const capiResponse = new
|
|
375
|
-
withLeadImagesAndDisplayMainImage,
|
|
376
|
-
context
|
|
377
|
-
)
|
|
374
|
+
const capiResponse = new Content(withLeadImagesAndDisplayMainImage, context)
|
|
378
375
|
const topper = new Topper(capiResponse, context)
|
|
379
376
|
const fallback = topper.fallbackImage()
|
|
380
377
|
expect(fallback?.url()).toEqual(
|
|
@@ -383,7 +380,7 @@ describe('produces correct images', () => {
|
|
|
383
380
|
})
|
|
384
381
|
it('uses the main image as fallback if no lead images', () => {
|
|
385
382
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
386
|
-
const capiResponse = new
|
|
383
|
+
const capiResponse = new Content(clonedBase, context)
|
|
387
384
|
const topper = new Topper(capiResponse, context)
|
|
388
385
|
const fallback = topper.fallbackImage()
|
|
389
386
|
expect(fallback?.url()).toEqual(
|
|
@@ -428,7 +425,7 @@ describe('produces the correct metadata tags', () => {
|
|
|
428
425
|
|
|
429
426
|
it('uses the annotation with the predicate hasDisplayTag for the displayConcept', () => {
|
|
430
427
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
431
|
-
const capiResponse = new
|
|
428
|
+
const capiResponse = new Content(clonedBase, context)
|
|
432
429
|
const topper = new Topper(capiResponse, context)
|
|
433
430
|
const displayConcept = topper.displayConcept()
|
|
434
431
|
expect(displayConcept?.prefLabel()).toEqual('UK politics & policy')
|
|
@@ -442,7 +439,7 @@ describe('produces the correct metadata tags', () => {
|
|
|
442
439
|
...clonedBase,
|
|
443
440
|
annotations: [...clonedBase.annotations, opinionGenre, lexBrand],
|
|
444
441
|
}
|
|
445
|
-
const capiResponse = new
|
|
442
|
+
const capiResponse = new Content(lexArticle, context)
|
|
446
443
|
const topper = new Topper(capiResponse, context)
|
|
447
444
|
const displayConcept = topper.displayConcept()
|
|
448
445
|
expect(displayConcept?.prefLabel()).toEqual('Lex')
|
|
@@ -456,7 +453,7 @@ describe('produces the correct metadata tags', () => {
|
|
|
456
453
|
...clonedBase,
|
|
457
454
|
annotations: [...clonedBase.annotations, opinionGenre],
|
|
458
455
|
}
|
|
459
|
-
const capiResponse = new
|
|
456
|
+
const capiResponse = new Content(opinionArticle, context)
|
|
460
457
|
const topper = new Topper(capiResponse, context)
|
|
461
458
|
const displayConcept = topper.displayConcept()
|
|
462
459
|
expect(displayConcept?.prefLabel()).toEqual('UK politics & policy')
|
|
@@ -470,7 +467,7 @@ describe('produces the correct metadata tags', () => {
|
|
|
470
467
|
...clonedBase,
|
|
471
468
|
annotations: [...clonedBase.annotations, lexBrand],
|
|
472
469
|
}
|
|
473
|
-
const capiResponse = new
|
|
470
|
+
const capiResponse = new Content(opinionArticle, context)
|
|
474
471
|
const topper = new Topper(capiResponse, context)
|
|
475
472
|
const displayConcept = topper.displayConcept()
|
|
476
473
|
expect(displayConcept?.prefLabel()).toEqual('UK politics & policy')
|
|
@@ -481,11 +478,11 @@ describe('produces the correct metadata tags', () => {
|
|
|
481
478
|
|
|
482
479
|
describe('headshot method', () => {
|
|
483
480
|
let topper: Topper
|
|
484
|
-
let capiResponse:
|
|
481
|
+
let capiResponse: Content
|
|
485
482
|
|
|
486
483
|
beforeEach(() => {
|
|
487
484
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
488
|
-
capiResponse = new
|
|
485
|
+
capiResponse = new Content(clonedBase, context)
|
|
489
486
|
topper = new Topper(capiResponse, context)
|
|
490
487
|
})
|
|
491
488
|
|
package/src/model/Topper.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Content } from './Content'
|
|
2
2
|
import { CAPIImage } from './Image'
|
|
3
3
|
import type { QueryContext } from '..'
|
|
4
4
|
import {
|
|
@@ -44,10 +44,7 @@ function isDarkBackground(backgroundColour: string): boolean {
|
|
|
44
44
|
export class Topper {
|
|
45
45
|
#systemCode: string
|
|
46
46
|
|
|
47
|
-
constructor(
|
|
48
|
-
private capiResponse: CapiResponse,
|
|
49
|
-
private context: QueryContext
|
|
50
|
-
) {
|
|
47
|
+
constructor(private capiResponse: Content, private context: QueryContext) {
|
|
51
48
|
this.#systemCode = context.systemCode ?? 'cp-content-pipeline'
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -235,6 +235,7 @@ export const baseMetadataSchema = z.object({
|
|
|
235
235
|
.optional(),
|
|
236
236
|
publication: z.array(z.string()).optional(),
|
|
237
237
|
clientName: z.string().optional(),
|
|
238
|
+
internalAnalyticsTags: z.string().optional(),
|
|
238
239
|
})
|
|
239
240
|
|
|
240
241
|
export const baseContentSchema = z.object({
|