@financial-times/cp-content-pipeline-schema 3.4.0 → 3.5.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 +23 -0
- package/lib/datasources/capi.d.ts +0 -2
- package/lib/datasources/capi.js +10 -23
- package/lib/datasources/capi.js.map +1 -1
- package/lib/datasources/instrumented.js +4 -1
- package/lib/datasources/instrumented.js.map +1 -1
- package/lib/datasources/origami-image.d.ts +0 -2
- package/lib/datasources/origami-image.js +5 -19
- package/lib/datasources/origami-image.js.map +1 -1
- package/lib/datasources/twitter.d.ts +0 -2
- package/lib/datasources/twitter.js +2 -16
- package/lib/datasources/twitter.js.map +1 -1
- package/lib/datasources/url-management.js +5 -3
- package/lib/datasources/url-management.js.map +1 -1
- package/lib/fixtures/dummyContext.js +1 -1
- package/lib/generated/index.d.ts +69 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js.map +1 -1
- package/lib/model/Byline.js +11 -14
- package/lib/model/Byline.js.map +1 -1
- package/lib/model/CapiList.js +2 -0
- package/lib/model/CapiList.js.map +1 -1
- package/lib/model/CapiResponse.d.ts +7 -3
- package/lib/model/CapiResponse.js +125 -57
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/CapiResponse.test.js +92 -7
- package/lib/model/CapiResponse.test.js.map +1 -1
- package/lib/model/Clip.js +2 -0
- package/lib/model/Clip.js.map +1 -1
- package/lib/model/Concept.js +4 -10
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/FlourishSource.js +5 -10
- package/lib/model/FlourishSource.js.map +1 -1
- package/lib/model/Image.js +10 -20
- package/lib/model/Image.js.map +1 -1
- package/lib/model/LeadFlourish.js +4 -10
- package/lib/model/LeadFlourish.js.map +1 -1
- package/lib/model/Person.js +5 -16
- package/lib/model/Person.js.map +1 -1
- package/lib/model/Picture.js +3 -0
- package/lib/model/Picture.js.map +1 -1
- package/lib/model/RichText.js +3 -0
- package/lib/model/RichText.js.map +1 -1
- package/lib/model/Topper.js +5 -16
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/schemas/capi/internal-content.d.ts +1 -1
- package/lib/resolvers/content-tree/references/RawImage.js +2 -0
- package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
- package/lib/resolvers/content.d.ts +26 -1
- package/lib/resolvers/content.js +21 -1
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/index.d.ts +26 -1
- package/lib/resolvers/literal-union.js +1 -0
- package/lib/resolvers/literal-union.js.map +1 -1
- package/lib/types/connection.d.ts +21 -0
- package/lib/types/connection.js +5 -0
- package/lib/types/connection.js.map +1 -0
- package/package.json +1 -1
- package/queries/article.graphql +8 -2
- package/src/datasources/capi.ts +1 -14
- package/src/datasources/origami-image.ts +1 -13
- package/src/datasources/twitter.ts +1 -14
- package/src/fixtures/dummyContext.ts +1 -1
- package/src/generated/index.ts +71 -1
- package/src/index.ts +1 -0
- package/src/model/CapiResponse.test.ts +137 -7
- package/src/model/CapiResponse.ts +129 -37
- package/src/model/schemas/capi/internal-content.ts +1 -1
- package/src/resolvers/content.ts +31 -1
- package/src/types/connection.ts +28 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +40 -2
- package/lib/helpers/timeout-error.d.ts +0 -6
- package/lib/helpers/timeout-error.js +0 -15
- package/lib/helpers/timeout-error.js.map +0 -1
- package/src/helpers/timeout-error.ts +0 -13
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/types/connection.ts"],"names":[],"mappings":";AAAA,wEAAwE;AACxE,4CAA4C"}
|
package/package.json
CHANGED
package/queries/article.graphql
CHANGED
|
@@ -665,7 +665,7 @@ fragment ArticleFields on Content {
|
|
|
665
665
|
}
|
|
666
666
|
}
|
|
667
667
|
... on LiveBlogPackage {
|
|
668
|
-
liveBlogPosts {
|
|
668
|
+
liveBlogPosts(count: $liveBlogPostCount) {
|
|
669
669
|
...Content
|
|
670
670
|
url
|
|
671
671
|
... on LiveBlogPost {
|
|
@@ -678,6 +678,11 @@ fragment ArticleFields on Content {
|
|
|
678
678
|
}
|
|
679
679
|
}
|
|
680
680
|
}
|
|
681
|
+
liveBlogPostsConnection(first: $liveBlogPostCount) {
|
|
682
|
+
pageInfo {
|
|
683
|
+
hasNextPage
|
|
684
|
+
}
|
|
685
|
+
}
|
|
681
686
|
pinnedPost {
|
|
682
687
|
...PinnedPost
|
|
683
688
|
}
|
|
@@ -696,7 +701,7 @@ fragment ArticleFields on Content {
|
|
|
696
701
|
}
|
|
697
702
|
}
|
|
698
703
|
|
|
699
|
-
query Article($uuid: String!, $useVanities: Boolean
|
|
704
|
+
query Article($uuid: String!, $useVanities: Boolean!, $liveBlogPostCount: Int) {
|
|
700
705
|
content(uuid: $uuid) {
|
|
701
706
|
...ArticleFields
|
|
702
707
|
}
|
|
@@ -705,6 +710,7 @@ query Article($uuid: String!, $useVanities: Boolean!) {
|
|
|
705
710
|
query ArticleFromJSON(
|
|
706
711
|
$content: JSON!
|
|
707
712
|
$useVanities: Boolean!
|
|
713
|
+
$liveBlogPostCount: Int
|
|
708
714
|
$uuid: String = null
|
|
709
715
|
) {
|
|
710
716
|
contentFromJSON(content: $content) {
|
package/src/datasources/capi.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
PrefixingKeyValueCache,
|
|
7
7
|
} from '@apollo/utils.keyvaluecache'
|
|
8
8
|
import { CapiList } from '../model/CapiList'
|
|
9
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
10
9
|
import { Person } from '../model/Person'
|
|
11
10
|
|
|
12
11
|
const REQUEST_TIMEOUT = process.env.CAPI_DATASOURCE_REQUEST_TIMEOUT
|
|
@@ -38,20 +37,13 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
38
37
|
return 'up-ica'
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
abortController = new AbortController()
|
|
42
|
-
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
43
|
-
|
|
44
40
|
nextNotificationLink?: string
|
|
45
41
|
|
|
46
42
|
override willSendRequest(path: string, request: AugmentedRequest) {
|
|
47
43
|
super.willSendRequest(path, request)
|
|
48
44
|
|
|
45
|
+
request.signal = AbortSignal.timeout(REQUEST_TIMEOUT)
|
|
49
46
|
request.headers['x-api-key'] = this.capiKey
|
|
50
|
-
request.signal = this.abortController.signal
|
|
51
|
-
this.timeout = setTimeout(
|
|
52
|
-
() => this.abortController.abort(new TimeoutError(REQUEST_TIMEOUT)),
|
|
53
|
-
REQUEST_TIMEOUT
|
|
54
|
-
)
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
async getContent(
|
|
@@ -71,11 +63,6 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
71
63
|
this.context.contentRequestedOnce = true
|
|
72
64
|
this.calls.push(uuid)
|
|
73
65
|
|
|
74
|
-
if (this.timeout) {
|
|
75
|
-
clearTimeout(this.timeout)
|
|
76
|
-
this.timeout = undefined
|
|
77
|
-
}
|
|
78
|
-
|
|
79
66
|
return CapiResponse.fromJSON(content, this.context, packageContainer)
|
|
80
67
|
}
|
|
81
68
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
2
1
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
3
2
|
import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest'
|
|
4
3
|
|
|
@@ -12,9 +11,6 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
12
11
|
return 'origami-image-service-v2'
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
abortController = new AbortController()
|
|
16
|
-
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
17
|
-
|
|
18
14
|
imageMetadataCacheTTL = process.env.IMAGE_METADATA_CACHE_TTL
|
|
19
15
|
? parseInt(process.env.IMAGE_METADATA_CACHE_TTL)
|
|
20
16
|
: 60 * 60 * 24 * 7 // 1 week
|
|
@@ -22,11 +18,7 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
22
18
|
override willSendRequest(path: string, request: AugmentedRequest) {
|
|
23
19
|
super.willSendRequest(path, request)
|
|
24
20
|
|
|
25
|
-
request.signal =
|
|
26
|
-
this.timeout = setTimeout(
|
|
27
|
-
() => this.abortController.abort(new TimeoutError(REQUEST_TIMEOUT)),
|
|
28
|
-
REQUEST_TIMEOUT
|
|
29
|
-
)
|
|
21
|
+
request.signal = AbortSignal.timeout(REQUEST_TIMEOUT)
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
protected cacheOptionsFor(): CacheOptions {
|
|
@@ -42,10 +34,6 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
42
34
|
|
|
43
35
|
this.calls.push(url)
|
|
44
36
|
|
|
45
|
-
if (this.timeout) {
|
|
46
|
-
clearTimeout(this.timeout)
|
|
47
|
-
this.timeout = undefined
|
|
48
|
-
}
|
|
49
37
|
return imageMetadata
|
|
50
38
|
}
|
|
51
39
|
}
|
|
@@ -2,7 +2,6 @@ import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest'
|
|
|
2
2
|
import * as z from 'zod'
|
|
3
3
|
|
|
4
4
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
5
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
6
5
|
|
|
7
6
|
const REQUEST_TIMEOUT = process.env.TWITTER_DATASOURCE_REQUEST_TIMEOUT
|
|
8
7
|
? parseInt(process.env.TWITTER_DATASOURCE_REQUEST_TIMEOUT)
|
|
@@ -16,17 +15,10 @@ export class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
|
16
15
|
return 'twitter-oembed-api'
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
abortController = new AbortController()
|
|
20
|
-
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
21
|
-
|
|
22
18
|
override willSendRequest(path: string, request: AugmentedRequest) {
|
|
23
19
|
super.willSendRequest(path, request)
|
|
24
20
|
|
|
25
|
-
request.signal =
|
|
26
|
-
this.timeout = setTimeout(
|
|
27
|
-
() => this.abortController.abort(new TimeoutError(REQUEST_TIMEOUT)),
|
|
28
|
-
REQUEST_TIMEOUT
|
|
29
|
-
)
|
|
21
|
+
request.signal = AbortSignal.timeout(REQUEST_TIMEOUT)
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
async getTweet(tweetUrl: string) {
|
|
@@ -34,11 +26,6 @@ export class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
|
34
26
|
|
|
35
27
|
this.calls.push(tweetUrl)
|
|
36
28
|
|
|
37
|
-
if (this.timeout) {
|
|
38
|
-
clearTimeout(this.timeout)
|
|
39
|
-
this.timeout = undefined
|
|
40
|
-
}
|
|
41
|
-
|
|
42
29
|
return Tweet.parse(tweet)
|
|
43
30
|
}
|
|
44
31
|
|
package/src/generated/index.ts
CHANGED
|
@@ -513,6 +513,20 @@ export type ContentUrlArgs = {
|
|
|
513
513
|
vanity?: InputMaybe<Scalars['Boolean']['input']>;
|
|
514
514
|
};
|
|
515
515
|
|
|
516
|
+
export type ContentConnection = {
|
|
517
|
+
/** A list of edges for the connection. */
|
|
518
|
+
readonly edges: ReadonlyArray<Maybe<ContentEdge>>;
|
|
519
|
+
/** Pagination data for the connection. */
|
|
520
|
+
readonly pageInfo: PageInfo;
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export type ContentEdge = {
|
|
524
|
+
/** The opaque cursor for this edge that can be used for future requests. */
|
|
525
|
+
readonly cursor: Scalars['String']['output'];
|
|
526
|
+
/** The child article of this edge. */
|
|
527
|
+
readonly node?: Maybe<Content>;
|
|
528
|
+
};
|
|
529
|
+
|
|
516
530
|
export type ContentPackage = Content & {
|
|
517
531
|
/** A scalar representing the access level of the article, eg. 'free'. */
|
|
518
532
|
readonly accessLevel?: Maybe<Scalars['AccessLevel']['output']>;
|
|
@@ -1120,6 +1134,8 @@ export type LiveBlogPackage = Content & {
|
|
|
1120
1134
|
readonly instantAlertConcept?: Maybe<Concept>;
|
|
1121
1135
|
/** The child articles of this live blog package. */
|
|
1122
1136
|
readonly liveBlogPosts?: Maybe<ReadonlyArray<Maybe<Content>>>;
|
|
1137
|
+
/** The child articles of this live blog package following the [Connections](https://relay.dev/graphql/connections.htm) spec. */
|
|
1138
|
+
readonly liveBlogPostsConnection?: Maybe<ContentConnection>;
|
|
1123
1139
|
/** An image object containing the url and the caption, to be displayed usually before the article content. */
|
|
1124
1140
|
readonly mainImage?: Maybe<Image>;
|
|
1125
1141
|
/** The number of milliseconds since the unix epoch the article was last changed, eg '1712140552443'. */
|
|
@@ -1161,6 +1177,19 @@ export type LiveBlogPackageBylineArgs = {
|
|
|
1161
1177
|
};
|
|
1162
1178
|
|
|
1163
1179
|
|
|
1180
|
+
export type LiveBlogPackageLiveBlogPostsArgs = {
|
|
1181
|
+
count?: InputMaybe<Scalars['Int']['input']>;
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
export type LiveBlogPackageLiveBlogPostsConnectionArgs = {
|
|
1186
|
+
after?: InputMaybe<Scalars['String']['input']>;
|
|
1187
|
+
before?: InputMaybe<Scalars['String']['input']>;
|
|
1188
|
+
first?: InputMaybe<Scalars['Int']['input']>;
|
|
1189
|
+
last?: InputMaybe<Scalars['Int']['input']>;
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
|
|
1164
1193
|
export type LiveBlogPackageUrlArgs = {
|
|
1165
1194
|
relative?: InputMaybe<Scalars['Boolean']['input']>;
|
|
1166
1195
|
vanity?: InputMaybe<Scalars['Boolean']['input']>;
|
|
@@ -1312,6 +1341,17 @@ export type OpinionTopperHeadshotArgs = {
|
|
|
1312
1341
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1313
1342
|
};
|
|
1314
1343
|
|
|
1344
|
+
export type PageInfo = {
|
|
1345
|
+
/** The cursor for the last edge. */
|
|
1346
|
+
readonly endCursor?: Maybe<Scalars['String']['output']>;
|
|
1347
|
+
/** Whether a subsequent page of edges is available on request. */
|
|
1348
|
+
readonly hasNextPage: Scalars['Boolean']['output'];
|
|
1349
|
+
/** Whether a previous page of edges is available on request. */
|
|
1350
|
+
readonly hasPreviousPage: Scalars['Boolean']['output'];
|
|
1351
|
+
/** The cursor for the first edge. */
|
|
1352
|
+
readonly startCursor?: Maybe<Scalars['String']['output']>;
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1315
1355
|
export type PartnerContentTopper = Topper & TopperWithImages & TopperWithTheme & {
|
|
1316
1356
|
/** Whether the topper should have a background box. */
|
|
1317
1357
|
readonly backgroundBox?: Maybe<Scalars['Boolean']['output']>;
|
|
@@ -1976,6 +2016,8 @@ export type ResolversTypes = ResolversObject<{
|
|
|
1976
2016
|
Concept: ResolverTypeWrapper<ConceptModel>;
|
|
1977
2017
|
ConceptInterface: ResolverTypeWrapper<ConceptModel>;
|
|
1978
2018
|
Content: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Content']>;
|
|
2019
|
+
ContentConnection: ResolverTypeWrapper<Omit<ContentConnection, 'edges'> & { edges: ReadonlyArray<Maybe<ResolversTypes['ContentEdge']>> }>;
|
|
2020
|
+
ContentEdge: ResolverTypeWrapper<Omit<ContentEdge, 'node'> & { node: Maybe<ResolversTypes['Content']> }>;
|
|
1979
2021
|
ContentPackage: ResolverTypeWrapper<CapiResponse>;
|
|
1980
2022
|
ContentType: ResolverTypeWrapper<Scalars['ContentType']['output']>;
|
|
1981
2023
|
CustomCodeComponent: ResolverTypeWrapper<ReferenceWithCAPIData<ContentTree.CustomCodeComponent>>;
|
|
@@ -2019,6 +2061,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
2019
2061
|
Mutation: ResolverTypeWrapper<{}>;
|
|
2020
2062
|
OpinionTopper: ResolverTypeWrapper<TopperModel>;
|
|
2021
2063
|
PackageDesign: ResolverTypeWrapper<Scalars['PackageDesign']['output']>;
|
|
2064
|
+
PageInfo: ResolverTypeWrapper<PageInfo>;
|
|
2022
2065
|
PartnerContentTopper: ResolverTypeWrapper<TopperModel>;
|
|
2023
2066
|
Person: ResolverTypeWrapper<PersonModel>;
|
|
2024
2067
|
Picture: ResolverTypeWrapper<PictureModel>;
|
|
@@ -2075,6 +2118,8 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
2075
2118
|
Concept: ConceptModel;
|
|
2076
2119
|
ConceptInterface: ConceptModel;
|
|
2077
2120
|
Content: ResolversInterfaceTypes<ResolversParentTypes>['Content'];
|
|
2121
|
+
ContentConnection: Omit<ContentConnection, 'edges'> & { edges: ReadonlyArray<Maybe<ResolversParentTypes['ContentEdge']>> };
|
|
2122
|
+
ContentEdge: Omit<ContentEdge, 'node'> & { node: Maybe<ResolversParentTypes['Content']> };
|
|
2078
2123
|
ContentPackage: CapiResponse;
|
|
2079
2124
|
ContentType: Scalars['ContentType']['output'];
|
|
2080
2125
|
CustomCodeComponent: ReferenceWithCAPIData<ContentTree.CustomCodeComponent>;
|
|
@@ -2118,6 +2163,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
2118
2163
|
Mutation: {};
|
|
2119
2164
|
OpinionTopper: TopperModel;
|
|
2120
2165
|
PackageDesign: Scalars['PackageDesign']['output'];
|
|
2166
|
+
PageInfo: PageInfo;
|
|
2121
2167
|
PartnerContentTopper: TopperModel;
|
|
2122
2168
|
Person: PersonModel;
|
|
2123
2169
|
Picture: PictureModel;
|
|
@@ -2396,6 +2442,18 @@ export type ContentResolvers<ContextType = QueryContext, ParentType extends Reso
|
|
|
2396
2442
|
url: Resolver<ResolversTypes['String'], ParentType, ContextType, Partial<ContentUrlArgs>>;
|
|
2397
2443
|
}>;
|
|
2398
2444
|
|
|
2445
|
+
export type ContentConnectionResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ContentConnection'] = ResolversParentTypes['ContentConnection']> = ResolversObject<{
|
|
2446
|
+
edges: Resolver<ReadonlyArray<Maybe<ResolversTypes['ContentEdge']>>, ParentType, ContextType>;
|
|
2447
|
+
pageInfo: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
|
|
2448
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2449
|
+
}>;
|
|
2450
|
+
|
|
2451
|
+
export type ContentEdgeResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ContentEdge'] = ResolversParentTypes['ContentEdge']> = ResolversObject<{
|
|
2452
|
+
cursor: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
2453
|
+
node: Resolver<Maybe<ResolversTypes['Content']>, ParentType, ContextType>;
|
|
2454
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2455
|
+
}>;
|
|
2456
|
+
|
|
2399
2457
|
export type ContentPackageResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ContentPackage'] = ResolversParentTypes['ContentPackage']> = ResolversObject<{
|
|
2400
2458
|
accessLevel: Resolver<Maybe<ResolversTypes['AccessLevel']>, ParentType, ContextType>;
|
|
2401
2459
|
altStandfirst: Resolver<Maybe<ResolversTypes['AltStandfirst']>, ParentType, ContextType>;
|
|
@@ -2749,7 +2807,8 @@ export type LiveBlogPackageResolvers<ContextType = QueryContext, ParentType exte
|
|
|
2749
2807
|
firstPublishedDate: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
2750
2808
|
id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
|
2751
2809
|
instantAlertConcept: Resolver<Maybe<ResolversTypes['Concept']>, ParentType, ContextType>;
|
|
2752
|
-
liveBlogPosts: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Content']>>>, ParentType, ContextType
|
|
2810
|
+
liveBlogPosts: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Content']>>>, ParentType, ContextType, Partial<LiveBlogPackageLiveBlogPostsArgs>>;
|
|
2811
|
+
liveBlogPostsConnection: Resolver<Maybe<ResolversTypes['ContentConnection']>, ParentType, ContextType, Partial<LiveBlogPackageLiveBlogPostsConnectionArgs>>;
|
|
2753
2812
|
mainImage: Resolver<Maybe<ResolversTypes['Image']>, ParentType, ContextType>;
|
|
2754
2813
|
modifiedTimestamp: Resolver<Maybe<ResolversTypes['Float']>, ParentType, ContextType>;
|
|
2755
2814
|
originatingParty: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
@@ -2843,6 +2902,14 @@ export interface PackageDesignScalarConfig extends GraphQLScalarTypeConfig<Resol
|
|
|
2843
2902
|
name: 'PackageDesign';
|
|
2844
2903
|
}
|
|
2845
2904
|
|
|
2905
|
+
export type PageInfoResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['PageInfo'] = ResolversParentTypes['PageInfo']> = ResolversObject<{
|
|
2906
|
+
endCursor: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2907
|
+
hasNextPage: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
|
2908
|
+
hasPreviousPage: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
|
2909
|
+
startCursor: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2910
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2911
|
+
}>;
|
|
2912
|
+
|
|
2846
2913
|
export type PartnerContentTopperResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['PartnerContentTopper'] = ResolversParentTypes['PartnerContentTopper']> = ResolversObject<{
|
|
2847
2914
|
backgroundBox: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
|
2848
2915
|
backgroundColour: Resolver<Maybe<ResolversTypes['TopperBackgroundColour']>, ParentType, ContextType>;
|
|
@@ -3183,6 +3250,8 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
|
|
|
3183
3250
|
Concept: ConceptResolvers<ContextType>;
|
|
3184
3251
|
ConceptInterface: ConceptInterfaceResolvers<ContextType>;
|
|
3185
3252
|
Content: ContentResolvers<ContextType>;
|
|
3253
|
+
ContentConnection: ContentConnectionResolvers<ContextType>;
|
|
3254
|
+
ContentEdge: ContentEdgeResolvers<ContextType>;
|
|
3186
3255
|
ContentPackage: ContentPackageResolvers<ContextType>;
|
|
3187
3256
|
ContentType: GraphQLScalarType;
|
|
3188
3257
|
CustomCodeComponent: CustomCodeComponentResolvers<ContextType>;
|
|
@@ -3223,6 +3292,7 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
|
|
|
3223
3292
|
Mutation: MutationResolvers<ContextType>;
|
|
3224
3293
|
OpinionTopper: OpinionTopperResolvers<ContextType>;
|
|
3225
3294
|
PackageDesign: GraphQLScalarType;
|
|
3295
|
+
PageInfo: PageInfoResolvers<ContextType>;
|
|
3226
3296
|
PartnerContentTopper: PartnerContentTopperResolvers<ContextType>;
|
|
3227
3297
|
Person: PersonResolvers<ContextType>;
|
|
3228
3298
|
Picture: PictureResolvers<ContextType>;
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { CapiResponse } from './CapiResponse'
|
|
|
2
2
|
import { baseCapiObject } from '../fixtures/capiObject'
|
|
3
3
|
import cloneDeep from 'clone-deep'
|
|
4
4
|
import context from '../fixtures/dummyContext'
|
|
5
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
6
5
|
|
|
7
6
|
describe('CAPI response', () => {
|
|
8
7
|
describe('Content ID', () => {
|
|
@@ -70,7 +69,7 @@ describe('CAPI response', () => {
|
|
|
70
69
|
})
|
|
71
70
|
|
|
72
71
|
describe('liveBlogPosts', () => {
|
|
73
|
-
test('returns a resolved array of the articles contained
|
|
72
|
+
test('returns a resolved array of the articles contained', async () => {
|
|
74
73
|
const liveBlogPackage = cloneDeep({
|
|
75
74
|
...baseCapiObject,
|
|
76
75
|
type: 'http://www.ft.com/ontology/content/LiveBlogPackage',
|
|
@@ -81,14 +80,14 @@ describe('CAPI response', () => {
|
|
|
81
80
|
'http://api.ft.com/content/000000000-0000-0000-0000-000000000000',
|
|
82
81
|
},
|
|
83
82
|
{
|
|
84
|
-
id: 'http://api.ft.com/things/000000000-0000-0000-0000-
|
|
83
|
+
id: 'http://api.ft.com/things/000000000-0000-0000-0000-000000000001',
|
|
85
84
|
apiUrl:
|
|
86
|
-
'http://api.ft.com/content/000000000-0000-0000-0000-
|
|
85
|
+
'http://api.ft.com/content/000000000-0000-0000-0000-000000000001',
|
|
87
86
|
},
|
|
88
87
|
{
|
|
89
|
-
id: 'http://api.ft.com/things/000000000-0000-0000-0000-
|
|
88
|
+
id: 'http://api.ft.com/things/000000000-0000-0000-0000-000000000002',
|
|
90
89
|
apiUrl:
|
|
91
|
-
'http://api.ft.com/content/000000000-0000-0000-0000-
|
|
90
|
+
'http://api.ft.com/content/000000000-0000-0000-0000-000000000002',
|
|
92
91
|
},
|
|
93
92
|
],
|
|
94
93
|
})
|
|
@@ -107,6 +106,137 @@ describe('CAPI response', () => {
|
|
|
107
106
|
})
|
|
108
107
|
})
|
|
109
108
|
|
|
109
|
+
describe('liveBlogPostsConnection', () => {
|
|
110
|
+
const generateId = (i: number) =>
|
|
111
|
+
'00000000-0000-0000-0000-0000000000' + String(i).padStart(2, '0')
|
|
112
|
+
|
|
113
|
+
const postCount = 100
|
|
114
|
+
const liveBlogPackage = cloneDeep({
|
|
115
|
+
...baseCapiObject,
|
|
116
|
+
type: 'http://www.ft.com/ontology/content/LiveBlogPackage',
|
|
117
|
+
contains: [...Array(postCount).keys()].map((i) => {
|
|
118
|
+
const id = generateId(postCount - 1 - i)
|
|
119
|
+
return {
|
|
120
|
+
id: 'http://api.ft.com/things/' + id,
|
|
121
|
+
apiUrl: 'http://api.ft.com/content/' + id,
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
})
|
|
125
|
+
const capiResponse = new CapiResponse(liveBlogPackage, context)
|
|
126
|
+
|
|
127
|
+
test('returns all posts when no argument specified', async () => {
|
|
128
|
+
const liveBlogPostsConnection =
|
|
129
|
+
await capiResponse.liveBlogPostsConnection({})
|
|
130
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(postCount)
|
|
131
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
132
|
+
expect(edge.node.id()).toEqual(generateId(i))
|
|
133
|
+
)
|
|
134
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
135
|
+
expect.objectContaining({
|
|
136
|
+
hasPreviousPage: false,
|
|
137
|
+
hasNextPage: false,
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('returns first x edges', async () => {
|
|
143
|
+
const firstCount = 20
|
|
144
|
+
const liveBlogPostsConnection =
|
|
145
|
+
await capiResponse.liveBlogPostsConnection({ first: firstCount })
|
|
146
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(firstCount)
|
|
147
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
148
|
+
expect(edge.node.id()).toEqual(generateId(i))
|
|
149
|
+
)
|
|
150
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
151
|
+
expect.objectContaining({
|
|
152
|
+
hasPreviousPage: false,
|
|
153
|
+
hasNextPage: true,
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('returns last x edges', async () => {
|
|
159
|
+
const lastCount = 20
|
|
160
|
+
const liveBlogPostsConnection =
|
|
161
|
+
await capiResponse.liveBlogPostsConnection({ last: lastCount })
|
|
162
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(lastCount)
|
|
163
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
164
|
+
expect(edge.node.id()).toEqual(generateId(i + (postCount - lastCount)))
|
|
165
|
+
)
|
|
166
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
hasPreviousPage: true,
|
|
169
|
+
hasNextPage: false,
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('returns edges after cursor', async () => {
|
|
175
|
+
const firstCount = 20
|
|
176
|
+
const startingLiveBlogPostsConnection =
|
|
177
|
+
await capiResponse.liveBlogPostsConnection({ first: firstCount })
|
|
178
|
+
const cursor = startingLiveBlogPostsConnection.pageInfo.endCursor
|
|
179
|
+
expect(cursor).toBeDefined()
|
|
180
|
+
|
|
181
|
+
const liveBlogPostsConnection =
|
|
182
|
+
await capiResponse.liveBlogPostsConnection({
|
|
183
|
+
first: firstCount,
|
|
184
|
+
after: cursor,
|
|
185
|
+
})
|
|
186
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(firstCount)
|
|
187
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
188
|
+
expect(edge.node.id()).toEqual(generateId(i + firstCount))
|
|
189
|
+
)
|
|
190
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
hasPreviousPage: true,
|
|
193
|
+
hasNextPage: true,
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('returns edges before cursor', async () => {
|
|
199
|
+
const lastCount = 20
|
|
200
|
+
const endingLiveBlogPostsConnection =
|
|
201
|
+
await capiResponse.liveBlogPostsConnection({ last: lastCount })
|
|
202
|
+
const cursor = endingLiveBlogPostsConnection.pageInfo.startCursor
|
|
203
|
+
expect(cursor).toBeDefined()
|
|
204
|
+
|
|
205
|
+
const liveBlogPostsConnection =
|
|
206
|
+
await capiResponse.liveBlogPostsConnection({
|
|
207
|
+
last: lastCount,
|
|
208
|
+
before: cursor,
|
|
209
|
+
})
|
|
210
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(lastCount)
|
|
211
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
212
|
+
expect(edge.node.id()).toEqual(
|
|
213
|
+
generateId(i + (postCount - lastCount * 2))
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
217
|
+
expect.objectContaining({
|
|
218
|
+
hasPreviousPage: true,
|
|
219
|
+
hasNextPage: true,
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('returns all edges when first is greater than total posts', async () => {
|
|
225
|
+
const liveBlogPostsConnection =
|
|
226
|
+
await capiResponse.liveBlogPostsConnection({ first: postCount + 1 })
|
|
227
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(postCount)
|
|
228
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
229
|
+
expect(edge.node.id()).toEqual(generateId(i))
|
|
230
|
+
)
|
|
231
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
232
|
+
expect.objectContaining({
|
|
233
|
+
hasPreviousPage: false,
|
|
234
|
+
hasNextPage: false,
|
|
235
|
+
})
|
|
236
|
+
)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
110
240
|
describe('Partner content type articles', () => {
|
|
111
241
|
test('should have `isPartnerContent` set correctly as `true` when correct publication id is set', () => {
|
|
112
242
|
const article = cloneDeep({
|
|
@@ -137,7 +267,7 @@ describe('CAPI response', () => {
|
|
|
137
267
|
|
|
138
268
|
const timingOutContext = cloneDeep(context)
|
|
139
269
|
timingOutContext.dataSources.capi.getPerson = () =>
|
|
140
|
-
Promise.reject(new TimeoutError
|
|
270
|
+
Promise.reject(new DOMException(undefined, 'TimeoutError'))
|
|
141
271
|
const capiResponse = new CapiResponse(article, timingOutContext)
|
|
142
272
|
|
|
143
273
|
const authors = await capiResponse.authors()
|