@financial-times/cp-content-pipeline-schema 2.8.0 → 2.9.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 +17 -0
- package/lib/datasources/capi.d.ts +1 -1
- package/lib/datasources/capi.js +14 -39
- package/lib/datasources/capi.js.map +1 -1
- package/lib/datasources/instrumented.d.ts +4 -1
- package/lib/datasources/instrumented.js +16 -16
- package/lib/datasources/instrumented.js.map +1 -1
- package/lib/datasources/origami-image.d.ts +1 -1
- package/lib/datasources/origami-image.js +7 -21
- package/lib/datasources/origami-image.js.map +1 -1
- package/lib/datasources/twitter.d.ts +1 -1
- package/lib/datasources/twitter.js +7 -21
- package/lib/datasources/twitter.js.map +1 -1
- package/lib/model/CapiResponse.js +33 -4
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/Concept.js +1 -1
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/Image.js +8 -3
- package/lib/model/Image.js.map +1 -1
- package/lib/model/Person.js +7 -2
- package/lib/model/Person.js.map +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.js +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.js.map +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js +7 -7
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js.map +1 -1
- package/lib/resolvers/content-tree/references/Flourish.js +7 -2
- package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
- package/lib/resolvers/content-tree/references/RawImage.js +7 -2
- package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
- package/lib/resolvers/content-tree/references/Recommended.js +1 -1
- package/lib/resolvers/content-tree/references/Recommended.js.map +1 -1
- package/lib/resolvers/content-tree/references/Tweet.js +7 -2
- package/lib/resolvers/content-tree/references/Tweet.js.map +1 -1
- package/lib/resolvers/content-tree/references/Video.js +15 -2
- package/lib/resolvers/content-tree/references/Video.js.map +1 -1
- package/lib/resolvers/core.js +16 -1
- package/lib/resolvers/core.js.map +1 -1
- package/package.json +5 -2
- package/src/datasources/capi.ts +16 -44
- package/src/datasources/instrumented.ts +29 -31
- package/src/datasources/origami-image.ts +11 -25
- package/src/datasources/twitter.ts +10 -24
- package/src/model/CapiResponse.ts +44 -6
- package/src/model/Concept.ts +1 -1
- package/src/model/Image.ts +9 -4
- package/src/model/Person.ts +7 -2
- package/src/resolvers/content-tree/bodyXMLToTree.test.ts +7 -7
- package/src/resolvers/content-tree/bodyXMLToTree.ts +1 -1
- package/src/resolvers/content-tree/references/Flourish.ts +7 -2
- package/src/resolvers/content-tree/references/RawImage.ts +7 -2
- package/src/resolvers/content-tree/references/Recommended.ts +1 -1
- package/src/resolvers/content-tree/references/Tweet.ts +7 -2
- package/src/resolvers/content-tree/references/Video.ts +18 -4
- package/src/resolvers/core.ts +18 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -188,7 +188,7 @@ export class CapiResponse {
|
|
|
188
188
|
* Don't let this failure block this system from continuing to try and handle the request
|
|
189
189
|
*/
|
|
190
190
|
if (!schemaResponse.success) {
|
|
191
|
-
context.logger.
|
|
191
|
+
context.logger.warn({
|
|
192
192
|
event: 'RECOVERABLE_ERROR',
|
|
193
193
|
error: new OperationalError({
|
|
194
194
|
message:
|
|
@@ -286,7 +286,7 @@ export class CapiResponse {
|
|
|
286
286
|
return vanityUrl ?? url
|
|
287
287
|
} catch (error) {
|
|
288
288
|
if (isError(error)) {
|
|
289
|
-
this.context.logger.
|
|
289
|
+
this.context.logger.warn({
|
|
290
290
|
event: 'RECOVERABLE_ERROR',
|
|
291
291
|
error,
|
|
292
292
|
})
|
|
@@ -405,9 +405,24 @@ export class CapiResponse {
|
|
|
405
405
|
'containedIn' in this.capiData && this.capiData.containedIn?.[0]?.id
|
|
406
406
|
|
|
407
407
|
if (containerId) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
try {
|
|
409
|
+
container = await this.context.dataSources.capi.getContent(
|
|
410
|
+
uuidFromUrl(containerId)
|
|
411
|
+
)
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (error instanceof Error) {
|
|
414
|
+
this.context.logger.warn({
|
|
415
|
+
event: 'RECOVERABLE_ERROR',
|
|
416
|
+
error: new OperationalError({
|
|
417
|
+
message: `Failed to fetch package container ${containerId} for content ${this.id()}`,
|
|
418
|
+
code: `PACKAGE_CONTAINEDIN_ERROR`,
|
|
419
|
+
cause: error,
|
|
420
|
+
}),
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
411
426
|
this.packageContainer = container
|
|
412
427
|
}
|
|
413
428
|
}
|
|
@@ -652,11 +667,34 @@ export class CapiResponse {
|
|
|
652
667
|
|
|
653
668
|
this.context.addSurrogateKeys(contains.map((article) => article.id))
|
|
654
669
|
|
|
655
|
-
|
|
670
|
+
const results = await Promise.allSettled(
|
|
656
671
|
contains.map(({ id }) =>
|
|
657
672
|
this.context.dataSources.capi.getContent(uuidFromUrl(id), this)
|
|
658
673
|
)
|
|
659
674
|
)
|
|
675
|
+
|
|
676
|
+
const failed = results.filter(
|
|
677
|
+
(result): result is PromiseRejectedResult =>
|
|
678
|
+
result.status === 'rejected'
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
if (failed.length) {
|
|
682
|
+
this.context.logger.warn({
|
|
683
|
+
event: 'RECOVERABLE_ERROR',
|
|
684
|
+
error: new OperationalError({
|
|
685
|
+
code: 'PACKAGE_CONTAINS_ERROR',
|
|
686
|
+
message: `Failed to fetch some of the content contained in the package ${this.id()}`,
|
|
687
|
+
cause: new AggregateError(failed.map((result) => result.reason)),
|
|
688
|
+
}),
|
|
689
|
+
})
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return results
|
|
693
|
+
.filter(
|
|
694
|
+
(result): result is PromiseFulfilledResult<CapiResponse> =>
|
|
695
|
+
result.status === 'fulfilled'
|
|
696
|
+
)
|
|
697
|
+
.map((result) => result.value)
|
|
660
698
|
}
|
|
661
699
|
|
|
662
700
|
return null
|
package/src/model/Concept.ts
CHANGED
package/src/model/Image.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '../resolvers/literal-union'
|
|
13
13
|
import { ImageFormat, ImageType } from '../resolvers/scalars'
|
|
14
14
|
import isError from '../helpers/isError'
|
|
15
|
-
import { BaseError } from '@dotcom-reliability-kit/errors'
|
|
15
|
+
import { BaseError, OperationalError } from '@dotcom-reliability-kit/errors'
|
|
16
16
|
import type { ImageSource } from '../generated'
|
|
17
17
|
|
|
18
18
|
export type ImageSourceArgs = {
|
|
@@ -54,7 +54,7 @@ export class CAPIImage implements Image {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// we shouldn't be here. but just in case,
|
|
57
|
-
this.context.logger.
|
|
57
|
+
this.context.logger.warn({
|
|
58
58
|
event: 'RECOVERABLE_ERROR',
|
|
59
59
|
error: new BaseError({
|
|
60
60
|
code: 'INVALID_IMAGE_TYPE',
|
|
@@ -164,9 +164,14 @@ export class CAPIImage implements Image {
|
|
|
164
164
|
return imageMetadata
|
|
165
165
|
} catch (error) {
|
|
166
166
|
if (isError(error)) {
|
|
167
|
-
this.context.logger.
|
|
167
|
+
this.context.logger.warn({
|
|
168
168
|
event: 'RECOVERABLE_ERROR',
|
|
169
|
-
error
|
|
169
|
+
error: new OperationalError({
|
|
170
|
+
code: 'IMAGE_DIMENSIONS_ERROR',
|
|
171
|
+
message: `Failed to get dimensions for ${this.capiImage.binaryUrl}`,
|
|
172
|
+
url: this.capiImage.binaryUrl,
|
|
173
|
+
cause: error,
|
|
174
|
+
}),
|
|
170
175
|
})
|
|
171
176
|
}
|
|
172
177
|
return null
|
package/src/model/Person.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { CapiResponse } from './CapiResponse'
|
|
|
4
4
|
import imageServiceUrl from '../helpers/imageService'
|
|
5
5
|
import isError from '../helpers/isError'
|
|
6
6
|
import decorateHeadshotUrl, { UUID_REGEX } from '../helpers/decorateHeadshotUrl'
|
|
7
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
7
8
|
|
|
8
9
|
type HeadshotArguments = {
|
|
9
10
|
width?: number | null
|
|
@@ -63,9 +64,13 @@ export class Person {
|
|
|
63
64
|
: null
|
|
64
65
|
} catch (error) {
|
|
65
66
|
if (isError(error)) {
|
|
66
|
-
this.context.logger.
|
|
67
|
+
this.context.logger.warn({
|
|
67
68
|
event: 'RECOVERABLE_ERROR',
|
|
68
|
-
error
|
|
69
|
+
error: new OperationalError({
|
|
70
|
+
message: `Error fetching CAPI Person for headshot URL for ${this.prefLabel()}`,
|
|
71
|
+
code: 'HEADSHOT_PERSON_FETCH_ERROR',
|
|
72
|
+
cause: error,
|
|
73
|
+
}),
|
|
69
74
|
})
|
|
70
75
|
}
|
|
71
76
|
return null
|
|
@@ -6,7 +6,7 @@ import { QueryContext, BodyXMLToTreeError } from '../..'
|
|
|
6
6
|
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
7
7
|
|
|
8
8
|
const mockLogger = new Logger()
|
|
9
|
-
const
|
|
9
|
+
const mockLogWarn = jest.spyOn(mockLogger, 'warn')
|
|
10
10
|
|
|
11
11
|
const mockContext = {
|
|
12
12
|
logger: mockLogger,
|
|
@@ -233,7 +233,7 @@ describe('bodyXMLToTree', () => {
|
|
|
233
233
|
"version": 1,
|
|
234
234
|
}
|
|
235
235
|
`)
|
|
236
|
-
expect(
|
|
236
|
+
expect(mockLogWarn).not.toBeCalled()
|
|
237
237
|
})
|
|
238
238
|
|
|
239
239
|
it('should handle heading and slots', () => {
|
|
@@ -268,7 +268,7 @@ describe('bodyXMLToTree', () => {
|
|
|
268
268
|
"version": 1,
|
|
269
269
|
}
|
|
270
270
|
`)
|
|
271
|
-
expect(
|
|
271
|
+
expect(mockLogWarn).not.toBeCalled()
|
|
272
272
|
})
|
|
273
273
|
|
|
274
274
|
it('should log an error on unexpected child after heading', () => {
|
|
@@ -303,8 +303,8 @@ describe('bodyXMLToTree', () => {
|
|
|
303
303
|
"version": 1,
|
|
304
304
|
}
|
|
305
305
|
`)
|
|
306
|
-
expect(
|
|
307
|
-
expect(
|
|
306
|
+
expect(mockLogWarn).toBeCalled()
|
|
307
|
+
expect(mockLogWarn.mock.lastCall).toMatchInlineSnapshot(`
|
|
308
308
|
Array [
|
|
309
309
|
Object {
|
|
310
310
|
"error": Object {
|
|
@@ -349,8 +349,8 @@ describe('bodyXMLToTree', () => {
|
|
|
349
349
|
"version": 1,
|
|
350
350
|
}
|
|
351
351
|
`)
|
|
352
|
-
expect(
|
|
353
|
-
expect(
|
|
352
|
+
expect(mockLogWarn).toBeCalled()
|
|
353
|
+
expect(mockLogWarn.mock.lastCall).toMatchInlineSnapshot(`
|
|
354
354
|
Array [
|
|
355
355
|
Object {
|
|
356
356
|
"error": Object {
|
|
@@ -80,7 +80,7 @@ export default function bodyXMLToTree(
|
|
|
80
80
|
function logErrors(context?: QueryContext) {
|
|
81
81
|
if (!context?.aggregatedErrors?.bodyXMLToTree.length) return
|
|
82
82
|
|
|
83
|
-
context.logger.
|
|
83
|
+
context.logger.warn({
|
|
84
84
|
event: 'RECOVERABLE_ERROR',
|
|
85
85
|
message: 'Unexpected structure in bodyXMLToTree',
|
|
86
86
|
error: new OperationalError({
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '../../../generated'
|
|
6
6
|
import { QueryContext } from '../../..'
|
|
7
7
|
import isError from '../../../helpers/isError'
|
|
8
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
8
9
|
|
|
9
10
|
export const Flourish = {
|
|
10
11
|
async fallbackImage(parent, _args, context) {
|
|
@@ -79,9 +80,13 @@ const getImageMetadata = async (
|
|
|
79
80
|
}
|
|
80
81
|
} catch (error) {
|
|
81
82
|
if (isError(error)) {
|
|
82
|
-
context.logger.
|
|
83
|
+
context.logger.warn({
|
|
83
84
|
event: 'RECOVERABLE_ERROR',
|
|
84
|
-
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
|
+
}),
|
|
85
90
|
})
|
|
86
91
|
}
|
|
87
92
|
return {
|
|
@@ -4,6 +4,7 @@ import imageServiceUrl from '../../../helpers/imageService'
|
|
|
4
4
|
import { RawImage as RawImageNode } from '../Workarounds'
|
|
5
5
|
import { RawImageResolvers } from '../../../generated'
|
|
6
6
|
import isError from '../../../helpers/isError'
|
|
7
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
7
8
|
|
|
8
9
|
class RawImageModel implements Image {
|
|
9
10
|
constructor(private rawImage: RawImageNode, private context: QueryContext) {}
|
|
@@ -50,9 +51,13 @@ class RawImageModel implements Image {
|
|
|
50
51
|
return imageMetadata
|
|
51
52
|
} catch (error) {
|
|
52
53
|
if (isError(error)) {
|
|
53
|
-
this.context.logger.
|
|
54
|
+
this.context.logger.warn({
|
|
54
55
|
event: 'RECOVERABLE_ERROR',
|
|
55
|
-
error
|
|
56
|
+
error: new OperationalError({
|
|
57
|
+
code: 'RAW_IMAGE_DIMENSIONS_ERROR',
|
|
58
|
+
message: `Error getting dimensions for raw image ${this.url()}`,
|
|
59
|
+
cause: error,
|
|
60
|
+
}),
|
|
56
61
|
})
|
|
57
62
|
}
|
|
58
63
|
return null
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
1
2
|
import { TweetResolvers } from '../../../generated'
|
|
2
3
|
import isError from '../../../helpers/isError'
|
|
3
4
|
|
|
@@ -10,9 +11,13 @@ export const Tweet = {
|
|
|
10
11
|
return tweet.html
|
|
11
12
|
} catch (error) {
|
|
12
13
|
if (isError(error)) {
|
|
13
|
-
context.logger.
|
|
14
|
+
context.logger.warn({
|
|
14
15
|
event: 'RECOVERABLE_ERROR',
|
|
15
|
-
error
|
|
16
|
+
error: new OperationalError({
|
|
17
|
+
code: 'TWEET_EMBED_ERROR',
|
|
18
|
+
message: `Failed to get HTML embed for tweet ${parent.reference.id}`,
|
|
19
|
+
cause: error,
|
|
20
|
+
}),
|
|
16
21
|
})
|
|
17
22
|
}
|
|
18
23
|
return null
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { uuidFromUrl } from '../../../helpers/metadata'
|
|
2
2
|
import { VideoReferenceResolvers } from '../../../generated'
|
|
3
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
3
4
|
|
|
4
5
|
export const Video = {
|
|
5
6
|
// This will override the original id from the parent that is a URL.
|
|
6
7
|
id: (parent) => uuidFromUrl(parent.reference.id),
|
|
7
8
|
|
|
8
9
|
async title(parent, _args, context) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
try {
|
|
11
|
+
const capiResponse = await context.dataSources.capi.getContent(
|
|
12
|
+
uuidFromUrl(parent.reference.id)
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
return capiResponse.title()
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
throw new OperationalError({
|
|
19
|
+
code: 'VIDEO_CONTENT_FETCH_ERROR',
|
|
20
|
+
message: `Error getting content object for video ${parent.reference.id}`,
|
|
21
|
+
cause: error,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
13
27
|
},
|
|
14
28
|
|
|
15
29
|
type(parent) {
|
package/src/resolvers/core.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs'
|
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { QueryResolvers, MutationResolvers } from '../generated'
|
|
4
4
|
import { CapiResponse } from '../model/CapiResponse'
|
|
5
|
+
import { BaseError, HttpError } from '@dotcom-reliability-kit/errors'
|
|
5
6
|
|
|
6
7
|
const packageJson = JSON.parse(
|
|
7
8
|
fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8')
|
|
@@ -13,7 +14,23 @@ const resolvers = {
|
|
|
13
14
|
Query: {
|
|
14
15
|
version: () => version,
|
|
15
16
|
async content(_, args, context) {
|
|
16
|
-
|
|
17
|
+
try {
|
|
18
|
+
return await context.dataSources.capi.getContent(args.uuid)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (
|
|
21
|
+
error instanceof BaseError &&
|
|
22
|
+
error.data.upstreamStatusCode === 404
|
|
23
|
+
) {
|
|
24
|
+
throw new HttpError({
|
|
25
|
+
code: 'CONTENT_NOT_FOUND',
|
|
26
|
+
message: `Content ${args.uuid} not found in Content API`,
|
|
27
|
+
statusCode: 404,
|
|
28
|
+
relatesToSystems: ['up-ica'],
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw error
|
|
33
|
+
}
|
|
17
34
|
},
|
|
18
35
|
contentFromJSON(_, { content }, context) {
|
|
19
36
|
return CapiResponse.fromJSON(content, context)
|