@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.
- package/CHANGELOG.md +19 -0
- package/lib/fixtures/dummyContext.d.ts +3 -0
- package/lib/fixtures/dummyContext.js +24 -0
- package/lib/fixtures/dummyContext.js.map +1 -0
- package/lib/generated/index.d.ts +36 -13
- package/lib/model/Byline.d.ts +6 -3
- package/lib/model/Byline.js +14 -9
- package/lib/model/Byline.js.map +1 -1
- package/lib/model/Byline.test.js +19 -14
- package/lib/model/Byline.test.js.map +1 -1
- package/lib/model/CapiResponse.d.ts +5 -6
- package/lib/model/CapiResponse.js +15 -15
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/CapiResponse.test.js +8 -19
- package/lib/model/CapiResponse.test.js.map +1 -1
- package/lib/model/schemas/capi/content-package.d.ts +8 -8
- package/lib/model/schemas/capi/content-package.js +8 -2
- package/lib/model/schemas/capi/content-package.js.map +1 -1
- package/lib/resolvers/concept.js +1 -0
- package/lib/resolvers/concept.js.map +1 -1
- package/lib/resolvers/content.d.ts +4 -4
- package/lib/resolvers/content.js +4 -3
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/index.d.ts +8 -8
- package/lib/resolvers/teaser.d.ts +4 -4
- package/package.json +1 -1
- package/src/fixtures/dummyContext.ts +28 -0
- package/src/generated/index.ts +50 -13
- package/src/model/Byline.test.ts +16 -14
- package/src/model/Byline.ts +27 -19
- package/src/model/CapiResponse.test.ts +1 -25
- package/src/model/CapiResponse.ts +20 -21
- package/src/model/schemas/capi/content-package.ts +17 -9
- package/src/resolvers/concept.ts +1 -3
- package/src/resolvers/content.ts +11 -3
- package/src/types/internal-content.d.ts +2 -0
- package/src/types/n-display-metadata.d.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/concept.graphql +4 -3
- package/typedefs/content.graphql +7 -7
package/src/model/Byline.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { ContentTree } from '@financial-times/content-tree'
|
|
2
2
|
import { AuthorLink } from '../resolvers/content-tree/Workarounds'
|
|
3
|
-
import {
|
|
3
|
+
import { QueryContext } from '..'
|
|
4
4
|
|
|
5
5
|
export class Byline {
|
|
6
6
|
constructor(
|
|
7
7
|
private byline: string,
|
|
8
|
-
private
|
|
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)[] =
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
}
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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,
|
package/src/resolvers/concept.ts
CHANGED
|
@@ -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(),
|
package/src/resolvers/content.ts
CHANGED
|
@@ -26,12 +26,20 @@ const resolvers = {
|
|
|
26
26
|
body(parent) {
|
|
27
27
|
return new RichText('bodyXML', parent.bodyXML(), parent)
|
|
28
28
|
},
|
|
29
|
-
topper: (parent,
|
|
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
|
-
|
|
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
|
|
@@ -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
|