@financial-times/cp-content-pipeline-schema 2.7.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 +24 -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/generated/index.d.ts +24 -0
- package/lib/model/CapiResponse.d.ts +2 -0
- package/lib/model/CapiResponse.js +40 -4
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/Concept.d.ts +0 -2
- package/lib/model/Concept.js +1 -57
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/Concept.test.js +0 -40
- package/lib/model/Concept.test.js.map +1 -1
- package/lib/model/Image.js +8 -3
- package/lib/model/Image.js.map +1 -1
- package/lib/model/Person.d.ts +21 -0
- package/lib/model/Person.js +106 -0
- package/lib/model/Person.js.map +1 -0
- package/lib/model/Person.test.d.ts +1 -0
- package/lib/model/Person.test.js +96 -0
- package/lib/model/Person.test.js.map +1 -0
- package/lib/model/Topper.d.ts +2 -1
- package/lib/model/Topper.js +18 -16
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/Topper.test.js +29 -0
- package/lib/model/Topper.test.js.map +1 -1
- package/lib/model/schemas/capi/base-schema.d.ts +3 -0
- package/lib/model/schemas/capi/base-schema.js +1 -0
- package/lib/model/schemas/capi/base-schema.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/content.d.ts +1 -0
- package/lib/resolvers/content.js +1 -0
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/core.js +16 -1
- package/lib/resolvers/core.js.map +1 -1
- package/lib/resolvers/index.d.ts +9 -3
- package/lib/resolvers/index.js +2 -0
- package/lib/resolvers/index.js.map +1 -1
- package/lib/resolvers/person.d.ts +8 -0
- package/lib/resolvers/person.js +11 -0
- package/lib/resolvers/person.js.map +1 -0
- package/lib/resolvers/topper.d.ts +3 -3
- package/package.json +5 -2
- package/queries/article.graphql +9 -0
- 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/generated/index.ts +28 -0
- package/src/model/CapiResponse.ts +51 -6
- package/src/model/Concept.test.ts +0 -49
- package/src/model/Concept.ts +1 -32
- package/src/model/Image.ts +9 -4
- package/src/model/Person.test.ts +110 -0
- package/src/model/Person.ts +79 -0
- package/src/model/Topper.test.ts +37 -0
- package/src/model/Topper.ts +18 -23
- package/src/model/schemas/capi/base-schema.ts +1 -0
- 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/content.ts +1 -0
- package/src/resolvers/core.ts +18 -1
- package/src/resolvers/index.ts +2 -0
- package/src/resolvers/person.ts +12 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +1 -0
- package/typedefs/person.graphql +5 -0
- package/typedefs/topper.graphql +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.9.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v2.8.0...cp-content-pipeline-schema-v2.9.0) (2024-05-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* construct OperationalError at the call site for datasources ([f07cb89](https://github.com/Financial-Times/cp-content-pipeline/commit/f07cb8933f63ebfbe96d9ccadb370c275bce8b43))
|
|
9
|
+
* use fetch-error-handler and centralize datasource error handling ([4c5d0d6](https://github.com/Financial-Times/cp-content-pipeline/commit/4c5d0d66dd78cb6a5db316759a2c8788b1d30576))
|
|
10
|
+
* use native fetch ([58e682b](https://github.com/Financial-Times/cp-content-pipeline/commit/58e682b149d65e6ff55a0f828f8e8f7e6f06c200))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* only wrap getPerson errors in OperationalError at call site ([05b1c51](https://github.com/Financial-Times/cp-content-pipeline/commit/05b1c51ddf6d63f73cacde895faa256cff8a676f))
|
|
16
|
+
* return null for containedIn on error instead of throwing ([a7e96f3](https://github.com/Financial-Times/cp-content-pipeline/commit/a7e96f3e180e994c37482e93e1e396a612f7db0f))
|
|
17
|
+
* use logger.warn for RECOVERABLE_ERROR ([d7f44a5](https://github.com/Financial-Times/cp-content-pipeline/commit/d7f44a526e300afad82f4296014b61c1e19a0a9c))
|
|
18
|
+
* use return await to let it catch promise rejections ([bc08819](https://github.com/Financial-Times/cp-content-pipeline/commit/bc088199263d92023ba03b71fb6497aa10eebe3f))
|
|
19
|
+
|
|
20
|
+
## [2.8.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v2.7.0...cp-content-pipeline-schema-v2.8.0) (2024-04-29)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* clickable bylines and headshots in opinion posts ([9295ce3](https://github.com/Financial-Times/cp-content-pipeline/commit/9295ce31e6e58d59882c1b74232ed5c7338f001d))
|
|
26
|
+
|
|
3
27
|
## [2.7.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v2.6.2...cp-content-pipeline-schema-v2.7.0) (2024-04-26)
|
|
4
28
|
|
|
5
29
|
|
|
@@ -7,7 +7,7 @@ export declare class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
7
7
|
capiKey: string;
|
|
8
8
|
articleCacheTTL: number;
|
|
9
9
|
peopleCacheTTL: number;
|
|
10
|
-
|
|
10
|
+
backendSystemCode: string;
|
|
11
11
|
abortController: AbortController;
|
|
12
12
|
timeout: ReturnType<typeof setTimeout> | undefined;
|
|
13
13
|
nextNotificationLink?: string;
|
package/lib/datasources/capi.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CapiDataSource = void 0;
|
|
4
4
|
const CapiResponse_1 = require("../model/CapiResponse");
|
|
5
5
|
const instrumented_1 = require("./instrumented");
|
|
6
|
-
const errors_1 = require("@dotcom-reliability-kit/errors");
|
|
7
6
|
const REQUEST_TIMEOUT = 5000; // 5 seconds
|
|
8
7
|
class CapiDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
9
8
|
constructor() {
|
|
@@ -16,7 +15,7 @@ class CapiDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
|
16
15
|
this.peopleCacheTTL = process.env.PEOPLE_CACHE_TTL
|
|
17
16
|
? parseInt(process.env.PEOPLE_CACHE_TTL)
|
|
18
17
|
: 600; // 10 minutes
|
|
19
|
-
this.
|
|
18
|
+
this.backendSystemCode = 'up-ica';
|
|
20
19
|
this.abortController = new AbortController();
|
|
21
20
|
this.timeout = undefined;
|
|
22
21
|
}
|
|
@@ -27,45 +26,21 @@ class CapiDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
|
27
26
|
this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
|
|
28
27
|
}
|
|
29
28
|
async getContent(uuid, packageContainer) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.timeout = undefined;
|
|
39
|
-
}
|
|
40
|
-
return CapiResponse_1.CapiResponse.fromJSON(content, this.context, packageContainer);
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
44
|
-
throw new errors_1.UpstreamServiceError({
|
|
45
|
-
code: 'CAPI_DATASOURCE_TIMEOUT',
|
|
46
|
-
message: `Request to Internal Content API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
|
|
47
|
-
statusCode: 408,
|
|
48
|
-
relatesToSystems: this.backendSystemCodes,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
throw error;
|
|
29
|
+
const content = await this.get(`internalcontent/${uuid}`, {
|
|
30
|
+
cacheOptions: { ttl: this.articleCacheTTL },
|
|
31
|
+
});
|
|
32
|
+
this.context.contentRequestedOnce = true;
|
|
33
|
+
this.calls.push(uuid);
|
|
34
|
+
if (this.timeout) {
|
|
35
|
+
clearTimeout(this.timeout);
|
|
36
|
+
this.timeout = undefined;
|
|
52
37
|
}
|
|
38
|
+
return CapiResponse_1.CapiResponse.fromJSON(content, this.context, packageContainer);
|
|
53
39
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
if (error instanceof Error) {
|
|
62
|
-
throw new errors_1.OperationalError({
|
|
63
|
-
cause: error,
|
|
64
|
-
code: 'CAPI_DATASOURCE_PERSON_ERROR',
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
40
|
+
getPerson(uuid) {
|
|
41
|
+
return this.get(`people/${uuid}`, {
|
|
42
|
+
cacheOptions: { ttl: this.peopleCacheTTL },
|
|
43
|
+
});
|
|
69
44
|
}
|
|
70
45
|
}
|
|
71
46
|
exports.CapiDataSource = CapiDataSource;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":";;;AAAA,wDAAoD;AACpD,iDAA2D;
|
|
1
|
+
{"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":";;;AAAA,wDAAoD;AACpD,iDAA2D;AAI3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AAEzC,MAAa,cAAe,SAAQ,yCAA0B;IAA9D;;QACE,YAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAA;QACzD,YAAO,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAA;QACxC,oBAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;YACzC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA,CAAC,UAAU;QACnC,mBAAc,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAC3C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACxC,CAAC,CAAC,GAAG,CAAA,CAAC,aAAa;QAErB,sBAAiB,GAAG,QAAQ,CAAA;QAE5B,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IAsChE,CAAC;IAlCU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,OAAO,CAAA;QAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CACd,IAAY,EACZ,gBAA+B;QAE/B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,EAAE;YACxD,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE;SAC5C,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAA;QACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAErB,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;SACzB;QAED,OAAO,2BAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;IACvE,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE;YAChC,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE;SAC3C,CAAC,CAAA;IACJ,CAAC;CACF;AAnDD,wCAmDC"}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { RESTDataSource, CacheOptions, DataSourceFetchResult, DataSourceRequest, AugmentedRequest } from '@apollo/datasource-rest';
|
|
2
|
+
import type { FetchErrorHandler } from '@dotcom-reliability-kit/fetch-error-handler/lib/create-handler';
|
|
2
3
|
import { QueryContext } from '..';
|
|
3
4
|
import { BaseDataSource, BaseDataSourceOptions } from './base';
|
|
4
5
|
export declare class InstrumentedRESTDataSource extends RESTDataSource implements BaseDataSource {
|
|
5
6
|
startTime?: bigint;
|
|
6
|
-
|
|
7
|
+
backendSystemCode: string | undefined;
|
|
7
8
|
context: QueryContext;
|
|
8
9
|
calls: string[];
|
|
10
|
+
errorHandler: FetchErrorHandler;
|
|
9
11
|
constructor({ cache, context }: BaseDataSourceOptions);
|
|
10
12
|
get cachePrefix(): string;
|
|
11
13
|
protected cacheKeyFor(url: URL): string;
|
|
12
14
|
willSendRequest(path: string, request: AugmentedRequest): void;
|
|
13
15
|
throwIfResponseIsError(): Promise<void>;
|
|
16
|
+
logResponseMetrics(status: number, duration: bigint): void;
|
|
14
17
|
fetch<TResult>(path: string, incomingRequest?: DataSourceRequest<CacheOptions>): Promise<DataSourceFetchResult<TResult>>;
|
|
15
18
|
}
|
|
@@ -3,17 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.InstrumentedRESTDataSource = void 0;
|
|
4
4
|
const datasource_rest_1 = require("@apollo/datasource-rest");
|
|
5
5
|
const utils_keyvaluecache_1 = require("@apollo/utils.keyvaluecache");
|
|
6
|
-
const
|
|
6
|
+
const fetch_error_handler_1 = require("@dotcom-reliability-kit/fetch-error-handler");
|
|
7
7
|
const cache_1 = require("../types/cache");
|
|
8
|
+
const errors_1 = require("@dotcom-reliability-kit/errors");
|
|
8
9
|
class InstrumentedRESTDataSource extends datasource_rest_1.RESTDataSource {
|
|
9
10
|
constructor({ cache, context }) {
|
|
10
11
|
const wrappedCache = new utils_keyvaluecache_1.PrefixingKeyValueCache((0, cache_1.isContextableCache)(cache) ? cache.withContext(context) : cache, 'placeholder_prefix_if_you_see_this_in_redis_something_is_very_wrong:' // can't use `this` yet to get our prefix
|
|
11
12
|
);
|
|
12
13
|
super({
|
|
13
14
|
cache: wrappedCache,
|
|
15
|
+
fetch: (url, init) => this.errorHandler(global.fetch(url, init)),
|
|
14
16
|
});
|
|
15
|
-
this.backendSystemCodes = [];
|
|
16
17
|
this.calls = [];
|
|
18
|
+
this.errorHandler = (0, fetch_error_handler_1.createFetchErrorHandler)({
|
|
19
|
+
upstreamSystemCode: this.backendSystemCode,
|
|
20
|
+
});
|
|
17
21
|
// okay _now_ we can use `this`
|
|
18
22
|
// also that's a private property so use the [] escape hatch
|
|
19
23
|
// sorry
|
|
@@ -35,30 +39,26 @@ class InstrumentedRESTDataSource extends datasource_rest_1.RESTDataSource {
|
|
|
35
39
|
// we implement our own error handling so skip the built-in GraphQLError
|
|
36
40
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
37
41
|
async throwIfResponseIsError() { }
|
|
42
|
+
logResponseMetrics(status, duration) {
|
|
43
|
+
this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${status}.count`, 1);
|
|
44
|
+
this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${status}.time`, Number(duration));
|
|
45
|
+
}
|
|
38
46
|
async fetch(path, incomingRequest) {
|
|
39
47
|
const startTime = process.hrtime.bigint();
|
|
40
48
|
this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.request.count`, 1);
|
|
41
49
|
try {
|
|
42
50
|
const result = await super.fetch(path, incomingRequest);
|
|
43
51
|
const duration = (process.hrtime.bigint() - startTime) / BigInt(1e6);
|
|
44
|
-
this.
|
|
45
|
-
this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${result.response.status}.time`, Number(duration));
|
|
46
|
-
if (!result.response.ok) {
|
|
47
|
-
throw new errors_1.UpstreamServiceError({
|
|
48
|
-
message: `${result.response.status}: ${result.response.statusText} from ${this.constructor.name}`,
|
|
49
|
-
statusCode: result.response.status,
|
|
50
|
-
relatesToSystems: this.backendSystemCodes,
|
|
51
|
-
url: result.response.url,
|
|
52
|
-
body: result.parsedBody,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
52
|
+
this.logResponseMetrics(result.response.status, duration);
|
|
55
53
|
return result;
|
|
56
54
|
}
|
|
57
55
|
catch (error) {
|
|
58
|
-
if (error instanceof
|
|
56
|
+
if (error instanceof errors_1.BaseError) {
|
|
57
|
+
const status = error.code === 'FETCH_ABORT_ERROR'
|
|
58
|
+
? 408
|
|
59
|
+
: Number(error.data.upstreamStatusCode ?? 0);
|
|
59
60
|
const duration = (process.hrtime.bigint() - startTime) / BigInt(1e6);
|
|
60
|
-
this.
|
|
61
|
-
this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.408.time`, Number(duration));
|
|
61
|
+
this.logResponseMetrics(status, duration);
|
|
62
62
|
}
|
|
63
63
|
throw error;
|
|
64
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumented.js","sourceRoot":"","sources":["../../src/datasources/instrumented.ts"],"names":[],"mappings":";;;AAAA,6DAMgC;AAChC,qEAAoE;AACpE,
|
|
1
|
+
{"version":3,"file":"instrumented.js","sourceRoot":"","sources":["../../src/datasources/instrumented.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { InstrumentedRESTDataSource } from './instrumented';
|
|
|
2
2
|
import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest';
|
|
3
3
|
export declare class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
4
4
|
baseURL: string;
|
|
5
|
-
|
|
5
|
+
backendSystemCode: string;
|
|
6
6
|
abortController: AbortController;
|
|
7
7
|
timeout: ReturnType<typeof setTimeout> | undefined;
|
|
8
8
|
imageMetadataCacheTTL: number;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OrigamiImageDataSource = void 0;
|
|
4
|
-
const errors_1 = require("@dotcom-reliability-kit/errors");
|
|
5
4
|
const instrumented_1 = require("./instrumented");
|
|
6
5
|
const REQUEST_TIMEOUT = 5000; // 5 seconds
|
|
7
6
|
class OrigamiImageDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
8
7
|
constructor() {
|
|
9
8
|
super(...arguments);
|
|
10
9
|
this.baseURL = 'https://www.ft.com/__origami/service/image/v2/';
|
|
11
|
-
this.
|
|
10
|
+
this.backendSystemCode = 'origami-image-service-v2';
|
|
12
11
|
this.abortController = new AbortController();
|
|
13
12
|
this.timeout = undefined;
|
|
14
13
|
this.imageMetadataCacheTTL = process.env.IMAGE_METADATA_CACHE_TTL
|
|
@@ -24,26 +23,13 @@ class OrigamiImageDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
|
24
23
|
return { ttl: this.imageMetadataCacheTTL };
|
|
25
24
|
}
|
|
26
25
|
async getImageMetadata(url) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.timeout = undefined;
|
|
33
|
-
}
|
|
34
|
-
return imageMetadata;
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
38
|
-
throw new errors_1.UpstreamServiceError({
|
|
39
|
-
code: 'ORIGAMI_DATASOURCE_TIMEOUT',
|
|
40
|
-
message: `Request to Image Service took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
|
|
41
|
-
statusCode: 408,
|
|
42
|
-
relatesToSystems: this.backendSystemCodes,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
throw error;
|
|
26
|
+
const imageMetadata = await this.get(`images/metadata/${encodeURIComponent(url)}?source=next`);
|
|
27
|
+
this.calls.push(url);
|
|
28
|
+
if (this.timeout) {
|
|
29
|
+
clearTimeout(this.timeout);
|
|
30
|
+
this.timeout = undefined;
|
|
46
31
|
}
|
|
32
|
+
return imageMetadata;
|
|
47
33
|
}
|
|
48
34
|
}
|
|
49
35
|
exports.OrigamiImageDataSource = OrigamiImageDataSource;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":";;;AAAA,iDAA2D;AAG3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AAEzC,MAAa,sBAAuB,SAAQ,yCAA0B;IAAtE;;QACE,YAAO,GAAG,gDAAgD,CAAA;QAC1D,sBAAiB,GAAG,0BAA0B,CAAA;QAE9C,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;QAE9D,0BAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB;YAC1D,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;YAChD,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA,CAAC,SAAS;IA+BhC,CAAC;IA7BU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAES,eAAe;QACvB,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,GAAW;QAEX,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,GAAG,CAClC,mBAAmB,kBAAkB,CAAC,GAAG,CAAC,cAAc,CACzD,CAAA;QAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEpB,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;SACzB;QACD,OAAO,aAAa,CAAA;IACtB,CAAC;CACF;AAxCD,wDAwCC"}
|
|
@@ -2,7 +2,7 @@ import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest';
|
|
|
2
2
|
import { InstrumentedRESTDataSource } from './instrumented';
|
|
3
3
|
export declare class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
4
4
|
baseURL: string;
|
|
5
|
-
|
|
5
|
+
backendSystemCode: string;
|
|
6
6
|
abortController: AbortController;
|
|
7
7
|
timeout: ReturnType<typeof setTimeout> | undefined;
|
|
8
8
|
willSendRequest(path: string, request: AugmentedRequest): void;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TwitterDataSource = void 0;
|
|
4
|
-
const errors_1 = require("@dotcom-reliability-kit/errors");
|
|
5
4
|
const instrumented_1 = require("./instrumented");
|
|
6
5
|
const REQUEST_TIMEOUT = 5000; // 5 seconds
|
|
7
6
|
class TwitterDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
8
7
|
constructor() {
|
|
9
8
|
super(...arguments);
|
|
10
9
|
this.baseURL = 'https://publish.twitter.com';
|
|
11
|
-
this.
|
|
10
|
+
this.backendSystemCode = 'twitter-oembed-api';
|
|
12
11
|
this.abortController = new AbortController();
|
|
13
12
|
this.timeout = undefined;
|
|
14
13
|
}
|
|
@@ -18,26 +17,13 @@ class TwitterDataSource extends instrumented_1.InstrumentedRESTDataSource {
|
|
|
18
17
|
this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
|
|
19
18
|
}
|
|
20
19
|
async getTweet(tweetUrl) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.timeout = undefined;
|
|
27
|
-
}
|
|
28
|
-
return tweet;
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
32
|
-
throw new errors_1.UpstreamServiceError({
|
|
33
|
-
code: 'TWITTER_DATASOURCE_TIMEOUT',
|
|
34
|
-
message: `Request to Twitter API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
|
|
35
|
-
statusCode: 408,
|
|
36
|
-
relatesToSystems: this.backendSystemCodes,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
throw error;
|
|
20
|
+
const tweet = await this.get(`oembed?url=${tweetUrl}&omit_script=true`);
|
|
21
|
+
this.calls.push(tweetUrl);
|
|
22
|
+
if (this.timeout) {
|
|
23
|
+
clearTimeout(this.timeout);
|
|
24
|
+
this.timeout = undefined;
|
|
40
25
|
}
|
|
26
|
+
return tweet;
|
|
41
27
|
}
|
|
42
28
|
cacheOptionsFor() {
|
|
43
29
|
// HACK:20221110:KB twitter does return a cache_age property, but cacheOptionsFor
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../src/datasources/twitter.ts"],"names":[],"mappings":";;;AACA,
|
|
1
|
+
{"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../src/datasources/twitter.ts"],"names":[],"mappings":";;;AACA,iDAA2D;AAE3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AACzC,MAAa,iBAAkB,SAAQ,yCAA0B;IAAjE;;QACE,YAAO,GAAG,6BAA6B,CAAA;QACvC,sBAAiB,GAAG,oBAAoB,CAAA;QAExC,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IA8BhE,CAAC;IA5BU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAgB;QAC7B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,cAAc,QAAQ,mBAAmB,CAAC,CAAA;QAEvE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEzB,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;SACzB;QAED,OAAO,KAAK,CAAA;IACd,CAAC;IAED,eAAe;QACb,iFAAiF;QACjF,oFAAoF;QACpF,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACtB,CAAC;CACF;AAnCD,8CAmCC"}
|
package/lib/generated/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
|
|
2
2
|
import type { Concept as ConceptModel } from '../model/Concept';
|
|
3
|
+
import type { Person as PersonModel } from '../model/Person';
|
|
3
4
|
import type { CapiResponse } from '../model/CapiResponse';
|
|
4
5
|
import type { Image as ImageModel } from '../model/Image';
|
|
5
6
|
import type { Clip as ClipModel } from '../model/Clip';
|
|
@@ -972,6 +973,7 @@ export type LiveBlogPost = Content & {
|
|
|
972
973
|
readonly altTitle?: Maybe<AltTitle>;
|
|
973
974
|
/** An array of concepts related to the article, eg. organisations or topics. */
|
|
974
975
|
readonly annotations?: Maybe<ReadonlyArray<Maybe<Concept>>>;
|
|
976
|
+
readonly authors?: Maybe<ReadonlyArray<Maybe<Person>>>;
|
|
975
977
|
/** An abstract syntax tree of the article content. */
|
|
976
978
|
readonly body?: Maybe<RichText>;
|
|
977
979
|
/** The raw string of the XML as returned from ContentAPI. */
|
|
@@ -1077,6 +1079,16 @@ export type OpinionTopper = Topper & TopperWithHeadshot & TopperWithTheme & {
|
|
|
1077
1079
|
readonly textShadow?: Maybe<Scalars['Boolean']['output']>;
|
|
1078
1080
|
};
|
|
1079
1081
|
export type OpinionTopperHeadshotArgs = {
|
|
1082
|
+
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1083
|
+
url?: InputMaybe<Scalars['String']['input']>;
|
|
1084
|
+
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1085
|
+
};
|
|
1086
|
+
export type Person = {
|
|
1087
|
+
readonly headshot?: Maybe<Scalars['String']['output']>;
|
|
1088
|
+
readonly prefLabel?: Maybe<Scalars['String']['output']>;
|
|
1089
|
+
readonly streamPage?: Maybe<Scalars['String']['output']>;
|
|
1090
|
+
};
|
|
1091
|
+
export type PersonHeadshotArgs = {
|
|
1080
1092
|
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1081
1093
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1082
1094
|
};
|
|
@@ -1234,6 +1246,7 @@ export type PodcastTopper = Topper & TopperWithBrand & TopperWithHeadshot & Topp
|
|
|
1234
1246
|
};
|
|
1235
1247
|
export type PodcastTopperHeadshotArgs = {
|
|
1236
1248
|
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1249
|
+
url?: InputMaybe<Scalars['String']['input']>;
|
|
1237
1250
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1238
1251
|
};
|
|
1239
1252
|
export type Query = {
|
|
@@ -1390,6 +1403,7 @@ export type TopperWithHeadshot = {
|
|
|
1390
1403
|
};
|
|
1391
1404
|
export type TopperWithHeadshotHeadshotArgs = {
|
|
1392
1405
|
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1406
|
+
url?: InputMaybe<Scalars['String']['input']>;
|
|
1393
1407
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1394
1408
|
};
|
|
1395
1409
|
export type TopperWithImages = {
|
|
@@ -1582,6 +1596,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
1582
1596
|
Mutation: ResolverTypeWrapper<{}>;
|
|
1583
1597
|
OpinionTopper: ResolverTypeWrapper<TopperModel>;
|
|
1584
1598
|
PackageDesign: ResolverTypeWrapper<Scalars['PackageDesign']['output']>;
|
|
1599
|
+
Person: ResolverTypeWrapper<PersonModel>;
|
|
1585
1600
|
Picture: ResolverTypeWrapper<PictureModel>;
|
|
1586
1601
|
PictureFullBleed: ResolverTypeWrapper<PictureModel>;
|
|
1587
1602
|
PictureInline: ResolverTypeWrapper<PictureModel>;
|
|
@@ -1671,6 +1686,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
1671
1686
|
Mutation: {};
|
|
1672
1687
|
OpinionTopper: TopperModel;
|
|
1673
1688
|
PackageDesign: Scalars['PackageDesign']['output'];
|
|
1689
|
+
Person: PersonModel;
|
|
1674
1690
|
Picture: PictureModel;
|
|
1675
1691
|
PictureFullBleed: PictureModel;
|
|
1676
1692
|
PictureInline: PictureModel;
|
|
@@ -2187,6 +2203,7 @@ export type LiveBlogPostResolvers<ContextType = QueryContext, ParentType extends
|
|
|
2187
2203
|
altStandfirst: Resolver<Maybe<ResolversTypes['AltStandfirst']>, ParentType, ContextType>;
|
|
2188
2204
|
altTitle: Resolver<Maybe<ResolversTypes['AltTitle']>, ParentType, ContextType>;
|
|
2189
2205
|
annotations: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Concept']>>>, ParentType, ContextType>;
|
|
2206
|
+
authors: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Person']>>>, ParentType, ContextType>;
|
|
2190
2207
|
body: Resolver<Maybe<ResolversTypes['RichText']>, ParentType, ContextType>;
|
|
2191
2208
|
bodyXML: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2192
2209
|
byline: Resolver<Maybe<ResolversTypes['StructuredContent']>, ParentType, ContextType, Partial<LiveBlogPostBylineArgs>>;
|
|
@@ -2248,6 +2265,12 @@ export type OpinionTopperResolvers<ContextType = QueryContext, ParentType extend
|
|
|
2248
2265
|
export interface PackageDesignScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['PackageDesign'], any> {
|
|
2249
2266
|
name: 'PackageDesign';
|
|
2250
2267
|
}
|
|
2268
|
+
export type PersonResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person']> = ResolversObject<{
|
|
2269
|
+
headshot: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, Partial<PersonHeadshotArgs>>;
|
|
2270
|
+
prefLabel: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2271
|
+
streamPage: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2272
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2273
|
+
}>;
|
|
2251
2274
|
export type PictureResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['Picture'] = ResolversParentTypes['Picture']> = ResolversObject<{
|
|
2252
2275
|
__resolveType?: TypeResolveFn<'PictureFullBleed' | 'PictureInline' | 'PictureStandard', ParentType, ContextType>;
|
|
2253
2276
|
alt: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
@@ -2537,6 +2560,7 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
|
|
|
2537
2560
|
Mutation: MutationResolvers<ContextType>;
|
|
2538
2561
|
OpinionTopper: OpinionTopperResolvers<ContextType>;
|
|
2539
2562
|
PackageDesign: GraphQLScalarType;
|
|
2563
|
+
Person: PersonResolvers<ContextType>;
|
|
2540
2564
|
Picture: PictureResolvers<ContextType>;
|
|
2541
2565
|
PictureFullBleed: PictureFullBleedResolvers<ContextType>;
|
|
2542
2566
|
PictureInline: PictureInlineResolvers<ContextType>;
|
|
@@ -2,6 +2,7 @@ import type { ImageSet, ContentTypeSchemas, ClipSet } from '../types/internal-co
|
|
|
2
2
|
import type { QueryContext } from '..';
|
|
3
3
|
import { CAPIImage } from './Image';
|
|
4
4
|
import { Concept } from './Concept';
|
|
5
|
+
import { Person } from './Person';
|
|
5
6
|
import { AccessLevel, ContentType, PackageDesign, CanBeSyndicated } from '../resolvers/scalars';
|
|
6
7
|
import { LiteralUnionScalarValues } from '../resolvers/literal-union';
|
|
7
8
|
import { ContentPackageContainsArgs, ContentUrlArgs, Media, TableOfContents } from '../generated';
|
|
@@ -73,6 +74,7 @@ export declare class CapiResponse {
|
|
|
73
74
|
backgroundBox?: boolean | undefined;
|
|
74
75
|
textShadow?: boolean | undefined;
|
|
75
76
|
} | null | undefined;
|
|
77
|
+
authors(): Person[] | null;
|
|
76
78
|
accessLevel(): LiteralUnionScalarValues<typeof AccessLevel>;
|
|
77
79
|
editorialDesk(): string | null;
|
|
78
80
|
canBeSyndicated(): LiteralUnionScalarValues<typeof CanBeSyndicated>;
|
|
@@ -17,6 +17,7 @@ const errors_1 = require("@dotcom-reliability-kit/errors");
|
|
|
17
17
|
const lodash_sortby_1 = __importDefault(require("lodash.sortby"));
|
|
18
18
|
const Image_1 = require("./Image");
|
|
19
19
|
const Concept_1 = require("./Concept");
|
|
20
|
+
const Person_1 = require("./Person");
|
|
20
21
|
const isError_1 = __importDefault(require("../helpers/isError"));
|
|
21
22
|
const metadata_1 = require("../helpers/metadata");
|
|
22
23
|
const capi_1 = require("./schemas/capi");
|
|
@@ -101,7 +102,7 @@ class CapiResponse {
|
|
|
101
102
|
* Don't let this failure block this system from continuing to try and handle the request
|
|
102
103
|
*/
|
|
103
104
|
if (!schemaResponse.success) {
|
|
104
|
-
context.logger.
|
|
105
|
+
context.logger.warn({
|
|
105
106
|
event: 'RECOVERABLE_ERROR',
|
|
106
107
|
error: new errors_1.OperationalError({
|
|
107
108
|
message: 'The data received from the CAPI data source does not match our data source schema. It is likely that our schema will require updating to handle all possible responses from CAPI.',
|
|
@@ -170,7 +171,7 @@ class CapiResponse {
|
|
|
170
171
|
}
|
|
171
172
|
catch (error) {
|
|
172
173
|
if ((0, isError_1.default)(error)) {
|
|
173
|
-
this.context.logger.
|
|
174
|
+
this.context.logger.warn({
|
|
174
175
|
event: 'RECOVERABLE_ERROR',
|
|
175
176
|
error,
|
|
176
177
|
});
|
|
@@ -189,6 +190,12 @@ class CapiResponse {
|
|
|
189
190
|
return this.capiData.topper;
|
|
190
191
|
return null;
|
|
191
192
|
}
|
|
193
|
+
authors() {
|
|
194
|
+
const authors = this.getAuthors();
|
|
195
|
+
return authors.length === 0
|
|
196
|
+
? null
|
|
197
|
+
: authors.map((author) => new Person_1.Person(author, this, this.context));
|
|
198
|
+
}
|
|
192
199
|
accessLevel() {
|
|
193
200
|
return this.capiData.accessLevel ?? 'subscribed';
|
|
194
201
|
}
|
|
@@ -277,7 +284,22 @@ class CapiResponse {
|
|
|
277
284
|
else {
|
|
278
285
|
const containerId = 'containedIn' in this.capiData && this.capiData.containedIn?.[0]?.id;
|
|
279
286
|
if (containerId) {
|
|
280
|
-
|
|
287
|
+
try {
|
|
288
|
+
container = await this.context.dataSources.capi.getContent((0, metadata_1.uuidFromUrl)(containerId));
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (error instanceof Error) {
|
|
292
|
+
this.context.logger.warn({
|
|
293
|
+
event: 'RECOVERABLE_ERROR',
|
|
294
|
+
error: new errors_1.OperationalError({
|
|
295
|
+
message: `Failed to fetch package container ${containerId} for content ${this.id()}`,
|
|
296
|
+
code: `PACKAGE_CONTAINEDIN_ERROR`,
|
|
297
|
+
cause: error,
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
281
303
|
this.packageContainer = container;
|
|
282
304
|
}
|
|
283
305
|
}
|
|
@@ -419,7 +441,21 @@ class CapiResponse {
|
|
|
419
441
|
? this.capiData.contains.slice(Math.max(0, fromIndex - surroundingArticles), fromIndex + surroundingArticles)
|
|
420
442
|
: this.capiData.contains;
|
|
421
443
|
this.context.addSurrogateKeys(contains.map((article) => article.id));
|
|
422
|
-
|
|
444
|
+
const results = await Promise.allSettled(contains.map(({ id }) => this.context.dataSources.capi.getContent((0, metadata_1.uuidFromUrl)(id), this)));
|
|
445
|
+
const failed = results.filter((result) => result.status === 'rejected');
|
|
446
|
+
if (failed.length) {
|
|
447
|
+
this.context.logger.warn({
|
|
448
|
+
event: 'RECOVERABLE_ERROR',
|
|
449
|
+
error: new errors_1.OperationalError({
|
|
450
|
+
code: 'PACKAGE_CONTAINS_ERROR',
|
|
451
|
+
message: `Failed to fetch some of the content contained in the package ${this.id()}`,
|
|
452
|
+
cause: new AggregateError(failed.map((result) => result.reason)),
|
|
453
|
+
}),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return results
|
|
457
|
+
.filter((result) => result.status === 'fulfilled')
|
|
458
|
+
.map((result) => result.value);
|
|
423
459
|
}
|
|
424
460
|
return null;
|
|
425
461
|
}
|