@financial-times/cp-content-pipeline-schema 0.4.6 → 0.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/content.js +2 -2
  3. package/lib/datasources/capi.d.ts +2 -0
  4. package/lib/datasources/capi.js +32 -11
  5. package/lib/datasources/capi.js.map +1 -1
  6. package/lib/datasources/origami-image.d.ts +4 -0
  7. package/lib/datasources/origami-image.js +27 -1
  8. package/lib/datasources/origami-image.js.map +1 -1
  9. package/lib/datasources/twitter.d.ts +4 -1
  10. package/lib/datasources/twitter.js +27 -1
  11. package/lib/datasources/twitter.js.map +1 -1
  12. package/lib/fixtures/capiObject.js +1 -2
  13. package/lib/fixtures/capiObject.js.map +1 -1
  14. package/lib/model/CapiResponse.d.ts +1202 -85
  15. package/lib/model/CapiResponse.js +34 -11
  16. package/lib/model/CapiResponse.js.map +1 -1
  17. package/lib/model/Image.d.ts +28 -7
  18. package/lib/model/Image.js +39 -19
  19. package/lib/model/Image.js.map +1 -1
  20. package/lib/model/Image.test.js +25 -25
  21. package/lib/model/Image.test.js.map +1 -1
  22. package/lib/model/Picture.d.ts +21 -0
  23. package/lib/model/Picture.js +65 -0
  24. package/lib/model/Picture.js.map +1 -0
  25. package/lib/model/Topper.d.ts +3 -3
  26. package/lib/model/Topper.js +2 -2
  27. package/lib/model/Topper.js.map +1 -1
  28. package/lib/model/schemas/capi/article.d.ts +617 -0
  29. package/lib/model/schemas/capi/article.js +31 -0
  30. package/lib/model/schemas/capi/article.js.map +1 -0
  31. package/lib/{datasources/schemas/capi.d.ts → model/schemas/capi/base-schema.d.ts} +657 -672
  32. package/lib/{datasources/schemas/capi.js → model/schemas/capi/base-schema.js} +57 -60
  33. package/lib/model/schemas/capi/base-schema.js.map +1 -0
  34. package/lib/model/schemas/capi/index.d.ts +1233 -0
  35. package/lib/model/schemas/capi/index.js +13 -0
  36. package/lib/model/schemas/capi/index.js.map +1 -0
  37. package/lib/model/schemas/capi/live-blog-package.d.ts +615 -0
  38. package/lib/model/schemas/capi/live-blog-package.js +30 -0
  39. package/lib/model/schemas/capi/live-blog-package.js.map +1 -0
  40. package/lib/picture.d.ts +9 -11
  41. package/lib/picture.js +7 -35
  42. package/lib/picture.js.map +1 -1
  43. package/lib/richText.d.ts +1 -1
  44. package/lib/richText.js +17 -6
  45. package/lib/richText.js.map +1 -1
  46. package/lib/richText.test.js +32 -0
  47. package/lib/richText.test.js.map +1 -1
  48. package/lib/tags/ImageSet.d.ts +1 -0
  49. package/lib/tags/ImageSet.js +19 -3
  50. package/lib/tags/ImageSet.js.map +1 -1
  51. package/lib/tags/LayoutImage.js +4 -3
  52. package/lib/tags/LayoutImage.js.map +1 -1
  53. package/lib/tags/Tweet.js +11 -1
  54. package/lib/tags/Tweet.js.map +1 -1
  55. package/lib/tags/index.js +2 -1
  56. package/lib/tags/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/__snapshots__/richText.test.ts.snap +2647 -17382
  59. package/src/content.ts +2 -2
  60. package/src/datasources/capi.ts +43 -11
  61. package/src/datasources/origami-image.ts +35 -1
  62. package/src/datasources/twitter.ts +35 -2
  63. package/src/fixtures/capiObject.ts +1 -2
  64. package/src/model/CapiResponse.ts +56 -25
  65. package/src/model/Image.test.ts +25 -25
  66. package/src/model/Image.ts +53 -12
  67. package/src/model/Picture.ts +75 -0
  68. package/src/model/Topper.ts +7 -4
  69. package/src/model/schemas/capi/article.ts +38 -0
  70. package/src/{datasources/schemas/capi.ts → model/schemas/capi/base-schema.ts} +63 -65
  71. package/src/model/schemas/capi/index.ts +21 -0
  72. package/src/model/schemas/capi/live-blog-package.ts +37 -0
  73. package/src/picture.ts +11 -47
  74. package/src/richText.test.ts +42 -0
  75. package/src/richText.ts +17 -6
  76. package/src/tags/ImageSet.ts +24 -4
  77. package/src/tags/LayoutImage.ts +22 -17
  78. package/src/tags/Tweet.ts +12 -1
  79. package/src/tags/index.ts +2 -1
  80. package/src/types/internal-content.d.ts +8 -1
  81. package/tsconfig.tsbuildinfo +1 -1
  82. package/lib/datasources/schemas/capi.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v0.4.7...cp-content-pipeline-schema-v0.5.0) (2022-12-14)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * render main image outside of body
9
+
10
+ ### Features
11
+
12
+ * render main image outside of body ([980ff15](https://github.com/Financial-Times/cp-content-pipeline/commit/980ff15091f7ffd9df5c2dbe99bafc4aea5bb34e))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * return and expect null from image model methods ([8235c6a](https://github.com/Financial-Times/cp-content-pipeline/commit/8235c6a786b7272599e5e7bc07dc478469701566))
18
+
19
+ ## [0.4.7](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v0.4.6...cp-content-pipeline-schema-v0.4.7) (2022-12-02)
20
+
21
+
22
+ ### Features
23
+
24
+ * add 5s timeout to origami image service ([9c50168](https://github.com/Financial-Times/cp-content-pipeline/commit/9c5016808763b00fd122064e3fab5c8d368262a8))
25
+ * add 5s timeout to twitter response ([8aae9ca](https://github.com/Financial-Times/cp-content-pipeline/commit/8aae9ca6b011fb694145abf089382cb10568214c))
26
+ * allow for different schemas by content type ([ee1090b](https://github.com/Financial-Times/cp-content-pipeline/commit/ee1090bc2400638bc481d3699cc5932783141c57))
27
+ * enable different schemas for different content types ([edccf4c](https://github.com/Financial-Times/cp-content-pipeline/commit/edccf4cc8fe767590328c8c349990f04816ba1a8))
28
+ * separate schema validation metrics by content type ([711f164](https://github.com/Financial-Times/cp-content-pipeline/commit/711f16460472f4e4318108256da1156d8fc10e6e))
29
+ * timeout CAPI requests longer than 5s ([c8e43ba](https://github.com/Financial-Times/cp-content-pipeline/commit/c8e43ba0590235c7f47593302fa2a8c0ef8fd243))
30
+
3
31
  ## [0.4.6](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v0.4.5...cp-content-pipeline-schema-v0.4.6) (2022-11-23)
4
32
 
5
33
 
package/lib/content.js CHANGED
@@ -7,8 +7,8 @@ const contentFields = `
7
7
  type: String!
8
8
  standfirst: String
9
9
  topper: Topper
10
- body: RichText!
11
- bodyXML: String!
10
+ body: RichText
11
+ bodyXML: String
12
12
  url(relative: Boolean): String!
13
13
  publishedDate: String!
14
14
  firstPublishedDate: String!
@@ -9,6 +9,8 @@ export declare class CapiDataSource extends InstrumentedRESTDataSource {
9
9
  articleCacheTTL: number;
10
10
  peopleCacheTTL: number;
11
11
  backendSystemCodes: string[];
12
+ abortController: AbortController;
13
+ timeout: ReturnType<typeof setTimeout> | undefined;
12
14
  willSendRequest(request: WillSendRequestOptions): void;
13
15
  protected cacheOptionsFor(url: string): CacheOptions | undefined;
14
16
  getContent(uuid: string): Promise<CapiResponse>;
@@ -1,8 +1,8 @@
1
1
  import { CapiResponse } from '../model/CapiResponse.js';
2
2
  import { InstrumentedRESTDataSource } from './instrumented.js';
3
- import { InternalContentSchema } from './schemas/capi.js';
4
- import { OperationalError } from '@dotcom-reliability-kit/errors';
3
+ import { OperationalError, UpstreamServiceError, } from '@dotcom-reliability-kit/errors';
5
4
  import { logRecoverableError } from '@dotcom-reliability-kit/log-error';
5
+ const REQUEST_TIMEOUT = 5000; // 5 seconds
6
6
  export class CapiDataSource extends InstrumentedRESTDataSource {
7
7
  constructor() {
8
8
  super(...arguments);
@@ -15,9 +15,13 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
15
15
  ? parseInt(process.env.PEOPLE_CACHE_TTL)
16
16
  : 600; // 10 minutes
17
17
  this.backendSystemCodes = ['up-ica'];
18
+ this.abortController = new AbortController();
19
+ this.timeout = undefined;
18
20
  }
19
21
  willSendRequest(request) {
20
22
  request.headers['x-api-key'] = this.capiKey;
23
+ request.signal = this.abortController.signal;
24
+ this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
21
25
  }
22
26
  cacheOptionsFor(url) {
23
27
  if (url.startsWith('internalcontent/')) {
@@ -28,15 +32,21 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
28
32
  }
29
33
  }
30
34
  async getContent(uuid) {
31
- return this.get(`internalcontent/${uuid}`).then((response) => {
32
- var _a, _b;
35
+ var _a, _b;
36
+ try {
37
+ const content = await this.get(`internalcontent/${uuid}`);
38
+ if (this.timeout) {
39
+ clearTimeout(this.timeout);
40
+ this.timeout = undefined;
41
+ }
42
+ const model = new CapiResponse(content, this.context);
33
43
  /*
34
44
  * Check if the incoming data matches the types defined in our data source schema
35
45
  * Our data source schema should handle all possible response from CAPI
36
46
  * As there is no agreed schema provided by CAPI there is a chance it will out of date / incorrect at times
37
47
  * Manual updates will be required at times to keep it in sync with the responses we receive
38
48
  */
39
- const schemaResponse = InternalContentSchema.safeParse(response);
49
+ const schemaResponse = model.schema().safeParse(content);
40
50
  /*
41
51
  * Log an error if the response fails validation
42
52
  * Logs should be used to warn us of issues or response changes
@@ -49,17 +59,28 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
49
59
  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.',
50
60
  code: 'CAPI_SCHEMA_VALIDATION_FAILURE',
51
61
  cause: schemaResponse.error,
52
- contentId: response.id,
53
- contentType: response.type,
62
+ contentId: content.id,
63
+ contentType: content.type,
54
64
  }),
55
65
  });
56
- (_a = this.context.metrics) === null || _a === void 0 ? void 0 : _a.count(`graphql.datasource.${this.constructor.name}.validation.failure.count`, 1);
66
+ (_a = this.context.metrics) === null || _a === void 0 ? void 0 : _a.count(`graphql.datasource.${this.constructor.name}.${model.type()}.validation.failure.count`, 1);
57
67
  }
58
68
  else {
59
- (_b = this.context.metrics) === null || _b === void 0 ? void 0 : _b.count(`graphql.datasource.${this.constructor.name}.validation.success.count`, 1);
69
+ (_b = this.context.metrics) === null || _b === void 0 ? void 0 : _b.count(`graphql.datasource.${this.constructor.name}.${model.type()}.validation.success.count`, 1);
60
70
  }
61
- return new CapiResponse(response, this.context);
62
- });
71
+ return model;
72
+ }
73
+ catch (error) {
74
+ if (error instanceof Error && error.name === 'AbortError') {
75
+ throw new UpstreamServiceError({
76
+ code: 'CAPI_DATASOURCE_TIMEOUT',
77
+ message: `Request to Internal Content API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
78
+ statusCode: 408,
79
+ relatesToSystems: this.backendSystemCodes,
80
+ });
81
+ }
82
+ throw error;
83
+ }
63
84
  }
64
85
  async getPerson(uuid) {
65
86
  return this.get(`people/${uuid}`);
@@ -1 +1 @@
1
- {"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AAEvE,MAAM,OAAO,cAAe,SAAQ,0BAA0B;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,CAAA,CAAC,aAAa;QACpB,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,uBAAkB,GAAG,CAAC,QAAQ,CAAC,CAAA;IA4DjC,CAAC;IA1DU,eAAe,CAAC,OAA+B;QACtD,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,OAAO,CAAA;IAC7C,CAAC;IAES,eAAe,CAAC,GAAW;QACnC,IAAI,GAAG,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE;YACtC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,CAAA;SACrC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE;YACpC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,CAAA;SACpC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,OAAO,IAAI,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;;YAC3D;;;;;eAKG;YACH,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;YAEhE;;;;eAIG;YACH,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;gBAC3B,mBAAmB,CAAC;oBAClB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;oBAC3B,KAAK,EAAE,IAAI,gBAAgB,CAAC;wBAC1B,OAAO,EACL,mLAAmL;wBACrL,IAAI,EAAE,gCAAgC;wBACtC,KAAK,EAAE,cAAc,CAAC,KAAK;wBAC3B,SAAS,EAAE,QAAQ,CAAC,EAAE;wBACtB,WAAW,EAAE,QAAQ,CAAC,IAAI;qBAC3B,CAAC;iBACH,CAAC,CAAA;gBAEF,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,2BAA2B,EACtE,CAAC,CACF,CAAA;aACF;iBAAM;gBACL,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,2BAA2B,EACtE,CAAC,CACF,CAAA;aACF;YAED,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACjD,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACnC,CAAC;CACF"}
1
+ {"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAG9D,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AAEvE,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AACzC,MAAM,OAAO,cAAe,SAAQ,0BAA0B;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,CAAA,CAAC,aAAa;QACpB,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,uBAAkB,GAAG,CAAC,QAAQ,CAAC,CAAA;QAE/B,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IAsFhE,CAAC;IApFU,eAAe,CAAC,OAA+B;QACtD,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;IAES,eAAe,CAAC,GAAW;QACnC,IAAI,GAAG,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE;YACtC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,CAAA;SACrC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE;YACpC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,CAAA;SACpC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;;QAC3B,IAAI;YACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAA;YAEzD,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;aACzB;YACD,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;YAErD;;;;;eAKG;YACH,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;YACxD;;;;eAIG;YACH,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;gBAC3B,mBAAmB,CAAC;oBAClB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;oBAC3B,KAAK,EAAE,IAAI,gBAAgB,CAAC;wBAC1B,OAAO,EACL,mLAAmL;wBACrL,IAAI,EAAE,gCAAgC;wBACtC,KAAK,EAAE,cAAc,CAAC,KAAK;wBAC3B,SAAS,EAAE,OAAO,CAAC,EAAE;wBACrB,WAAW,EAAE,OAAO,CAAC,IAAI;qBAC1B,CAAC;iBACH,CAAC,CAAA;gBAEF,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBACE,IAAI,CAAC,WAAW,CAAC,IACnB,IAAI,KAAK,CAAC,IAAI,EAAE,2BAA2B,EAC3C,CAAC,CACF,CAAA;aACF;iBAAM;gBACL,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBACE,IAAI,CAAC,WAAW,CAAC,IACnB,IAAI,KAAK,CAAC,IAAI,EAAE,2BAA2B,EAC3C,CAAC,CACF,CAAA;aACF;YAED,OAAO,KAAK,CAAA;SACb;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE;gBACzD,MAAM,IAAI,oBAAoB,CAAC;oBAC7B,IAAI,EAAE,yBAAyB;oBAC/B,OAAO,EAAE,oDAAoD,eAAe,8BAA8B;oBAC1G,UAAU,EAAE,GAAG;oBACf,gBAAgB,EAAE,IAAI,CAAC,kBAAkB;iBAC1C,CAAC,CAAA;aACH;YACD,MAAM,KAAK,CAAA;SACZ;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACnC,CAAC;CACF"}
@@ -1,7 +1,11 @@
1
+ import { WillSendRequestOptions } from '@apollo/datasource-rest';
1
2
  import { InstrumentedRESTDataSource } from './instrumented.js';
2
3
  export declare class OrigamiImageDataSource extends InstrumentedRESTDataSource {
3
4
  baseURL: string;
4
5
  backendSystemCodes: string[];
6
+ abortController: AbortController;
7
+ timeout: ReturnType<typeof setTimeout> | undefined;
8
+ willSendRequest(request: WillSendRequestOptions): void;
5
9
  getImageMetadata(url: string): Promise<{
6
10
  width: number;
7
11
  height: number;
@@ -1,12 +1,38 @@
1
+ import { UpstreamServiceError } from '@dotcom-reliability-kit/errors';
1
2
  import { InstrumentedRESTDataSource } from './instrumented.js';
3
+ const REQUEST_TIMEOUT = 5000; // 5 seconds
2
4
  export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
3
5
  constructor() {
4
6
  super(...arguments);
5
7
  this.baseURL = 'https://www.ft.com/__origami/service/image/v2';
6
8
  this.backendSystemCodes = ['origami-image-service-v2'];
9
+ this.abortController = new AbortController();
10
+ this.timeout = undefined;
11
+ }
12
+ willSendRequest(request) {
13
+ request.signal = this.abortController.signal;
14
+ this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
7
15
  }
8
16
  async getImageMetadata(url) {
9
- return this.get(`/images/metadata/${encodeURIComponent(url)}?source=next`);
17
+ try {
18
+ const imageMetadata = await this.get(`/images/metadata/${encodeURIComponent(url)}?source=next`);
19
+ if (this.timeout) {
20
+ clearTimeout(this.timeout);
21
+ this.timeout = undefined;
22
+ }
23
+ return imageMetadata;
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error && error.name === 'AbortError') {
27
+ throw new UpstreamServiceError({
28
+ code: 'ORIGAMI_DATASOURCE_TIMEOUT',
29
+ message: `Request to Image Service took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
30
+ statusCode: 408,
31
+ relatesToSystems: this.backendSystemCodes,
32
+ });
33
+ }
34
+ throw error;
35
+ }
10
36
  }
11
37
  }
12
38
  //# sourceMappingURL=origami-image.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,MAAM,OAAO,sBAAuB,SAAQ,0BAA0B;IAAtE;;QACE,YAAO,GAAG,+CAA+C,CAAA;QACzD,uBAAkB,GAAG,CAAC,0BAA0B,CAAC,CAAA;IAOnD,CAAC;IALC,KAAK,CAAC,gBAAgB,CACpB,GAAW;QAEX,OAAO,IAAI,CAAC,GAAG,CAAC,oBAAoB,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAC5E,CAAC;CACF"}
1
+ {"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AAEzC,MAAM,OAAO,sBAAuB,SAAQ,0BAA0B;IAAtE;;QACE,YAAO,GAAG,+CAA+C,CAAA;QACzD,uBAAkB,GAAG,CAAC,0BAA0B,CAAC,CAAA;QAEjD,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IAkChE,CAAC;IAhCU,eAAe,CAAC,OAA+B;QACtD,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,gBAAgB,CACpB,GAAW;QAEX,IAAI;YACF,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,GAAG,CAClC,oBAAoB,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAC1D,CAAA;YACD,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;aACzB;YACD,OAAO,aAAa,CAAA;SACrB;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE;gBACzD,MAAM,IAAI,oBAAoB,CAAC;oBAC7B,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,6CAA6C,eAAe,8BAA8B;oBACnG,UAAU,EAAE,GAAG;oBACf,gBAAgB,EAAE,IAAI,CAAC,kBAAkB;iBAC1C,CAAC,CAAA;aACH;YACD,MAAM,KAAK,CAAA;SACZ;IACH,CAAC;CACF"}
@@ -1,8 +1,11 @@
1
- import { CacheOptions } from '@apollo/datasource-rest/dist/RESTDataSource.js';
1
+ import { CacheOptions, WillSendRequestOptions } from '@apollo/datasource-rest/dist/RESTDataSource.js';
2
2
  import { InstrumentedRESTDataSource } from './instrumented.js';
3
3
  export declare class TwitterDataSource extends InstrumentedRESTDataSource {
4
4
  baseURL: string;
5
5
  backendSystemCodes: string[];
6
+ abortController: AbortController;
7
+ timeout: ReturnType<typeof setTimeout> | undefined;
8
+ willSendRequest(request: WillSendRequestOptions): void;
6
9
  getTweet(tweetUrl: string): Promise<any>;
7
10
  cacheOptionsFor(): CacheOptions;
8
11
  }
@@ -1,12 +1,38 @@
1
+ import { UpstreamServiceError } from '@dotcom-reliability-kit/errors';
1
2
  import { InstrumentedRESTDataSource } from './instrumented.js';
3
+ const REQUEST_TIMEOUT = 5000; // 5 seconds
2
4
  export class TwitterDataSource extends InstrumentedRESTDataSource {
3
5
  constructor() {
4
6
  super(...arguments);
5
7
  this.baseURL = 'https://publish.twitter.com';
6
8
  this.backendSystemCodes = ['twitter'];
9
+ this.abortController = new AbortController();
10
+ this.timeout = undefined;
11
+ }
12
+ willSendRequest(request) {
13
+ request.signal = this.abortController.signal;
14
+ this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
7
15
  }
8
16
  async getTweet(tweetUrl) {
9
- return this.get(`/oembed?url=${tweetUrl}&omit_script=true`);
17
+ try {
18
+ const tweet = await this.get(`/oembed?url=${tweetUrl}&omit_script=true`);
19
+ if (this.timeout) {
20
+ clearTimeout(this.timeout);
21
+ this.timeout = undefined;
22
+ }
23
+ return tweet;
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error && error.name === 'AbortError') {
27
+ throw new UpstreamServiceError({
28
+ code: 'TWITTER_DATASOURCE_TIMEOUT',
29
+ message: `Request to Twitter API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
30
+ statusCode: 408,
31
+ relatesToSystems: this.backendSystemCodes,
32
+ });
33
+ }
34
+ throw error;
35
+ }
10
36
  }
11
37
  cacheOptionsFor() {
12
38
  // 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,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,MAAM,OAAO,iBAAkB,SAAQ,0BAA0B;IAAjE;;QACE,YAAO,GAAG,6BAA6B,CAAA;QACvC,uBAAkB,GAAG,CAAC,SAAS,CAAC,CAAA;IAWlC,CAAC;IATC,KAAK,CAAC,QAAQ,CAAC,QAAgB;QAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,QAAQ,mBAAmB,CAAC,CAAA;IAC7D,CAAC;IAED,eAAe;QACb,iFAAiF;QACjF,oFAAoF;QACpF,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACtB,CAAC;CACF"}
1
+ {"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../src/datasources/twitter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AACzC,MAAM,OAAO,iBAAkB,SAAQ,0BAA0B;IAAjE;;QACE,YAAO,GAAG,6BAA6B,CAAA;QACvC,uBAAkB,GAAG,CAAC,SAAS,CAAC,CAAA;QAEhC,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IAoChE,CAAC;IAlCU,eAAe,CAAC,OAA+B;QACtD,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,IAAI;YACF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,eAAe,QAAQ,mBAAmB,CAAC,CAAA;YACxE,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;aACzB;YACD,OAAO,KAAK,CAAA;SACb;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE;gBACzD,MAAM,IAAI,oBAAoB,CAAC;oBAC7B,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,2CAA2C,eAAe,8BAA8B;oBACjG,UAAU,EAAE,GAAG;oBACf,gBAAgB,EAAE,IAAI,CAAC,kBAAkB;iBAC1C,CAAC,CAAA;aACH;YACD,MAAM,KAAK,CAAA;SACZ;IACH,CAAC;IAED,eAAe;QACb,iFAAiF;QACjF,oFAAoF;QACpF,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACtB,CAAC;CACF"}
@@ -6,8 +6,7 @@ export const baseCapiObject = {
6
6
  title: 'Kwarteng faces test persuading UK watchdog of fiscal plans',
7
7
  standfirst: '',
8
8
  byline: 'Chris Giles in London',
9
- // eslint-disable-next-line no-irregular-whitespace
10
- bodyXML: `<body><p>Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n\n<h2>Inline image</h2>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/95ee6478-5409-11ea-1627-dfb4377493f8\" data-embedded=\"true\"></ft-content>\n<h2>Image set (graphic)</h2>\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/76741096-0fe5-3a83-900d-ddac76dd4f23\" data-embedded=\"true\"></ft-content>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<h2>Image set (picture)</h2>\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/c7777155-9d5a-332b-8bc6-9c6106ebf63b\" data-embedded=\"true\"></ft-content>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"auto\" data-layout-width=\"full-grid\"><h3>Picture Comparison</h3>\n<div class=\"layout-slot\">\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/14d6fe88-adba-11e7-34df-3c309ec54a79\" data-embedded=\"true\"></ft-content>\n\n</div>\n<div class=\"layout-slot\">\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/e1238074-ad9c-11e7-1e10-9dd107dd87eb\" data-embedded=\"true\"></ft-content>\n\n</div>\n</div>\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"aside\" data-layout-width=\"inset-left\"><div class=\"layout-slot\"><h2><strong>CV</strong>\n</h2>\n<h3>gdfgf</h3>\n<h4><strong>dfsgdfg</strong>\n</h4>\n<p>dfgds</p>\n<h4><strong>dfgdsfg</strong>\n</h4>\n<p>dfgf</p>\n<h4><strong>dfgsdfg</strong>\n</h4>\n<p> dfgdg</p>\n<h4><strong>gdfgdfgdfg</strong>\n</h4>\n<p>dsfgdfgdsfgdg</p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"card\"><div class=\"layout-slot\"><h3>Background news</h3>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n</div>\n</div>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n<div class=\"layout\" data-layout-name=\"card\"><div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/29361148-7867-11e8-312e-8e97ca4a99ed\" data-embedded=\"true\"></ft-content>\n<h3>Background news with image</h3>\n<p>Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin.</p>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n\n<div class=\"layout\" data-layout-name=\"card\"><h3><strong>Layout -</strong> 1col img + text</h3>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/e793ddfc-74a5-11e8-24d4-d4279092fe94\" data-embedded=\"true\"></ft-content>\n</div>\n<div class=\"layout-slot\"><p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Lorem ipsum adipiscing elit. Sed feugiat turpis</p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"aside\"><h3><strong>Layout -</strong> 1col img + text (1st layout slot = wide)</h3>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/d1e0e398-8a7a-11e8-21f8-10eb0f34ee0a\" data-embedded=\"true\"></ft-content>\n</div>\n<div class=\"layout-slot\"><p><strong>Sed feugiat turpis:</strong> XX XX </p>\n<p><strong>Sed feugiat:</strong> XX</p>\n<p><strong>Sed feugiat turpis:</strong> XXX</p>\n<p><strong>Sed feugiat: </strong>XXXXXX</p>\n<p><strong>Sed feugiat turpis:</strong> XX XX </p>\n<p><strong>Sed feugiat:</strong> XX</p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"card\"><h2><strong>Layout -</strong> 2col text</h2>\n<div class=\"layout-slot\"><h4><strong>Lorem ipsum adipiscing elit</strong>\n</h4>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu</p>\n</div>\n<div class=\"layout-slot\"><h4><strong>Lorem ipsum adipiscing elit</strong>\n</h4>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum </p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"card\"><h2><strong>Layout -</strong> 2col img + text</h2>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/98b6d6bc-79f0-11e8-2233-c7402b6fd944\" data-embedded=\"true\"></ft-content>\n<h4><strong>Lorem ipsum adipiscing elit</strong>\n</h4>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum. Lorem ipsum adipiscing elit. </p>\n</div>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/3221c988-796f-11e8-312e-8e97ca4a99ed\" data-embedded=\"true\"></ft-content>\n<h4><strong>Lorem ipsum adipiscing elit</strong>\n</h4>\n<ul><li>Curabitu r accumsan elit luctus nunc condimentum</li>\n<li>Pellentesque a ante ac nisl porttitor placerat id ac arcu</li>\n<li>Phasellus sit amet felis quis velit sollicitudin</li>\n</ul>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"aside\"><h2><strong>Layout -</strong> 3col text</h2>\n<div class=\"layout-slot\"><h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu</p>\n</div>\n<div class=\"layout-slot\"><h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum </p>\n</div>\n<div class=\"layout-slot\"><h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum </p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"card\"><h2><strong>Layout -</strong> 3col img + text</h2>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/3341a0f0-7217-11e7-32c0-5127ddabb175\" data-embedded=\"true\"></ft-content>\n<h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n</div>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/36146196-7217-11e7-32c0-5127ddabb175\" data-embedded=\"true\"></ft-content>\n<h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n</div>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/3341a0f0-7217-11e7-32c0-5127ddabb175\" data-embedded=\"true\"></ft-content>\n<h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"card\" data-layout-width=\"full-grid\"><h2><strong>Layout -</strong> 3col img + text (full grid)</h2>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/48d7e260-7a2e-11e8-312e-8e97ca4a99ed\" data-embedded=\"true\"></ft-content>\n<h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. <strong>Phasellus sit amet felis quis </strong>\n</p>\n</div>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/70c71b8c-7b88-11e8-312e-8e97ca4a99ed\" data-embedded=\"true\"></ft-content>\n<h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n</div>\n<div class=\"layout-slot\"><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/9b199916-7b9a-11e8-312e-8e97ca4a99ed\" data-embedded=\"true\"></ft-content>\n<h5><strong>Adipiscing elit</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n</div>\n</div>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<div class=\"layout\" data-layout-name=\"timeline\">\n<h2><strong>Timeline</strong>\n</h2>\n<div class=\"layout-slot\">\n\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/14a21156-64bb-11e8-23b7-5b9feeabc365\" data-embedded=\"true\"></ft-content>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. </p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Pellentesque a ante ac nisl porttitor placerat id ac arcu.</p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi.</p>\n\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/cda2d504-6426-11e8-23b7-5b9feeabc365\" data-embedded=\"true\"></ft-content>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. </p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Pellentesque a ante ac nisl porttitor placerat id ac arcu.</p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi.</p>\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/9e5f53f0-5ce5-11e8-3521-1849e5369740\" data-embedded=\"true\"></ft-content>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi.</p>\n</div>\n</div>\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n\n<div class=\"layout-set\" data-layout-name=\"card\"><div class=\"layout\" data-layout-width=\"body\"><h2>Comparison Grid</h2>\n<div class=\"layout-slot\"><h3><strong>Ipsum 1</strong>\n</h3>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><h5><strong>Lorem a</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu.</p>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><h5><strong>Lorem b</strong>\n</h5>\n<p>Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n</div>\n</div>\n<div class=\"layout\" data-layout-width=\"body\"><div class=\"layout-slot\"><h3><strong>Ipsum 2</strong>\n</h3>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><h5><strong>Lorem a</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu.</p>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><h5><strong>Lorem b</strong>\n</h5>\n<p>Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n</div>\n</div>\n<div class=\"layout\" data-layout-width=\"body\"><div class=\"layout-slot\"><h3><strong>Ipsum 3</strong>\n</h3>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><h5><strong>Lorem a</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu.</p>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\"><h5><strong>Lorem b</strong>\n</h5>\n<p>Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n</div>\n</div>\n</div>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n<div class=\"layout-set\" data-layout-name=\"auto\" data-layout-width=\"auto\">\n<div class=\"layout\" data-layout-name=\"auto\" data-layout-width=\"auto\"><h3><strong>Responsive explainer -</strong> 2col img + text (2nd layout slot = wide)</h3>\n<div class=\"layout-slot\"><h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan</p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan</p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan</p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan</p>\n<h5><strong>XX Lorem</strong>\n</h5>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan</p>\n</div>\n<div class=\"layout-slot\" data-slot-width=\"wide\">\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/3ea92458-80cc-11e8-2233-c7402b6fd944\" data-embedded=\"true\"></ft-content>\n\n</div>\n</div>\n</div>\n<ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/f77c0299-8904-3d7d-a0a0-b2ab1fbd1a2d\" data-embedded=\"true\"></ft-content>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitur accumsan elit luctus nunc condimentum non gravida ipsum blandit. Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin pellentesque ac ut dui. Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Donec semper tellus id magna feugiat at eleifend urna volutpat.</p>\n\n<h2>Onsite video</h2>\n<ft-content type=\"http://www.ft.com/ontology/content/Video\" url=\"http://api.ft.com/content/65e41e62-551b-4027-aedb-173ed3dbbd21\" data-embedded=\"true\"></ft-content>\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<h2>YouTube video</h2>\n<p>\n<a data-asset-type=\"video\" data-embedded=\"true\" href=\"https://www.youtube.com/watch?v=pG9e4hSmLBc\"></a>\n</p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<pull-quote><pull-quote-text><p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis</p></pull-quote-text><pull-quote-source>placerat at non nisi</pull-quote-source></pull-quote>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<pull-quote><pull-quote-text><p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis</p></pull-quote-text><pull-quote-image><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/6aee0abe-7bb4-11e8-312e-8e97ca4a99ed\" data-embedded=\"true\"></ft-content></pull-quote-image><pull-quote-source>placerat at non nisi</pull-quote-source></pull-quote>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<big-number><big-number-headline><p>13.7</p></big-number-headline><big-number-intro><p>adipiscing elit</p></big-number-intro></big-number>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<recommended><recommended-title>Recommended</recommended-title><ul><li><ft-content type=\"http://www.ft.com/ontology/content/Article\" url=\"http://api.ft.com/content/0438835a-600a-11e8-9334-2218e7146b04\">Pearson puts London property up for sale</ft-content></li></ul></recommended>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<blockquote><p>Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi</p><cite>Blockus quotus </cite></blockquote>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<h1>Heading 1</h1>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<h2>Heading 2</h2>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<h3>Heading 3</h3>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<h4>Heading 4</h4>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<ul><li>Lorem ipsum adipiscing elit</li>\n<li>Sed feugiat turpis at massa tristique</li>\n<li>Curabitu r accumsan elit luctus</li>\n</ul>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n\n<ol><li>Lorem ipsum adipiscing elit</li>\n<li>Sed feugiat turpis at massa tristique</li>\n<li>Curabitu r accumsan elit luctus</li>\n</ol>\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<hr/>\n<p><strong>Divider and bold words.</strong> Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n\n<table class=\"data-table\"><caption>Table - Full-width 2-column</caption>\n<thead><tr><th><strong>lorem</strong>\n</th>\n<th><strong>ipsum</strong>\n</th>\n</tr>\n</thead>\n<tbody><tr><td>lorem</td>\n<td>ipsum</td>\n</tr>\n<tr><td>lorem</td>\n<td>ipsum</td>\n</tr>\n</tbody>\n</table>\n\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<table class=\"data-table\"><caption>Table - Full-width 3-column</caption>\n<thead><tr><th><strong>Lorem</strong>\n</th>\n<th><strong>Ipsum</strong>\n</th>\n<th><strong>Lorem</strong>\n</th>\n</tr>\n</thead>\n<tbody><tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n</tr>\n</tbody>\n</table>\n\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi. </p>\n<table class=\"data-table\"><caption>Table - Full-width 4-column</caption>\n<thead><tr><th><strong>Lorem</strong>\n</th>\n<th><strong>Ipsum</strong>\n</th>\n<th><strong>Lorem</strong>\n</th>\n<th><strong>Ipsum</strong>\n</th>\n</tr>\n</thead>\n<tbody><tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n<td>Ipsum</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n<td>Ipsum</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n<td>Ipsum</td>\n</tr>\n<tr><td>Lorem</td>\n<td>Ipsum</td>\n<td>Lorem</td>\n<td>Ipsum</td>\n</tr>\n</tbody>\n</table>\n\n\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi . </p>\n<p>Lorem ipsum adipiscing elit. Sed feugiat turpis at massa tristique sagittis. Curabitu r accumsan elit luctus nunc condimentum . Pellentesque a ante ac nisl porttitor placerat id ac arcu. Phasellus sit amet felis quis velit sollicitudin. Ut sit amet dolor vel risus imperdiet placerat at non nisi . </p>\n<p>Ut sit amet dolor vel risus imperdiet placerat at non nisi. Mauris eu nisl non libero semper ornare ut in neque. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n</p>\n</body>`,
9
+ bodyXML: `<body><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/c45f7393-62c8-4a6b-b53d-641725351851\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3V3cwPP\"><strong>Eliot After The Waste Land</strong></a><strong> (Eliot Biographies, 2)<br/></strong>by Robert Crawford, <em>Jonathan Cape £25</em></p><p>After a six-year wait, Robert Crawford follows up <em>Young Eliot</em> (2016) with his <a href=\"https://www.ft.com/content/210d7f37-115e-4197-b392-ffb094ed2699\">second (and final) volume</a> on the life of TS Eliot in time for the centenary of “The Waste Land”. Here, he explores Eliot’s marriage, religious life and draws on the 1,131 letters written to American friend Emily Hale that were only released from embargo in 2020 to probe the poet’s later years with tact and empathy.</p><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/bf48b0fc-ad4b-4ce9-a734-faf3e9aab225\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3UFKeul\"><strong>All the Knowledge in the World</strong></a><strong>: The Extraordinary History of the Encyclopaedia<br/></strong>by Simon Garfield, <em>Weidenfeld &amp; Nicolson £18.99</em></p><p>Simon Garfield, author of quirky histories on everything from fonts to maps, surveys the publishing phenomenon that is the encyclopedia. Tracing its origins from Ancient Greece right up to its modern incarnation in Wikipedia, his handsome book offers an erudite and amusing exploration of the human quest for knowledge.</p><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/cc74e2ec-3314-4b9f-81c1-bcfdf9dbaf99\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3VbW1AB\"><strong>The Waste Land</strong></a><strong>: A Biography of a Poem<br/></strong>by Matthew Hollis, <em>Faber £20</em></p><p>In the 100 years since TS Eliot penned his famous poem, it has taken on a life of its own. So it’s fitting, perhaps, that Matthew Hollis treats Eliot’s work to its own biography. This richly analytical book locates the poem’s genesis in the aftermath of the first world war and the “nightmare agony” of Eliot’s disastrous marriage.</p><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/7393690b-e02e-4cdb-a263-57e7d1a6eedf\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3OgAT9M\"><strong>Endless Flight</strong></a><strong>: The Life of Joseph Roth<br/></strong>by Keiron Pim, <em>Granta £25</em></p><p>“I paint the portrait of the age,” Joseph Roth once claimed. Certainly, his work as a journalist from 1917-1939, dissecting central and eastern Europe in more than a thousand essays and anticipating the collapse of democracy on the continent as well as 19 novels provides an exceptional anatomy of a tumultuous period of history. Keiron Pim’s <a href=\"https://www.ft.com/content/fee9056b-277e-41b4-9572-9fbac39f957b\">biography</a> goes some way to introducing the great Austro-Hungarian writer to a new age.</p><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/6e7b048f-d08f-43f7-b1fa-95e98c8ca811\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3hX8PfS\"><strong>Super-Infinite</strong></a><strong>: The Transformations of John Donne<br/></strong>by Katherine Rundell, <em>Faber £16.99</em></p><p>Katherine Rundell, a Fellow of All Souls College, Oxford, has produced a remarkable life of John Donne, the great poet of love, sex and death. Winner of this year’s Baillie Gifford Prize, her <a href=\"https://www.ft.com/content/c229396d-5448-44fa-b423-b018695d65d8\">biography</a> of the man who was at once soldier, poet, prisoner and priest is sensitive and witty, capturing the essence of a tricky subject. </p><experimental><div class=\"n-content-layout\" data-layout-name=\"card\" data-layout-width=\"true\"><div class=\"n-content-layout__container\"><h2>Books of the Year 2022</h2><div class=\"n-content-layout__slot\" data-slot-width=\"true\"><h4></h4><div class=\"n-content-layout__slot\" data-slot-width=\"true\"><img alt=\"\" data-copyright=\"\" data-image-type=\"image\" longdesc=\"\" src=\"https://d1e00ek4ebabms.cloudfront.net/production/43c41a77-ab10-40ed-a2a2-efa99ef32a10.jpg\"/></div></div><div class=\"n-content-layout__slot\" data-slot-width=\"true\"><h4></h4><div class=\"n-content-layout__slot\" data-slot-width=\"true\"><p>All this week, FT writers and critics share <a href=\"https://www.ft.com/content/2dd61d03-13ac-4278-8214-678c1d9a33c1\">their favourites</a>. Some highlights are: <br/><br/><strong>Monday:</strong> <a href=\"https://www.ft.com/content/416e1e1d-22e7-439b-9388-5d0cd56f5a9c\">Business</a> by Andrew Hill<br/><strong>Tuesday:</strong> <a href=\"https://www.ft.com/content/db8aaeb6-141f-4a7c-b364-2296fb0d8065\">Environment</a> by Pilita Clark<br/><strong>Wednesday:</strong> <a href=\"https://www.ft.com/content/634c1974-bc76-4f56-9548-274816dcc638\">Economics</a> by Martin Wolf<br/><strong>Thursday:</strong> Fiction by Laura Battle<br/><strong>Friday:</strong> Politics by Gideon Rachman<br/><strong>Saturday:</strong> Critics’ choice</p></div></div></div></div></experimental><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/fde6576e-ab0c-41ec-8e18-bcdb3d6c21c0\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3gn2kCl\"><strong>The Book of Phobias and Manias</strong></a><strong>: A History of the World in 99 Obsessions<br/></strong>by Kate Summerscale, <em>Profile £16.99</em></p><p>This neat compendium from the prizewinning author of <em>The Suspicions of Mr Whicher</em> charts a broad and intriguing range of fears and madness. Although these phobias and manias are alphabetised, Kate Summerscale suggests groupings (textures, animals, communal crazes) that — accompanied by a lightly erudite introduction — illuminate some of the darker corners of our collective psyche.</p><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/a07a6f3b-41cf-47c7-9b79-1c755ebfe54c\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3hX9oq0\"><strong>Papyrus</strong></a><strong>: The Invention of Books in the Ancient World<br/></strong>by Irene Vallejo, <em>Hodder &amp; Stoughton £25</em></p><p>Described as “a masterpiece” by Mario Vargas Llosa when it was first published in Spain in 2019, this bestselling phenomenon is now available in English. In it, Irene Vallejo recounts the birth of literary culture in the ancient world while interweaving dynamic, thrilling tales that underscore and celebrate the power of words to change the world.</p><ft-content type=\"http://www.ft.com/ontology/content/ImageSet\" url=\"http://api.ft.com/content/60b81603-654d-46ac-85ef-5865eb7bc1e8\" data-embedded=\"true\"></ft-content><p><a href=\"https://amzn.to/3EIwiKD\"><strong>Magnificent Rebels</strong></a><strong>: The First Romantics and the Invention of the Self</strong><br/>by Andrea Wulf, <em>John Murray £25/Knopf $35</em></p><p>Between 1794-1806, the cream of Germany’s intelligentsia descended on the tiny university town of Jena. There, Johann Wolfgang von Goethe mingled with philosophers Friedrich Schelling and Georg Wilhelm Friedrich Hegel, the scientist-explorer Alexander von Humboldt and the playwright Friedrich Schiller during the first flush of Romanticism. Andrea Wulf’s wonderful <a href=\"https://www.ft.com/content/391e2bcd-f14e-44e5-b6de-40990ce7272f\">book</a> brings to life the “Jena set” and a golden age of German culture. </p><experimental><div class=\"n-content-layout\" data-layout-name=\"card\" data-layout-width=\"fullWidth\"><div class=\"n-content-layout__container\"><h3>Tell us what you think</h3><div class=\"n-content-layout__slot\" data-slot-width=\"true\"><p>What are your favourites from this list — and what books have we missed? Tell us in the comments below</p></div></div></div></experimental><p><em>Join our online book group on Facebook at </em><a href=\"https://www.facebook.com/groups/139838140082304/\"><em>FT Books Café</em></a></p></body>`,
11
10
  annotations: [
12
11
  {
13
12
  apiUrl: 'http://api.ft.com/things/9bfe954c-e0ec-4716-ae91-24bd0f7860c9',
@@ -1 +1 @@
1
- {"version":3,"file":"capiObject.js","sourceRoot":"","sources":["../../src/fixtures/capiObject.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC7C,EAAE,EAAE,8DAA8D;IAClE,IAAI,EAAE,4CAA4C;IAClD,KAAK,EAAE,CAAC,4CAA4C,CAAC;IACrD,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,4DAA4D;IACnE,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,uBAAuB;IAC/B,mDAAmD;IACnD,OAAO,EAAE,ylhCAAylhC;IAClmhC,WAAW,EAAE;QACX;YACE,MAAM,EAAE,+DAA+D;YACvE,UAAU,EAAE,kCAAkC;YAC9C,EAAE,EAAE,+DAA+D;YACnE,SAAS,EAAE,6CAA6C;YACxD,SAAS,EAAE,qBAAqB;YAChC,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,uCAAuC;gBACvC,4CAA4C;gBAC5C,kCAAkC;aACnC;SACF;QACD;YACE,MAAM,EAAE,+DAA+D;YACvE,UAAU,EAAE,kCAAkC;YAC9C,EAAE,EAAE,+DAA+D;YACnE,SAAS,EAAE,0CAA0C;YACrD,SAAS,EAAE,sBAAsB;YACjC,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,uCAAuC;gBACvC,4CAA4C;gBAC5C,kCAAkC;aACnC;SACF;QACD;YACE,EAAE,EAAE,+DAA+D;YACnE,SAAS,EAAE,iDAAiD;YAC5D,SAAS,EAAE,aAAa;YACxB,MAAM,EAAE,EAAE;YACV,UAAU,EAAE,EAAE;YACd,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,EAAE;SACV;KACF;IACD,SAAS,EAAE;QACT,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,0CAAmD;QACzD,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,EAAE;QACf,SAAS,EAAE,uCAAuC;KACnD;IACD,UAAU,EAAE,EAAE;IACd,WAAW,EAAE,SAAkB;IAC/B,MAAM,EAAE,EAAE;IACV,aAAa,EAAE,EAAE;IACjB,kBAAkB,EAAE,EAAE;CACvB,CAAA"}
1
+ {"version":3,"file":"capiObject.js","sourceRoot":"","sources":["../../src/fixtures/capiObject.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC7C,EAAE,EAAE,8DAA8D;IAClE,IAAI,EAAE,4CAA4C;IAClD,KAAK,EAAE,CAAC,4CAA4C,CAAC;IACrD,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,4DAA4D;IACnE,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,uBAAuB;IAC/B,OAAO,EAAE,ggQAAggQ;IACzgQ,WAAW,EAAE;QACX;YACE,MAAM,EAAE,+DAA+D;YACvE,UAAU,EAAE,kCAAkC;YAC9C,EAAE,EAAE,+DAA+D;YACnE,SAAS,EAAE,6CAA6C;YACxD,SAAS,EAAE,qBAAqB;YAChC,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,uCAAuC;gBACvC,4CAA4C;gBAC5C,kCAAkC;aACnC;SACF;QACD;YACE,MAAM,EAAE,+DAA+D;YACvE,UAAU,EAAE,kCAAkC;YAC9C,EAAE,EAAE,+DAA+D;YACnE,SAAS,EAAE,0CAA0C;YACrD,SAAS,EAAE,sBAAsB;YACjC,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,uCAAuC;gBACvC,4CAA4C;gBAC5C,kCAAkC;aACnC;SACF;QACD;YACE,EAAE,EAAE,+DAA+D;YACnE,SAAS,EAAE,iDAAiD;YAC5D,SAAS,EAAE,aAAa;YACxB,MAAM,EAAE,EAAE;YACV,UAAU,EAAE,EAAE;YACd,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,EAAE;SACV;KACF;IACD,SAAS,EAAE;QACT,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,0CAAmD;QACzD,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,EAAE;QACf,SAAS,EAAE,uCAAuC;KACnD;IACD,UAAU,EAAE,EAAE;IACd,WAAW,EAAE,SAAkB;IAC/B,MAAM,EAAE,EAAE;IACV,aAAa,EAAE,EAAE;IACjB,kBAAkB,EAAE,EAAE;CACvB,CAAA"}