@financial-times/cp-content-pipeline-schema 0.7.27 → 0.7.29

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 (40) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/fixtures/dummyContext.d.ts +3 -0
  3. package/lib/fixtures/dummyContext.js +24 -0
  4. package/lib/fixtures/dummyContext.js.map +1 -0
  5. package/lib/generated/index.d.ts +36 -13
  6. package/lib/model/Byline.d.ts +6 -3
  7. package/lib/model/Byline.js +14 -9
  8. package/lib/model/Byline.js.map +1 -1
  9. package/lib/model/Byline.test.js +19 -14
  10. package/lib/model/Byline.test.js.map +1 -1
  11. package/lib/model/CapiResponse.d.ts +5 -6
  12. package/lib/model/CapiResponse.js +15 -15
  13. package/lib/model/CapiResponse.js.map +1 -1
  14. package/lib/model/CapiResponse.test.js +8 -19
  15. package/lib/model/CapiResponse.test.js.map +1 -1
  16. package/lib/model/schemas/capi/content-package.d.ts +8 -8
  17. package/lib/model/schemas/capi/content-package.js +8 -2
  18. package/lib/model/schemas/capi/content-package.js.map +1 -1
  19. package/lib/resolvers/concept.js +1 -0
  20. package/lib/resolvers/concept.js.map +1 -1
  21. package/lib/resolvers/content.d.ts +4 -4
  22. package/lib/resolvers/content.js +4 -3
  23. package/lib/resolvers/content.js.map +1 -1
  24. package/lib/resolvers/index.d.ts +8 -8
  25. package/lib/resolvers/teaser.d.ts +4 -4
  26. package/package.json +1 -1
  27. package/src/fixtures/dummyContext.ts +28 -0
  28. package/src/generated/index.ts +50 -13
  29. package/src/model/Byline.test.ts +16 -14
  30. package/src/model/Byline.ts +27 -19
  31. package/src/model/CapiResponse.test.ts +1 -25
  32. package/src/model/CapiResponse.ts +20 -21
  33. package/src/model/schemas/capi/content-package.ts +17 -9
  34. package/src/resolvers/concept.ts +1 -3
  35. package/src/resolvers/content.ts +11 -3
  36. package/src/types/internal-content.d.ts +2 -0
  37. package/src/types/n-display-metadata.d.ts +2 -1
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/typedefs/concept.graphql +4 -3
  40. package/typedefs/content.graphql +7 -7
@@ -1,21 +1,23 @@
1
1
  import { ContentTree } from '@financial-times/content-tree'
2
2
  import { AuthorLink } from '../resolvers/content-tree/Workarounds'
3
- import { StructuredContent } from '../generated'
3
+ import { QueryContext } from '..'
4
4
 
5
5
  export class Byline {
6
6
  constructor(
7
7
  private byline: string,
8
- private authorUrlMapping: Map<string, string>
8
+ private vanity: boolean,
9
+ private authorUrlMapping: Map<string, string>,
10
+ private context: QueryContext
9
11
  ) {}
10
12
 
11
- buildBylineTree() {
13
+ async buildBylineTree() {
12
14
  // Normalise apostrophes in byline string
13
15
  const bylineWithCorrectApostrophes = this.byline.replace("'", '’')
14
16
 
15
17
  const split = this.#splitBylineByName(bylineWithCorrectApostrophes, [
16
18
  ...this.authorUrlMapping.keys(),
17
19
  ])
18
- return this.#buildUrlTree(this.authorUrlMapping, split)
20
+ return await this.#buildUrlTree(this.authorUrlMapping, split)
19
21
  }
20
22
 
21
23
  #splitBylineByName(byline: string, names: string[]) {
@@ -24,23 +26,29 @@ export class Byline {
24
26
  return byline.split(regex).filter((string) => string !== '')
25
27
  }
26
28
 
27
- #buildUrlTree(urlMapping: Map<string, string>, parts: string[]) {
28
- const children: (AuthorLink | ContentTree.Text)[] = parts.map((part) => {
29
- const url = urlMapping.get(part)
29
+ async #buildUrlTree(urlMapping: Map<string, string>, parts: string[]) {
30
+ const children: (AuthorLink | ContentTree.Text)[] = await Promise.all(
31
+ parts.map(async (part) => {
32
+ const fullUrl = urlMapping.get(part)
33
+ const vanityUrl =
34
+ this.vanity && fullUrl
35
+ ? await this.context.dataSources.vanityUrls.get(fullUrl)
36
+ : null
30
37
 
31
- if (url) {
32
- return {
33
- type: 'author-link',
34
- href: url,
35
- children: [{ type: 'text', value: part }],
38
+ if (fullUrl) {
39
+ return {
40
+ type: 'author-link',
41
+ href: vanityUrl || fullUrl,
42
+ children: [{ type: 'text', value: part }],
43
+ }
44
+ } else {
45
+ return {
46
+ type: 'text',
47
+ value: part,
48
+ }
36
49
  }
37
- } else {
38
- return {
39
- type: 'text',
40
- value: part,
41
- }
42
- }
43
- })
50
+ })
51
+ )
44
52
 
45
53
  return {
46
54
  tree: { type: 'root', children },
@@ -1,31 +1,7 @@
1
1
  import { CapiResponse } from './CapiResponse'
2
2
  import { baseCapiObject } from '../fixtures/capiObject'
3
- import { jest } from '@jest/globals'
4
3
  import cloneDeep from 'clone-deep'
5
-
6
- import type { QueryContext } from '..'
7
-
8
- const now = Date.now()
9
-
10
- const context = {
11
- dataSources: {
12
- capi: {
13
- getContent: (id: string) =>
14
- Promise.resolve(
15
- new CapiResponse(
16
- cloneDeep({
17
- ...baseCapiObject,
18
- id,
19
- publishedDate: new Date(
20
- now + parseInt(id.slice(-1), 10)
21
- ).toISOString(),
22
- }),
23
- {} as unknown as QueryContext
24
- )
25
- ),
26
- },
27
- },
28
- } as unknown as QueryContext
4
+ import context from '../fixtures/dummyContext'
29
5
 
30
6
  describe('CAPI response', () => {
31
7
  describe('Content ID', () => {
@@ -1,10 +1,7 @@
1
1
  import type {
2
- Article,
3
- LiveBlogPackage,
4
- ContentPackage,
5
2
  ImageSet,
6
- Audio,
7
3
  Annotation,
4
+ ContentTypeSchemas,
8
5
  } from '../types/internal-content'
9
6
  import conceptIds from '@financial-times/n-concept-ids'
10
7
  import metadata, { TeaserMetadata } from '@financial-times/n-display-metadata'
@@ -34,8 +31,6 @@ import {
34
31
 
35
32
  import { Media } from '../generated'
36
33
 
37
- type ContentTypeSchemas = Article | LiveBlogPackage | ContentPackage | Audio
38
-
39
34
  type Design = {
40
35
  theme: LiteralUnionScalarValues<typeof PackageDesign>
41
36
  layout?: 'default' | 'wide'
@@ -276,23 +271,29 @@ export class CapiResponse {
276
271
  return new CapiResponse(clone, this.context)
277
272
  }
278
273
 
279
- #teaserMetadata() {
280
- return metadata.teaser({ annotations: this.#rawAnnotations() })
274
+ async #teaserMetadata() {
275
+ const containedIn = await this.containedIn()
276
+
277
+ return metadata.teaser({
278
+ annotations: this.#rawAnnotations(),
279
+ containedIn: containedIn ? [containedIn.capiData] : null,
280
+ })
281
281
  }
282
282
 
283
- teaserMetadataLink(
283
+ async teaserMetadataLink(
284
284
  field: ConditionalKeys<TeaserMetadata, Annotation | null>
285
285
  ) {
286
- const meta = this.#teaserMetadata()
286
+ const meta = await this.#teaserMetadata()
287
287
  const metaField = meta[field]
288
+
288
289
  return metaField ? new Concept(metaField, this.context) : null
289
290
  }
290
291
 
291
- teaserMetadataText(
292
+ async teaserMetadataText(
292
293
  field: ConditionalKeys<TeaserMetadata, string> | 'suffixText'
293
294
  ) {
294
295
  if (field === 'prefixText') {
295
- const meta = this.#teaserMetadata()
296
+ const meta = await this.#teaserMetadata()
296
297
  return meta[field]
297
298
  } else {
298
299
  // HACK 20230629 IM: suffixText doesn't actually exist in the metadata
@@ -474,15 +475,13 @@ export class CapiResponse {
474
475
  return []
475
476
  }
476
477
 
477
- containedIn() {
478
- if (!('containedIn' in this.capiData)) {
479
- return null
480
- }
481
- const containerId = this.capiData.containedIn?.[0]?.id
482
- if (!containerId) {
483
- return null
484
- }
485
- return this.context.dataSources.capi.getContent(uuidFromUrl(containerId))
478
+ async containedIn() {
479
+ const containerId =
480
+ 'containedIn' in this.capiData && this.capiData.containedIn?.[0]?.id
481
+
482
+ return containerId
483
+ ? await this.context.dataSources.capi.getContent(uuidFromUrl(containerId))
484
+ : null
486
485
  }
487
486
 
488
487
  isContainedInPackage(): boolean {
@@ -4,15 +4,23 @@ import {
4
4
  baseMediaSchema,
5
5
  } from './base-schema'
6
6
 
7
- const contentPackageContentSchema = baseContentSchema.pick({
8
- title: true,
9
- summary: true,
10
- alternativeTitles: true,
11
- contains: true,
12
- tableOfContents: true,
13
- design: true,
14
- bodyXML: true,
15
- })
7
+ const contentPackageContentSchema = baseContentSchema
8
+ .pick({
9
+ title: true,
10
+ summary: true,
11
+ alternativeTitles: true,
12
+ contains: true,
13
+ tableOfContents: true,
14
+ design: true,
15
+ bodyXML: true,
16
+ })
17
+ .merge(
18
+ baseContentSchema
19
+ .pick({
20
+ summary: true,
21
+ })
22
+ .partial()
23
+ )
16
24
 
17
25
  const contentPackageMetadataSchema = baseMetadataSchema.pick({
18
26
  id: true,
@@ -1,12 +1,10 @@
1
- import { IResolvers } from '@graphql-tools/utils'
2
- import type { QueryContext } from '..'
3
- import { Concept, URLArguments } from '../model/Concept'
4
1
  import { ConceptResolvers } from '../generated'
5
2
 
6
3
  const resolvers: { Concept: ConceptResolvers } = {
7
4
  Concept: {
8
5
  apiUrl: (parent) => parent.apiUrl(),
9
6
  directType: (parent) => parent.directType(),
7
+ isPackageBrand: (parent) => parent.isPackageBrand(),
10
8
  id: (parent) => parent.uuid(),
11
9
  predicate: (parent) => parent.predicate(),
12
10
  prefLabel: (parent) => parent.prefLabel(),
@@ -26,12 +26,20 @@ const resolvers = {
26
26
  body(parent) {
27
27
  return new RichText('bodyXML', parent.bodyXML(), parent)
28
28
  },
29
- topper: (parent, args, context) => new Topper(parent, context),
30
- byline(parent) {
29
+ topper: (parent, _, context) => new Topper(parent, context),
30
+ byline(parent, args, context) {
31
+ const vanity = Boolean(args.vanity)
31
32
  const bylineText = parent.byline()
33
+
32
34
  if (!bylineText) return null
33
35
  const authorUrlMapping = parent.getAuthorUrlMapping()
34
- return new Byline(bylineText, authorUrlMapping).buildBylineTree()
36
+
37
+ return new Byline(
38
+ bylineText,
39
+ vanity,
40
+ authorUrlMapping,
41
+ context
42
+ ).buildBylineTree()
35
43
  },
36
44
  url: (parent, args) =>
37
45
  args.relative
@@ -37,3 +37,5 @@ type Image = z.infer<typeof Image>
37
37
  type LeadImage = z.infer<typeof LeadImage>
38
38
  type ImageSet = z.infer<typeof ImageSet>
39
39
  type MainImage = z.infer<typeof MainImage>
40
+
41
+ type ContentTypeSchemas = Article | LiveBlogPackage | ContentPackage | Audio
@@ -1,5 +1,5 @@
1
1
  declare module '@financial-times/n-display-metadata' {
2
- import { Annotation } from './internal-content'
2
+ import { Annotation, ContentTypeSchemas } from './internal-content'
3
3
 
4
4
  export interface FlagsMetadata {
5
5
  isOpinion: boolean
@@ -15,6 +15,7 @@ declare module '@financial-times/n-display-metadata' {
15
15
 
16
16
  export interface Content {
17
17
  annotations: Annotation[]
18
+ containedIn: ContentTypeSchemas | null
18
19
  }
19
20
 
20
21
  export function teaser(content: Content): TeaserMetadata