diff --git a/.eslintrc.js b/.eslintrc.js index c4271481e5e..2a844b317a4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,12 +57,6 @@ module.exports = { 'linebreak-style': process.platform === 'win32' ? 'off' : ['error', 'unix'], 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', - 'import/no-import-module-exports': [ - 'error', - { - exceptions: ['**/*/startServer.js'], - }, - ], 'import/no-extraneous-dependencies': [ 'off', { @@ -100,14 +94,6 @@ module.exports = { // adds support for type, interface and enum declarations https://typescript-eslint.io/rules/no-use-before-define/#how-to-use 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': ['error'], - '@typescript-eslint/no-unused-vars': [ - 'warn', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], 'react/require-default-props': 'off', 'react/no-unused-prop-types': 'off', }, diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6cafa9a3cb5..3484582ea46 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,11 +10,10 @@ updates: - dependency-name: 'webpack' update-types: ['version-update:semver-major'] - dependency-name: 'winston' - # https://jira.dev.bbc.co.uk/browse/NEWSWORLDSERVICE-2185: Latest version of eslint has breaking changes - dependency-name: eslint update-types: ['version-update:semver-major'] - - dependency-name: '@typescript-eslint/eslint-plugin' - update-types: ['version-update:semver-major'] + # Latest version of Emotion contains a memory leak that needs resolved: https://github.com/emotion-js/emotion/issues/3221 + - dependency-name: '@emotion/*' # Opera Mini unsupported packages - dependency-name: 'uuid' # https://github.com/bbc/simorgh/pull/11840 - dependency-name: '@types/uuid' # https://github.com/bbc/simorgh/pull/11840 @@ -30,9 +29,6 @@ updates: patterns: - '@babel/*' - 'babel-*' - bbc: - patterns: - - '@bbc/*' emotion: patterns: - '@emotion/*' @@ -41,21 +37,17 @@ updates: - '@next/*' - 'next' - 'next-*' - - '@img-sharp-*' - loadable-minor-patch: - patterns: - - '@loadable/*' - react: - patterns: - - 'react' - - 'react-dom' - - '@types/react' - - '@types/react-dom' storybook: patterns: - '@storybook/*' - 'storybook' - '@esbuild/*' + bbc: + patterns: + - '@bbc/*' + loadable-minor-patch: + patterns: + - '@loadable/*' webpack-minor-patch: patterns: - 'webpack' diff --git a/.yarn/cache/@esbuild-darwin-arm64-npm-0.24.0-f33b2ff14e-10.zip b/.yarn/cache/@esbuild-darwin-arm64-npm-0.24.0-f33b2ff14e-10.zip new file mode 100644 index 00000000000..c85bd140940 Binary files /dev/null and b/.yarn/cache/@esbuild-darwin-arm64-npm-0.24.0-f33b2ff14e-10.zip differ diff --git a/.yarn/cache/@esbuild-linux-x64-npm-0.24.0-744e76a7ed-10.zip b/.yarn/cache/@esbuild-linux-x64-npm-0.24.0-744e76a7ed-10.zip deleted file mode 100644 index cbb5afdf05f..00000000000 Binary files a/.yarn/cache/@esbuild-linux-x64-npm-0.24.0-744e76a7ed-10.zip and /dev/null differ diff --git a/.yarn/cache/@img-sharp-darwin-arm64-npm-0.33.5-c319591c53-10.zip b/.yarn/cache/@img-sharp-darwin-arm64-npm-0.33.5-c319591c53-10.zip new file mode 100644 index 00000000000..2528a01baac Binary files /dev/null and b/.yarn/cache/@img-sharp-darwin-arm64-npm-0.33.5-c319591c53-10.zip differ diff --git a/.yarn/cache/@img-sharp-libvips-darwin-arm64-npm-1.0.4-d0d063884a-10.zip b/.yarn/cache/@img-sharp-libvips-darwin-arm64-npm-1.0.4-d0d063884a-10.zip new file mode 100644 index 00000000000..6b3340399d0 Binary files /dev/null and b/.yarn/cache/@img-sharp-libvips-darwin-arm64-npm-1.0.4-d0d063884a-10.zip differ diff --git a/.yarn/cache/@img-sharp-libvips-linux-x64-npm-1.0.4-0974f077b7-10.zip b/.yarn/cache/@img-sharp-libvips-linux-x64-npm-1.0.4-0974f077b7-10.zip deleted file mode 100644 index a4a5824aa57..00000000000 Binary files a/.yarn/cache/@img-sharp-libvips-linux-x64-npm-1.0.4-0974f077b7-10.zip and /dev/null differ diff --git a/.yarn/cache/@img-sharp-linux-x64-npm-0.33.5-1b6c430eb4-10.zip b/.yarn/cache/@img-sharp-linux-x64-npm-0.33.5-1b6c430eb4-10.zip deleted file mode 100644 index fd9356ce5d3..00000000000 Binary files a/.yarn/cache/@img-sharp-linux-x64-npm-0.33.5-1b6c430eb4-10.zip and /dev/null differ diff --git a/.yarn/cache/fsevents-patch-6b67494872-10.zip b/.yarn/cache/fsevents-patch-6b67494872-10.zip new file mode 100644 index 00000000000..9887ada72d9 Binary files /dev/null and b/.yarn/cache/fsevents-patch-6b67494872-10.zip differ diff --git a/.yarnrc.yml b/.yarnrc.yml index 00d159efa20..28e1e0bb469 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -16,5 +16,3 @@ yarnPath: .yarn/releases/yarn-4.1.1.cjs npmAuditExcludePackages: [] npmAuditIgnoreAdvisories: [] - -defaultSemverRangePrefix: "" diff --git a/cypress/e2e/pages/topicPage/tests.js b/cypress/e2e/pages/topicPage/tests.js index 8deb172d4d0..982609c6b78 100644 --- a/cypress/e2e/pages/topicPage/tests.js +++ b/cypress/e2e/pages/topicPage/tests.js @@ -2,6 +2,7 @@ import idSanitiser from '../../../../src/app/lib/utilities/idSanitiser'; export default ({ service, pageType, variant, currentPath }) => { let topicId; + let variantTopicId; let topicTitle; let firstItemHeadline; let pageCount; @@ -34,6 +35,7 @@ export default ({ service, pageType, variant, currentPath }) => { cy.getPageDataFromWindow().then(data => { const { pageData } = data; topicTitle = pageData.title; + variantTopicId = pageData.scriptSwitchId; pageCount = pageData.pageCount; numberOfItems = pageData.curations?.[0]?.summaries.length; firstItemHeadline = pageData.curations?.[0]?.summaries?.[0]?.title; @@ -269,7 +271,7 @@ export default ({ service, pageType, variant, currentPath }) => { // URL contains correct variant after click cy.url().should('contain', otherVariant); // URL contains the correct topic ID - cy.url().should('contain', topicId); + cy.url().should('contain', variantTopicId); // clicks script switch cy.get(`[data-variant="${variant}"]`).click(); // URL contains correct variant after click diff --git a/cypress/support/config/settings.js b/cypress/support/config/settings.js index c76edfb653c..465426966fa 100644 --- a/cypress/support/config/settings.js +++ b/cypress/support/config/settings.js @@ -280,7 +280,7 @@ module.exports = () => ({ test: { paths: [ '/afrique/region-23278969', // CPS MAP - '/afrique/nos_emissions/2016/06/160622_tc2_testmap1?renderer_env=test', // TC2 MAP + '/afrique/nos_emissions/2016/06/160622_tc2_testmap1', // TC2 MAP ], enabled: true, }, @@ -663,7 +663,7 @@ module.exports = () => ({ test: { paths: [ '/arabic/world-23278971', // CPS audio - '/arabic/worldnews/2015/11/151120_t_arabic_av?renderer_env=test', // TC2 video + '/arabic/worldnews/2015/11/151120_t_arabic_av', // TC2 video ], enabled: true, }, @@ -1831,7 +1831,7 @@ module.exports = () => ({ test: { paths: [ '/hausa/23269030', // CPS MAP with video clip - '/hausa/multimedia/2016/07/160714_tc2_audiomap?renderer_env=test', // TC2 MAP with audio clip + '/hausa/multimedia/2016/07/160714_tc2_audiomap', // TC2 MAP with audio clip ], enabled: true, }, @@ -2042,7 +2042,7 @@ module.exports = () => ({ test: { paths: [ '/hindi/23201477', // CPS video - '/hindi/sport/2016/08/160822_tc2_testmap1?renderer_env=test', // TC2 video + '/hindi/sport/2016/08/160822_tc2_testmap1', // TC2 video ], enabled: true, }, @@ -3845,7 +3845,7 @@ module.exports = () => ({ test: { paths: [ '/pashto/media-23257523', // CPS MAP with video clip - '/pashto/world/2016/09/160921_tc2_testmap1?renderer_env=test', // TC2 MAP with video clip + '/pashto/world/2016/09/160921_tc2_testmap1', // TC2 MAP with video clip ], enabled: false, }, @@ -3953,7 +3953,7 @@ module.exports = () => ({ // '/pashto', // Front Page '/pashto/bbc_pashto_radio/liveradio', // Live Radio // '/pashto/media-23257523', // CPS MAP - '/pashto/world/2016/09/160921_tc2_testmap1?renderer_env=test', // TC2 MAP + '/pashto/world/2016/09/160921_tc2_testmap1', // TC2 MAP '/pashto/23289748', // CPS STY '/pashto/23092924', // CPS PGL '/pashto/bbc_pashto_radio/programmes/p0340yr4', // On Demand Radio Brand @@ -4013,15 +4013,24 @@ module.exports = () => ({ liveRadio: { environments: { live: { - paths: ['/persian/bbc_dari_radio/liveradio'], + paths: [ + '/persian/bbc_persian_radio/liveradio', + '/persian/bbc_dari_radio/liveradio', + ], enabled: true, }, test: { - paths: ['/persian/bbc_dari_radio/liveradio?renderer_env=live'], + paths: [ + '/persian/bbc_persian_radio/liveradio?renderer_env=live', + '/persian/bbc_dari_radio/liveradio?renderer_env=live', + ], enabled: true, }, local: { - paths: ['/persian/bbc_dari_radio/liveradio'], + paths: [ + '/persian/bbc_persian_radio/liveradio', + '/persian/bbc_dari_radio/liveradio', + ], enabled: true, }, }, @@ -4032,7 +4041,9 @@ module.exports = () => ({ live: { paths: [ '/persian/bbc_dari_radio/programmes/p0340v0s', // On Demand Brand Dari + '/persian/bbc_persian_radio/programmes/p0340vyx', // On Demand Brand Persian '/persian/bbc_dari_radio/w172y2n5p9pfj6x', // On Demand Episode Dari + '/persian/bbc_persian_radio/w3ct2cv6', // On Demand Episode Persian '/persian/podcasts/p02pc9wf', // Podcast Brand '/persian/podcasts/p02pc9wf/p09knl1v', // Podcast Episode ], @@ -4041,7 +4052,9 @@ module.exports = () => ({ test: { paths: [ '/persian/bbc_dari_radio/programmes/p0364sj5', // On Demand Brand Dari + '/persian/bbc_persian_radio/programmes/p0340vyw', // On Demand Brand Persian '/persian/bbc_dari_radio/w172y2n5p9pfj6x', // On Demand Episode Dari + '/persian/bbc_persian_radio/w3ct2cv6', // On Demand Episode Persian '/persian/podcasts/p02pc9wf', // Podcast Brand '/persian/podcasts/p02pc9wf/p09knl1v', // Podcast Episode ], @@ -4049,6 +4062,7 @@ module.exports = () => ({ }, local: { paths: [ + '/persian/bbc_persian_radio/w172x32355t5635', '/persian/bbc_dari_radio/w3csz7mf', '/persian/podcasts/p02pc9wf', // Podcast Brand '/persian/podcasts/p02pc9wf/p095lyj1', // Podcast Episode @@ -4097,7 +4111,7 @@ module.exports = () => ({ test: { paths: [ '/persian/iran-23231114', // CPS MAP with audio clip - '/persian/iran/2016/09/160907_tc2_testmap1?renderer_env=test', // TC2 MAP with video clip + '/persian/iran/2016/09/160907_tc2_testmap1', // TC2 MAP with video clip ], enabled: true, }, @@ -4192,9 +4206,12 @@ module.exports = () => ({ // '/persian/world-51497110', // CPS MAP // '/persian/media-49522521', // CPS MAP with live stream // '/persian/world/2016/06/160613_om_naked_dining', // TC2 MAP + // '/persian/bbc_persian_radio/liveradio', // Live Radio // '/persian/bbc_dari_radio/liveradio', // Live Radio // '/persian/bbc_dari_radio/programmes/p0340v0s', // On Demand Radio Brand + // '/persian/bbc_persian_radio/programmes/p0340vyx', // On Demand Radio Brand // '/persian/bbc_dari_radio/w3ct0bst', // On Demand Radio Episode + // '/persian/bbc_persian_radio/w3ct0s49', // On Demand Radio Episode ], enabled: false, }, @@ -4204,11 +4221,14 @@ module.exports = () => ({ // '/persian', // Front Page // '/persian/popular/read', // Most Read // '/persian/iran-23231114', // CPS MAP - // '/persian/iran/2016/09/160907_tc2_testmap1?renderer_env=test', // TC2 MAP + // '/persian/iran/2016/09/160907_tc2_testmap1', // TC2 MAP // '/persian/23104784', // CPS PGL + // '/persian/bbc_persian_radio/liveradio', // Live Radio // '/persian/bbc_dari_radio/liveradio', // Live Radio // '/persian/bbc_dari_radio/programmes/p0340v0s', // On Demand Radio Brand + // '/persian/bbc_persian_radio/programmes/p0340vyx', // On Demand Radio Brand // '/persian/bbc_dari_radio/w3ct0bst', // On Demand Radio Episode + // '/persian/bbc_persian_radio/w3ct0s49', // On Demand Radio Episode ], enabled: false, }, @@ -4221,9 +4241,11 @@ module.exports = () => ({ // '/persian/iran/2016/09/160907_tc2_testmap1', // TC2 MAP // '/persian/magazine-49281981', // CPS PGL // '/persian/arts-52166891', // CPS STY + // '/persian/bbc_persian_radio/liveradio', // Live Radio // '/persian/bbc_dari_radio/liveradio', // Live Radio // // '', // On Demand Radio Brand // // '', // On Demand Radio Brand + // '/persian/bbc_persian_radio/w172x32355t5635', // On Demand Radio Episode // '/persian/bbc_dari_radio/w3csz7mf', // On Demand Radio Episode ], enabled: false, @@ -4756,7 +4778,7 @@ module.exports = () => ({ test: { paths: [ '/russian/av/media-23320267', // CPS video with redirect - '/russian/news/2016/05/160510_tc2_testmap3?renderer_env=test', // TC2 video + '/russian/news/2016/05/160510_tc2_testmap3', // TC2 video ], enabled: true, }, @@ -5718,7 +5740,7 @@ module.exports = () => ({ test: { paths: [ '/swahili/media-23268999', // CPS MAP with live stream - '/swahili/michezo/2016/07/160713_tc2_testmap2?renderer_env=test', // TC2 MAP with audio clip + '/swahili/michezo/2016/07/160713_tc2_testmap2', // TC2 MAP with audio clip ], enabled: true, }, @@ -7996,7 +8018,7 @@ module.exports = () => ({ test: { paths: [ '/zhongwen/simp/uk-23283128', // CPS Audio - '/zhongwen/simp/multimedia/2016/11/161107_tc2_testmap1?renderer_env=test', // TC2 Video + '/zhongwen/simp/multimedia/2016/11/161107_tc2_testmap1', // TC2 Video ], enabled: true, }, @@ -8173,7 +8195,7 @@ module.exports = () => ({ test: { paths: [ '/zhongwen/trad/uk-23283128', // CPS Audio - '/zhongwen/trad/multimedia/2016/11/161107_tc2_testmap1?renderer_env=test', // TC2 Video + '/zhongwen/trad/multimedia/2016/11/161107_tc2_testmap1', // TC2 Video ], enabled: true, }, diff --git a/cypress/support/helpers/ampOnlyServices.js b/cypress/support/helpers/ampOnlyServices.js index bdcce005d1c..df8612f50e1 100644 --- a/cypress/support/helpers/ampOnlyServices.js +++ b/cypress/support/helpers/ampOnlyServices.js @@ -1,2 +1,2 @@ const ampOnlyServices = ['news', 'sport', 'newsround']; -export default ampOnlyServices; +export { ampOnlyServices as default }; diff --git a/data/arabic/live/67574192.json b/data/arabic/live/67574192.json index aa1dfcc8ab9..0ff34f8d6e8 100644 --- a/data/arabic/live/67574192.json +++ b/data/arabic/live/67574192.json @@ -32,7 +32,7 @@ "sportDataEvent": { "id": null }, "eavisEvent": { "id": null }, "article": { "id": null }, - "mediaCollections": null, + "mediaCollections": [], "keyHighlight": { "id": null }, "electionBanner": { "id": null }, "supportingLinks": { "id": null }, diff --git a/data/arabic/live/cvp5r6m6mgpt.json b/data/arabic/live/cvp5r6m6mgpt.json deleted file mode 100644 index 8d25396f915..00000000000 --- a/data/arabic/live/cvp5r6m6mgpt.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "data": { - "title": "نتائج الانتخابات الأمريكية: احصل على آخر التحديثات", - "description": "البث المباشر: حفل تنصيب الرئيس الخامس والأربعين للولايات المتحدة", - "language": "ar", - "headerImage": null, - "promoImage": null, - "home": "arabic", - "section": null, - "commercialInfo": { - "adCategory": null, - "adSubCategory": null, - "hasAdvertising": false - }, - "sportDataEvent": { - "id": null - }, - "eavisEvent": { - "id": null - }, - "article": { - "id": null - }, - "mediaCollections": [ - { - "type": "liveMedia", - "model": { - "urn": "urn:bbc:pips:sid:bbc_arabic_tv", - "title": "BBC Arabic TV", - "type": "episode", - "synopses": { - "short": "جولة إخبارية يومية تتناول أهم الأحداث العربية والعالمية في تقارير ولقاءات وتحليلات ", - "medium": "جولة إخبارية يومية تتناول أهم الأحداث العربية والعالمية في تقارير ولقاءات وتحليلات ", - "long": "جولة إخبارية يومية تتناول أهم الأحداث العربية والعالمية في تقارير ولقاءات وتحليلات " - }, - "mediaType": "audio_video", - "imageUrlTemplate": "https://ichef.bbci.co.uk/images/ic/$recipe/p08b23t4.png", - "masterbrand": { - "id": "bbc_arabic_tv", - "name": "تلفزيون بي بي سي عربي", - "networkName": "تلفزيون بي بي سي عربي", - "type": "tv", - "imageUrlTemplate": "ichef.bbci.co.uk/images/ic/$recipe/p08b23t4.png" - }, - "version": { - "vpid": "n4pdm3cdh4", - "duration": "PT1H", - "availabilityType": "simulcast", - "versionTypes": [ - { - "type": "Original", - "name": "Original version" - } - ], - "schedule": null, - "serviceId": "bbc_arabic_tv", - "authToken": null, - "status": "LIVE", - "warnings": null - }, - "leadMedia": true - } - } - ], - "keyHighlight": { - "id": null - }, - "electionBanner": { - "id": null - }, - "supportingLinks": { - "id": null - }, - "riddle": { - "id": null - }, - "seo": { - "seoTitle": "نتائج الانتخابات الأمريكية: احصل على آخر التحديثات", - "seoDescription": "البث المباشر: حفل تنصيب الرئيس الخامس والأربعين للولايات المتحدة", - "datePublished": "2024-12-20T11:25:00.000Z", - "dateModified": "2024-12-20T11:25:00.000Z" - }, - "endDateTime": "2025-01-10T17:00:00.000Z", - "startDateTime": "2024-12-20T11:25:00.000Z", - "firstPublishedDateTime": "2024-12-20T11:26:28.576Z", - "type": "live-coverage", - "dataSource": "TIPO", - "isLive": false, - "summaryPoints": { - "id": null, - "content": null - }, - "liveTextStream": { - "id": "39A9B954BC394441ACFDC0A2E6CAD80F", - "contributors": null, - "content": { - "data": { - "results": [ - { - "typeCode": null, - "header": { - "model": { - "blocks": [ - { - "id": "f4dc5312", - "type": "headline", - "model": { - "blocks": [ - { - "id": "d9a41071", - "type": "text", - "model": { - "blocks": [ - { - "id": "df5cbb32", - "type": "paragraph", - "model": { - "text": "انشر عنوانًا واحدًا", - "blocks": [ - { - "id": "3dea2ef4", - "type": "fragment", - "model": { - "text": "انشر عنوانًا واحدًا", - "attributes": [] - } - } - ] - } - } - ] - } - } - ] - } - }, - { - "id": "eff4c25c", - "type": "subheadline", - "model": { - "blocks": [ - { - "id": "e0fb639f", - "type": "text", - "model": { - "blocks": [ - { - "id": "2bb7258d", - "type": "paragraph", - "model": { - "text": "أضف عنوان فرعي واحد", - "blocks": [ - { - "id": "d4c63f8a", - "type": "fragment", - "model": { - "text": "أضف عنوان فرعي واحد", - "attributes": [] - } - } - ] - } - } - ] - } - } - ] - } - } - ] - } - }, - "content": { - "model": { - "blocks": [ - { - "id": "047416cb", - "type": "paragraph", - "model": { - "text": "انشر نص واحد هنا", - "blocks": [ - { - "id": "43167322", - "type": "fragment", - "model": { - "text": "انشر نص واحد هنا", - "attributes": [] - } - } - ] - } - }, - { - "id": "f511af36", - "type": "paragraph", - "model": { - "text": "", - "blocks": [] - } - }, - { - "id": "47e47ec1", - "type": "paragraph", - "model": { - "text": "انشر نص واحد هنا", - "blocks": [ - { - "id": "c3f2553c", - "type": "fragment", - "model": { - "text": "انشر نص واحد هنا", - "attributes": ["bold"] - } - } - ] - } - }, - { - "id": "a0b92a9d", - "type": "paragraph", - "model": { - "text": "", - "blocks": [] - } - }, - { - "id": "d12a64cf", - "type": "paragraph", - "model": { - "text": "انشر نص واحد هنا", - "blocks": [ - { - "id": "31e6edf3", - "type": "fragment", - "model": { - "text": "انشر نص واحد هنا", - "attributes": ["italic"] - } - } - ] - } - }, - { - "id": "e70c8ab7", - "type": "paragraph", - "model": { - "text": "", - "blocks": [] - } - }, - { - "id": "b051f499", - "type": "paragraph", - "model": { - "text": "", - "blocks": [] - } - }, - { - "id": "54b1f147", - "type": "paragraph", - "model": { - "text": "", - "blocks": [] - } - } - ] - } - }, - "link": null, - "urn": "asset:09f3b54d-fbe4-4bfd-a19e-263bc0f26633", - "type": "POST", - "options": { - "isBreakingNews": false - }, - "dates": { - "firstPublished": "2023-03-22T11:34:11.000Z", - "lastPublished": "2023-03-22T11:34:11.000Z", - "time": null, - "curated": "2023-03-22T11:34:12.537Z" - }, - "titles": [ - { - "title": null, - "source": "primary" - } - ], - "descriptions": [ - { - "text": null, - "source": "summary" - } - ], - "images": [ - { - "originalUrl": null, - "altText": null, - "copyright": null, - "urlTemplate": null, - "url": null - } - ] - } - ], - "page": { - "index": 1, - "total": 1 - } - } - } - }, - "metadata": { - "atiAnalytics": { - "contentId": "urn:bbc:tipo:topic:cvp5r6m6mgpt", - "contentType": "live-coverage", - "pageIdentifier": "live_coverage.cvp5r6m6mgpt.page", - "pageTitle": "نتائج الانتخابات الأمريكية: احصل على آخر التحديثات", - "timePublished": "2024-12-20T11:25:00.000Z", - "timeUpdated": "2024-12-20T11:25:00.000Z" - } - } - }, - "contentType": "application/json; charset=utf-8" -} diff --git a/data/arabic/topics/cng9qem66p5t.json b/data/arabic/topics/cng9qem66p5t.json index a1a43314a22..ca2e37cc9f7 100644 --- a/data/arabic/topics/cng9qem66p5t.json +++ b/data/arabic/topics/cng9qem66p5t.json @@ -124,7 +124,8 @@ } ], "activePage": 1, - "pageCount": 1 + "pageCount": 1, + "variantTopicId": null }, "contentType": "application/json; charset=utf-8" } diff --git a/data/kyrgyz/topics/cvpv9djp9qqt.json b/data/kyrgyz/topics/cvpv9djp9qqt.json index 42387360c34..a9dd953aeae 100644 --- a/data/kyrgyz/topics/cvpv9djp9qqt.json +++ b/data/kyrgyz/topics/cvpv9djp9qqt.json @@ -597,6 +597,7 @@ ], "activePage": 1, "pageCount": 1, + "variantTopicId": null, "metadata": { "analytics": { "name": "kyrgyz.topics.cvpv9djp9qqt.page", diff --git a/data/mundo/live/c7dkx155e626t.json b/data/mundo/live/c7dkx155e626t.json deleted file mode 100644 index 49296d71fcb..00000000000 --- a/data/mundo/live/c7dkx155e626t.json +++ /dev/null @@ -1,465 +0,0 @@ -{ - "data": { - "title": "USA Elections Aftermath: Get the latest updates", - "description": "Live Streaming: The Inauguration of the 45th President of the United States", - "language": "es", - "headerImage": { - "url": "https://ichef.bbci.co.uk/ace/standard/480/cpsdevpb/ed77/test/98a4c130-b304-11ef-acb8-cfaabd95075c.jpg", - "urlTemplate": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsdevpb/ed77/test/98a4c130-b304-11ef-acb8-cfaabd95075c.jpg", - "height": 549, - "width": 976, - "altText": "test", - "copyright": "BBC" - }, - "promoImage": { - "url": "https://ichef.bbci.co.uk/ace/standard/480/cpsdevpb/8edf/test/a9379d10-b304-11ef-acb8-cfaabd95075c.jpg", - "urlTemplate": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsdevpb/8edf/test/a9379d10-b304-11ef-acb8-cfaabd95075c.jpg", - "height": 549, - "width": 976, - "altText": "test", - "copyright": "BBC" - }, - "home": "mundo", - "section": null, - "commercialInfo": { - "adCategory": null, - "adSubCategory": null, - "hasAdvertising": true - }, - "sportDataEvent": { - "id": null - }, - "eavisEvent": { - "id": null - }, - "article": { - "id": null - }, - "mediaCollections": [ - { - "type": "liveMedia", - "model": { - "urn": "urn:bbc:pips:pid:p0gh4n63", - "title": "Non-Stop Cartoons!", - "type": "episode", - "synopses": { - "short": "Toon in, kick back and relax to 100% cartoons!", - "medium": "Toon in, kick back and relax. From laugh out loud to mischief and mayhem. 100% cartoons all day long.", - "long": "Toon in, kick back and relax. From laugh out loud to mischief and mayhem. 100% cartoons all day long. Join your favourites Grizzy, Shaun, Taffy, Boy Girl Dog Cat Mouse Cheese, The Deep and those Monster Loving Maniacs." - }, - "mediaType": "audio_video", - "imageUrlTemplate": "https://ichef.bbci.co.uk/images/ic/$recipe/p0k31t4d.jpg", - "masterbrand": { - "id": "cbbc", - "name": "CBBC", - "networkName": "CBBC", - "type": "tv", - "imageUrlTemplate": "ichef.bbci.co.uk/images/ic/$recipe/p0f8qps2.jpg" - }, - "version": { - "vpid": "p0gh4n67", - "duration": "PT24H", - "availabilityType": "webcast", - "versionTypes": [ - { - "type": "Original", - "name": "Original version" - } - ], - "schedule": { - "start": "2024-12-19T07:00:20Z", - "accurateStart": "2024-12-19T07:00:20Z", - "end": "2024-12-19T12:00:20Z" - }, - "serviceId": null, - "authToken": null, - "status": "LIVE", - "warnings": { - "warning_text": "Contains some upsetting scenes.", - "warning": [ - { - "warning_code": "L1", - "short_description": "Some upsetting scenes" - } - ] - } - }, - "leadMedia": true - } - }, - { - "type": "liveMedia", - "model": { - "urn": "urn:bbc:pips:pid:l0056wdq", - "title": "Starmer Scrutinised By MPs", - "type": "episode", - "synopses": { - "short": "Keir Starmer facing questions in his first appearance before the Liaison Committee", - "medium": "", - "long": "" - }, - "mediaType": "audio_video", - "imageUrlTemplate": "https://ichef.bbci.co.uk/images/ic/$recipe/p0kd22h2.jpg", - "masterbrand": { - "id": "bbc_news", - "name": "BBC News", - "networkName": "BBC News", - "type": "", - "imageUrlTemplate": "ichef.bbci.co.uk/images/ic/$recipe/p0bvs4dq.jpg" - }, - "version": { - "vpid": "l0056wdr", - "duration": "PT23H56M13S", - "availabilityType": "webcast", - "versionTypes": [ - { - "type": "Original", - "name": "Original version" - } - ], - "schedule": { - "start": "2024-12-19T14:14:48Z", - "accurateStart": "2024-12-19T14:17:35Z", - "end": "2024-12-20T14:13:48Z" - }, - "serviceId": null, - "authToken": null, - "status": "LIVE", - "warnings": null - }, - "leadMedia": true - } - } - ], - "keyHighlight": { - "id": null - }, - "electionBanner": { - "id": null - }, - "supportingLinks": { - "id": null - }, - "riddle": { - "id": null - }, - "seo": { - "seoTitle": "USA Elections Aftermath: Get the latest updates", - "seoDescription": "Live Streaming: The Inauguration of the 45th President of the United States", - "datePublished": "2024-12-05T11:26:00.000Z", - "dateModified": "2024-12-05T12:23:32.000Z" - }, - "endDateTime": "2025-12-06T11:25:00.000Z", - "startDateTime": "2024-12-05T11:26:00.000Z", - "firstPublishedDateTime": "2024-12-05T11:51:25.301Z", - "type": "live-coverage", - "dataSource": "TIPO", - "isLive": true, - "summaryPoints": { - "id": "urn:bbc:optimo:asset:c12x81qd94vo", - "content": { - "model": { - "blocks": [ - { - "id": "982be8ac", - "type": "text", - "model": { - "blocks": [ - { - "id": "8596bac4", - "type": "unorderedList", - "model": { - "blocks": [ - { - "id": "9b4a62ca", - "type": "listItem", - "model": { - "blocks": [ - { - "id": "ae7b1150", - "type": "paragraph", - "model": { - "text": "Brian Thompson, director ejecutivo de United Health Care (UHC), la mayor aseguradora de salud privada de Estados Unidos, fue asesinado este miércoles en Nueva York por un hombre enmascarado", - "blocks": [ - { - "id": "c5947833", - "type": "fragment", - "model": { - "text": "Brian Thompson, director ejecutivo de United Health Care (UHC), la mayor aseguradora de salud privada de Estados Unidos, fue asesinado este miércoles en Nueva York por un hombre enmascarado", - "attributes": [] - } - } - ] - } - } - ] - } - }, - { - "id": "e9cb4a5b", - "type": "listItem", - "model": { - "blocks": [ - { - "id": "79250da1", - "type": "paragraph", - "model": { - "text": "En lo que los investigadores describieron como un \"ataque descarado y selectivo\".", - "blocks": [ - { - "id": "cde27120", - "type": "fragment", - "model": { - "text": "En lo que los investigadores describieron como un \"ataque descarado y selectivo\".", - "attributes": [] - } - } - ] - } - } - ] - } - }, - { - "id": "b4be2cf4", - "type": "listItem", - "model": { - "blocks": [ - { - "id": "11342a4b", - "type": "paragraph", - "model": { - "text": "Agentes de policía de la ciudad de Nueva York están utilizando tecnología de reconocimiento facial y un teléfono celular desechado para identificar al hombre que mató a Thompson.", - "blocks": [ - { - "id": "62fb4406", - "type": "fragment", - "model": { - "text": "Agentes de policía de la ciudad de Nueva York están utilizando tecnología de reconocimiento facial y un teléfono celular desechado para identificar al hombre que mató a Thompson.", - "attributes": [] - } - } - ] - } - } - ] - } - } - ] - } - } - ] - } - } - ] - } - } - }, - "liveTextStream": { - "id": "52C95343A578423ABFA312004999027F", - "contributors": null, - "content": { - "data": { - "results": [ - { - "typeCode": null, - "header": { - "model": { - "blocks": [ - { - "id": "c2934708", - "type": "headline", - "model": { - "blocks": [ - { - "id": "9b4e950e", - "type": "text", - "model": { - "blocks": [ - { - "id": "b706438f", - "type": "paragraph", - "model": { - "text": "Asesinan a tiros en Nueva York al director ejecutivo de la principal aseguradora privada de salud de EE.UU.", - "blocks": [ - { - "id": "c389157d", - "type": "fragment", - "model": { - "text": "Asesinan a tiros en Nueva York al director ejecutivo de la principal aseguradora privada de salud de EE.UU.", - "attributes": [] - } - } - ] - } - } - ] - } - } - ] - } - } - ] - } - }, - "content": { - "model": { - "blocks": [ - { - "id": "7eff4bad", - "type": "paragraph", - "model": { - "text": "Brian Thompson, director ejecutivo de United Health Care (UHC), la mayor aseguradora de salud privada de Estados Unidos, fue asesinado este miércoles en Nueva York por un hombre enmascarado, en lo que los investigadores describieron como un \"ataque descarado y selectivo\".", - "blocks": [ - { - "id": "1a200412", - "type": "fragment", - "model": { - "text": "Brian Thompson, director ejecutivo de United Health Care (UHC), la mayor aseguradora de salud privada de Estados Unidos, fue asesinado este miércoles en Nueva York por un hombre enmascarado, en lo que los investigadores describieron como un \"ataque descarado y selectivo\".", - "attributes": ["bold"] - } - } - ] - } - }, - { - "id": "a71ec7cb", - "type": "paragraph", - "model": { - "text": "Agentes de policía de la ciudad de Nueva York están utilizando tecnología de reconocimiento facial y un teléfono celular desechado para identificar al hombre que mató a Thompson.", - "blocks": [ - { - "id": "b7768413", - "type": "fragment", - "model": { - "text": "Agentes de policía de la ciudad de Nueva York están utilizando tecnología de reconocimiento facial y un teléfono celular desechado para identificar al hombre que mató a Thompson.", - "attributes": [] - } - } - ] - } - }, - { - "id": "4c027bca", - "type": "paragraph", - "model": { - "text": "El ejecutivo, de 50 años de edad, fue atacado a disparos alrededor de las 06:45 hora local (11:45 GMT) en la entrada de un hotel en Manhattan, donde tenía previsto hablar en una conferencia de inversores más tarde ese mismo día.", - "blocks": [ - { - "id": "35186eb8", - "type": "fragment", - "model": { - "text": "El ejecutivo, de 50 años de edad, fue atacado a disparos alrededor de las 06:45 hora local (11:45 GMT) en la entrada de un hotel en Manhattan, donde tenía previsto hablar en una conferencia de inversores más tarde ese mismo día.", - "attributes": [] - } - } - ] - } - }, - { - "id": "b2f51c7d", - "type": "paragraph", - "model": { - "text": "Vistiendo un suéter con capucha, el atacante llegó al lugar caminando unos cinco minutos antes que Thompson. Cuando el ejecutivo llegó, se le acercó y le disparó con una pistola. Después desatascó el arma e hizo un segundo disparo.", - "blocks": [ - { - "id": "891df326", - "type": "fragment", - "model": { - "text": "Vistiendo un suéter con capucha, el atacante llegó al lugar caminando unos cinco minutos antes que Thompson. Cuando el ejecutivo llegó, ", - "attributes": [] - } - }, - { - "id": "2e3d7d9a", - "type": "fragment", - "model": { - "text": "se le acercó y le disparó con una pistola", - "attributes": ["bold"] - } - }, - { - "id": "98c981f7", - "type": "fragment", - "model": { - "text": ". Después desatascó el arma e hizo ", - "attributes": [] - } - }, - { - "id": "5d795bf0", - "type": "fragment", - "model": { - "text": "un segundo disparo", - "attributes": ["bold"] - } - }, - { - "id": "0d82c61b", - "type": "fragment", - "model": { - "text": ".", - "attributes": [] - } - } - ] - } - } - ] - } - }, - "link": null, - "urn": "asset:4b5c66af-7b36-4e25-a49c-ce975df249f5", - "type": "POST", - "options": { - "isBreakingNews": false - }, - "dates": { - "firstPublished": "2024-12-05T12:23:32.000Z", - "lastPublished": "2024-12-05T12:23:32.000Z", - "time": null, - "curated": "2024-12-05T12:23:33.050Z" - }, - "titles": [ - { - "title": null, - "source": "primary" - } - ], - "descriptions": [ - { - "text": null, - "source": "summary" - } - ], - "images": [ - { - "originalUrl": null, - "altText": null, - "copyright": null, - "urlTemplate": null, - "url": null - } - ] - } - ], - "page": { - "index": 1, - "total": 1 - } - } - } - }, - "metadata": { - "atiAnalytics": { - "contentId": "urn:bbc:tipo:topic:c7dkx155e626t", - "contentType": "live-coverage", - "pageIdentifier": "live_coverage.c7dkx155e626t.page", - "pageTitle": "USA Elections Aftermath: Get the latest updates", - "timePublished": "2024-12-05T11:26:00.000Z", - "timeUpdated": "2024-12-05T12:23:32.000Z" - } - } - }, - "contentType": "application/json; charset=utf-8" -} diff --git a/data/mundo/topics/c1en6xwmpkvt.json b/data/mundo/topics/c1en6xwmpkvt.json index 128abd4805d..b3a5ad67548 100644 --- a/data/mundo/topics/c1en6xwmpkvt.json +++ b/data/mundo/topics/c1en6xwmpkvt.json @@ -503,7 +503,8 @@ } ], "activePage": 1, - "pageCount": 1 + "pageCount": 1, + "variantTopicId": null }, "contentType": "application/json; charset=utf-8" } diff --git a/data/mundo/topics/c7zp57yyz25t.json b/data/mundo/topics/c7zp57yyz25t.json index 93e8408bbc5..f9c43880951 100644 --- a/data/mundo/topics/c7zp57yyz25t.json +++ b/data/mundo/topics/c7zp57yyz25t.json @@ -1,299 +1,300 @@ { - "data": { - "title": "América Latina", - "description": "", - "imageData": null, - "curations": [ - { - "summaries": [ - { - "type": "article", - "title": "Las intrigantes caras talladas en rocas que quedaron expuestas por la grave sequía en el Amazonas", - "firstPublished": "2023-10-24T18:04:38.999Z", - "lastPublished": "2023-10-24T18:04:38.999Z", - "link": "https://www.bbc.com/mundo/articles/c88e2j4y2qpo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/9e8b/live/01532810-7293-11ee-bf76-55d3810c1824.jpg", - "description": "La caída en el nivel del río, que descendió por debajo de los 13 metros, dejó al descubierto los enigmáticos grabados rupestres de rostros humanos.", - "imageAlt": "Rostros tallados en rocas", - "id": "c88e2j4y2qpo" - }, - { - "type": "article", - "title": "Dónde reside la extraordinaria capacidad de supervivencia del peronismo en Argentina", - "firstPublished": "2023-10-23T19:05:13.917Z", - "lastPublished": "2023-10-23T19:05:13.917Z", - "link": "https://www.bbc.com/mundo/articles/cjj8p1z936do", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/06c0/live/9aae7000-71d5-11ee-a387-671baf956d33.jpg", - "description": "Tras el inesperado primer lugar del ministro de Economía Sergio Massa en las elecciones presidenciales te contamos cómo el peronismo siempre ha resurgido después de las peores catástrofes económicas de Argentina.", - "imageAlt": "Sergio Massa", - "id": "cjj8p1z936do" - }, - { - "type": "article", - "title": "3 claves que explican el triunfo del centrista Sergio Massa frente a su rival libertario Javier Milei en Argentina", - "firstPublished": "2023-10-23T17:02:05.465Z", - "lastPublished": "2023-10-23T17:33:43.737Z", - "link": "https://www.bbc.com/mundo/articles/cyd121pd8q7o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/5403/live/cc3e1420-71af-11ee-9d67-33d6dd26a73e.jpg", - "description": "Desafiando la mayoría de las encuestas, Sergio Massa obtuvo el 36,6% de los votos, más que cualquier otro candidato en las elecciones presidenciales en Argentina. En esta nota analizamos las razones detrás de este triunfo. ", - "imageAlt": "Sergio Massa, candidato de Unión Por La Patria y actual Ministro de Economía de Argentina. ", - "id": "cyd121pd8q7o" - }, - { - "type": "article", - "title": "María Corina Machado, la elegida de la oposición de Venezuela para desafiar a Maduro (y cómo le puede afectar su inhabilitación)", - "firstPublished": "2023-10-23T09:17:53.552Z", - "lastPublished": "2023-10-23T09:17:53.552Z", - "link": "https://www.bbc.com/mundo/articles/cp64p2lpg7yo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/da5e/live/de4a8530-7142-11ee-ae48-293583c32fa4.jpg", - "description": "Machado ganó unas primarias de la oposición por un amplio margen y, por primera vez, se convirtió en la líder del movimiento que se opone al chavismo.", - "imageAlt": "Maria Corina Machado", - "id": "cp64p2lpg7yo" - }, - { - "type": "article", - "title": "Massa vs. Milei: 3 factores que definirán cuál de los dos será el próximo presidente de Argentina", - "firstPublished": "2023-10-23T06:54:08.384Z", - "lastPublished": "2023-10-23T06:54:08.384Z", - "link": "https://www.bbc.com/mundo/articles/cg3epzv7pz9o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/cecc/live/97946820-71bc-11ee-a44e-71f38a720542.jpg", - "description": "Argentina se encamina hacia un balotaje de resultado incierto entre un candidato oficialista y otro libertario. Analizamos qué podría inclinar la balanza hacia uno de ellos.", - "imageAlt": "Milei y Massa durante un debate de campaña.", - "id": "cg3epzv7pz9o" - }, - { - "type": "article", - "title": "Los hitos en la vida y la carrera de Javier Milei, el \"rara avis\" que promete dinamitar el sistema y un cambio radical para Argentina", - "firstPublished": "2023-10-23T03:58:50.739Z", - "lastPublished": "2023-10-23T03:58:50.739Z", - "link": "https://www.bbc.com/mundo/articles/cpe87jl17yjo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/68ff/live/13a97110-712e-11ee-9456-d511b84977e4.jpg", - "description": "Javier Milei, el segundo candidato más votado este domingo, peleará el 19 de noviembre contra Sergio Massa por la presidencia de Argentina. Su intensa vida explica su fenómeno político. ", - "imageAlt": "Javier Milei, del partido La Libertad Avanza, en un acto de campaña.", - "id": "cpe87jl17yjo" - }, - { - "type": "article", - "title": "Elecciones en Argentina: el \"superministro\" centrista Sergio Massa y el libertario Javier Milei se disputarán la presidencia en segunda vuelta", - "firstPublished": "2023-10-23T00:23:26.334Z", - "lastPublished": "2023-10-23T00:23:26.334Z", - "link": "https://www.bbc.com/mundo/articles/cy912v1kjn5o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/c34c/live/6d575dd0-713d-11ee-a503-4588075e3427.jpg", - "description": "El actual ministro de Economía, Sergio Massa, y el economista libertario Javier Milei se disputarían la presidencia el 19 de noviembre.", - "imageAlt": "Sergio Massa y Javier Milei.", - "id": "cy912v1kjn5o" - }, - { - "type": "article", - "title": "Por qué las elecciones de este domingo en Argentina desafían la lógica política de ese país", - "firstPublished": "2023-10-20T11:50:38.284Z", - "lastPublished": "2023-10-22T08:44:13.738Z", - "link": "https://www.bbc.com/mundo/articles/crg1eyzj0eeo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/f69a/live/9c7e8910-70f9-11ee-b28c-3bb76357dce9.jpg", - "description": "Con tres candidatos que podrían alcanzar la presidencia según las encuestas y un descalabro económico de fondo, los argentinos van a las urnas en unos comicios que podrían marcar cambios sustanciales en la historia reciente argentina.", - "imageAlt": "Sergio Massa, Patricia Bullrich y Javier Milei", - "id": "crg1eyzj0eeo" - }, - { - "type": "article", - "title": "La alianza con la que México y la Gran Colombia buscaron defenderse del imperialismo europeo y expulsar a España del Caribe hace 200 años", - "firstPublished": "2023-10-21T15:45:07.644Z", - "lastPublished": "2023-10-21T15:45:07.644Z", - "link": "https://www.bbc.com/mundo/articles/cldx79wq4v5o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/b7b5/live/d0a5e0a0-6823-11ee-bae1-0f3ee25a3569.jpg", - "description": "Con el Tratado de Amistad, Liga y Confederación los nacientes estados se comprometieron a ayudarse mutuamente a asegurar su independencia frente a cualquier enemigo.", - "imageAlt": "Pintura de un asalto al puerto de La Habana en el siglo XVIII", - "id": "cldx79wq4v5o" - }, - { - "type": "article", - "title": "Manzanas del Cuidado: el pionero plan de Bogotá para atender a mujeres que cuidan de otros (y que varias ciudades latinoamericanas quieren replicar)", - "firstPublished": "2023-10-20T11:29:29.037Z", - "lastPublished": "2023-10-20T11:29:29.037Z", - "link": "https://www.bbc.com/mundo/articles/cqeq38je3qro", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/0aa9/live/cce9caa0-566a-11ee-943d-91ce654583c0.jpg", - "description": "En Bogotá, un tercio de las mujeres se dedican de manera exclusiva al cuidado doméstico no remunerado. Por eso la alcaldía de la capital colombiana creó unos centros de ayuda para ellas.", - "imageAlt": "Natalia Moreno", - "id": "cqeq38je3qro" - }, - { - "type": "article", - "title": "\"Argenchina\": ¿por qué Argentina superó a Brasil y se convirtió en el \"favorito\" de China en América Latina?", - "firstPublished": "2023-10-18T11:04:07.702Z", - "lastPublished": "2023-10-18T11:04:07.702Z", - "link": "https://www.bbc.com/mundo/articles/cxe3kl5z1y7o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/2a2c/live/c35e7b80-6cdf-11ee-a93c-cdee1fd6cb4f.jpg", - "description": "Argentina superó a Brasil como el principal socio comercial de China en América Latina en 2022. Expertos consideran que la relación continuará sin importar el resultado de las próximas elecciones presidenciales en Argentina.", - "imageAlt": "Banderas china y argentina", - "id": "cxe3kl5z1y7o" - }, - { - "type": "article", - "title": "3 claves para entender la crisis de violencia en Ecuador y las propuestas para combatirla de Daniel Noboa, el nuevo presidente electo", - "firstPublished": "2023-10-16T13:11:20.367Z", - "lastPublished": "2023-10-16T13:11:20.367Z", - "link": "https://www.bbc.com/mundo/articles/cnd89g0zxleo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/0eb5/live/ab9db0b0-6c17-11ee-a6b3-61dda6c6fec7.jpg", - "description": "Daniel Noboa tendrá hasta mayo de 2025 para enfrentar el gran desafío de combatir la grave violencia que sacude a Ecuador. ", - "imageAlt": "Daniel Noboa durante la campaña electoral. ", - "id": "cnd89g0zxleo" - }, - { - "type": "article", - "title": "Daniel Noboa, la meteórica carrera política del joven empresario que triunfó en lo que su padre fracasó 5 veces y será presidente de Ecuador", - "firstPublished": "2023-10-16T01:35:27.040Z", - "lastPublished": "2023-10-16T01:35:27.040Z", - "link": "https://www.bbc.com/mundo/articles/c3g34ny7wn5o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/e9e0/live/eef2da90-6bc0-11ee-b092-a3cef6940fcc.jpg", - "description": "Daniel Noboa dio la sorpresa en la primera vuelta y ahora la consolidó al confirmarse como presidente tras una corta carrera política que comenzó en 2021. Te contamos quién es.", - "imageAlt": "Daniel Noboa", - "id": "c3g34ny7wn5o" - }, - { - "type": "article", - "title": "El empresario Daniel Noboa da un giro a la política de Ecuador: será el presidente más joven del país tras derrotar a la abogada correísta Luisa González", - "firstPublished": "2023-10-15T23:24:55.415Z", - "lastPublished": "2023-10-16T01:03:33.895Z", - "link": "https://www.bbc.com/mundo/articles/cv20j810w93o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/17f0/live/1d292870-6bc5-11ee-b46b-573a36e0a10c.jpg", - "description": "Ecuador eligió al joven empresario Daniel Noboa como presidente hasta mayo de 2025 al imponerse en las elecciones anticipadas a la abogada correísta Luisa González.", - "imageAlt": "Daniel Noboa", - "id": "cv20j810w93o" - }, - { - "type": "article", - "title": "Las espectaculares imágenes del eclipse solar \"anillo de fuego\" que pudo verse en América Latina", - "firstPublished": "2023-10-14T18:30:07.292Z", - "lastPublished": "2023-10-14T18:30:07.292Z", - "link": "https://www.bbc.com/mundo/articles/c6p599l418jo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/4fef/live/26f28080-6ad1-11ee-8073-5b93bd1aa7db.jpg", - "description": "El fenómeno pudo verse en Estados Unidos y varios países de la región. ", - "imageAlt": "El \"anillo de fuego\" desde Panamá", - "id": "c6p599l418jo" - }, - { - "type": "article", - "title": "Desde antiguas guerras hasta Cristóbal Colón: cómo los eclipses han cambiado el rumbo de la historia", - "firstPublished": "2023-10-13T18:44:13.458Z", - "lastPublished": "2023-10-13T18:44:13.458Z", - "link": "https://www.bbc.com/mundo/articles/cn0q54zeje4o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/1e20/live/52a5cfe0-69cb-11ee-a013-7d20b5f37524.jpg", - "description": "Algunas guerras antiguas y hasta la Teoría de la Relatividad de Einstein tienen alguna relación con los eclipses. La astronomía ha amparado a muchos para tomar decisiones que pudieron haber cambiado la historia.", - "imageAlt": "Cristóbal Colón", - "id": "cn0q54zeje4o" - }, - { - "type": "article", - "title": "Cuán ordenadas deja realmente las cuentas el presidente Guillermo Lasso en Ecuador (y cuál ha sido el costo social de su política económica)", - "firstPublished": "2023-08-17T12:30:34.829Z", - "lastPublished": "2023-10-13T13:23:34.825Z", - "link": "https://www.bbc.com/mundo/articles/cv25gq93pvro", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/6fd8/live/4489c580-3c1a-11ee-8d1a-e90bdc09a724.jpg", - "description": "El gobierno de Guillermo Lasso consiguió reducir la deuda pública, pero a cambio tuvo que recortar en inversión social. Ahora deja la presidencia antes del final de su mandato. ", - "imageAlt": "Guillermo Lasso", - "id": "cv25gq93pvro" - }, - { - "type": "article", - "title": "Muere Luis Alfredo Garavito: los crímenes contra casi 200 niños del mayor asesino en serie de la historia de Colombia", - "firstPublished": "2023-10-13T01:44:41.867Z", - "lastPublished": "2023-10-13T01:44:41.867Z", - "link": "https://www.bbc.com/mundo/articles/cw0k974k4xko", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/4fda/live/e065b060-6968-11ee-999a-17d3ba52098c.jpg", - "description": "Pese a que sus condenas sumaban 1.853 años, ciertas disposiciones de la ley hacían posible que recuperara su libertad en dos meses. Con su muerte se cierra esa posibilidad, así como uno de los capítulos más traumáticos de la vida colombiana.", - "imageAlt": "Retrato Luis Alfredo Garavito", - "id": "cw0k974k4xko" - }, - { - "type": "article", - "title": "\"Hasta para pedir un préstamo en el banco\": la alucinante cantidad de cosas para las que necesitan el certificado de votación los ecuatorianos", - "firstPublished": "2023-10-12T13:48:18.602Z", - "lastPublished": "2023-10-12T13:48:18.602Z", - "link": "https://www.bbc.com/mundo/articles/cn48rj2486jo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/d589/live/2a578a20-6934-11ee-a305-894244efd71e.png", - "description": "En Ecuador el voto es obligatorio y el certificado de votación es casi tan importante como la cédula de identidad. ", - "imageAlt": "Cédula", - "id": "cn48rj2486jo" - }, - { - "type": "article", - "title": "Qué es un eclipse \"anillo de fuego\" y dónde se verá en América Latina este 14 de octubre ", - "firstPublished": "2023-10-12T11:57:06.188Z", - "lastPublished": "2023-10-12T11:57:06.188Z", - "link": "https://www.bbc.com/mundo/articles/cw0k12lkwjjo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/da61/live/fda5dbd0-69b0-11ee-97a6-f13289207f54.png", - "description": "Desde México, Centroamérica y partes de Sudamérica, el eclipse anular del 14 de octubre podrá ser visto de manera total o parcial.", - "imageAlt": "Eclipse anular", - "id": "cw0k12lkwjjo" - }, - { - "type": "article", - "title": "\"Mi hija me llamó y me dijo: ‘Papi, estamos en guerra’. Fue la última vez que hablé con ella\": la desesperación de los familiares de latinoamericanos desaparecidos en Israel", - "firstPublished": "2023-10-09T23:53:57.595Z", - "lastPublished": "2023-10-09T23:53:57.595Z", - "link": "https://www.bbc.com/mundo/articles/ce9d7d67lgqo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/57fe/live/b624cda0-67b9-11ee-a097-ff42e411b681.jpg", - "description": "Decenas de latinoamericanos han muerto o desaparecido durante el ataque de Hamás a Israel. Una pareja colombiana estaba en la fiesta electrónica donde fueron encontrados 260 cadáveres. Familiares que buscan a sus seres queridos temen que sean rehenes.", - "imageAlt": "Ivonne Rubio y Antonio Macías, pareja de colombianos desaparecidos en Israel.", - "id": "ce9d7d67lgqo" - }, - { - "type": "article", - "title": "El faro sin luz que honra a Cristóbal Colón y las maldiciones atribuidas a sus restos en República Dominicana", - "firstPublished": "2023-10-08T13:31:05.909Z", - "lastPublished": "2023-10-08T13:31:05.909Z", - "link": "https://www.bbc.com/mundo/articles/czv945d0xgjo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/672a/live/23508740-6452-11ee-a0c8-ab8a89e71afa.jpg", - "description": "En Santo Domingo se inauguró en los 90 un faro para honrar a Cristóbal Colón. El autor de esta crónica, que lo conoció de niño, regresa y comparte su curiosa historia. ", - "imageAlt": "Vista aérea del Faro a Colón", - "id": "czv945d0xgjo" - }, - { - "type": "article", - "title": "Por qué los migrantes venezolanos se volvieron un asunto clave para el presidente Biden", - "firstPublished": "2023-10-06T23:34:50.416Z", - "lastPublished": "2023-10-06T23:34:50.416Z", - "link": "https://www.bbc.com/mundo/articles/c6p4zp4re6lo", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/7020/live/82863a00-649a-11ee-999b-452f629fee80.jpg", - "description": "La llegada masiva de migrantes venezolanos ha llevado a la Casa Blanca a reenfocar su política migratoria, en medio de fuertes presiones de republicanos y demócratas.", - "imageAlt": "Un grupo de migrantes en la frontera con TExas", - "id": "c6p4zp4re6lo" - }, - { - "type": "article", - "title": "\"Aquí los que deberían estar pidiendo perdón por el asesinato de mi hijo son Álvaro Uribe y Juan Manuel Santos\": Rubiela Giraldo, madre de una de las víctimas de los falsos positivos en Colombia", - "firstPublished": "2023-10-06T11:01:56.599Z", - "lastPublished": "2023-10-06T11:01:56.599Z", - "link": "https://www.bbc.com/mundo/articles/c4nd2jde111o", - "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/dab7/live/2cbb2b70-6388-11ee-8c6f-1d8308af9ab5.jpg", - "description": "Esta semana el gobierno colombiano realizó un acto de petición de perdón público por la muerte de miles de jóvenes que fueron ejecutados extrajudicialmente por el ejército.", - "imageAlt": "Rubiela Giraldo.", - "id": "c4nd2jde111o" - } + "data": { + "title": "América Latina", + "description": "", + "imageData": null, + "curations": [ + { + "summaries": [ + { + "type": "article", + "title": "Las intrigantes caras talladas en rocas que quedaron expuestas por la grave sequía en el Amazonas", + "firstPublished": "2023-10-24T18:04:38.999Z", + "lastPublished": "2023-10-24T18:04:38.999Z", + "link": "https://www.bbc.com/mundo/articles/c88e2j4y2qpo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/9e8b/live/01532810-7293-11ee-bf76-55d3810c1824.jpg", + "description": "La caída en el nivel del río, que descendió por debajo de los 13 metros, dejó al descubierto los enigmáticos grabados rupestres de rostros humanos.", + "imageAlt": "Rostros tallados en rocas", + "id": "c88e2j4y2qpo" + }, + { + "type": "article", + "title": "Dónde reside la extraordinaria capacidad de supervivencia del peronismo en Argentina", + "firstPublished": "2023-10-23T19:05:13.917Z", + "lastPublished": "2023-10-23T19:05:13.917Z", + "link": "https://www.bbc.com/mundo/articles/cjj8p1z936do", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/06c0/live/9aae7000-71d5-11ee-a387-671baf956d33.jpg", + "description": "Tras el inesperado primer lugar del ministro de Economía Sergio Massa en las elecciones presidenciales te contamos cómo el peronismo siempre ha resurgido después de las peores catástrofes económicas de Argentina.", + "imageAlt": "Sergio Massa", + "id": "cjj8p1z936do" + }, + { + "type": "article", + "title": "3 claves que explican el triunfo del centrista Sergio Massa frente a su rival libertario Javier Milei en Argentina", + "firstPublished": "2023-10-23T17:02:05.465Z", + "lastPublished": "2023-10-23T17:33:43.737Z", + "link": "https://www.bbc.com/mundo/articles/cyd121pd8q7o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/5403/live/cc3e1420-71af-11ee-9d67-33d6dd26a73e.jpg", + "description": "Desafiando la mayoría de las encuestas, Sergio Massa obtuvo el 36,6% de los votos, más que cualquier otro candidato en las elecciones presidenciales en Argentina. En esta nota analizamos las razones detrás de este triunfo. ", + "imageAlt": "Sergio Massa, candidato de Unión Por La Patria y actual Ministro de Economía de Argentina. ", + "id": "cyd121pd8q7o" + }, + { + "type": "article", + "title": "María Corina Machado, la elegida de la oposición de Venezuela para desafiar a Maduro (y cómo le puede afectar su inhabilitación)", + "firstPublished": "2023-10-23T09:17:53.552Z", + "lastPublished": "2023-10-23T09:17:53.552Z", + "link": "https://www.bbc.com/mundo/articles/cp64p2lpg7yo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/da5e/live/de4a8530-7142-11ee-ae48-293583c32fa4.jpg", + "description": "Machado ganó unas primarias de la oposición por un amplio margen y, por primera vez, se convirtió en la líder del movimiento que se opone al chavismo.", + "imageAlt": "Maria Corina Machado", + "id": "cp64p2lpg7yo" + }, + { + "type": "article", + "title": "Massa vs. Milei: 3 factores que definirán cuál de los dos será el próximo presidente de Argentina", + "firstPublished": "2023-10-23T06:54:08.384Z", + "lastPublished": "2023-10-23T06:54:08.384Z", + "link": "https://www.bbc.com/mundo/articles/cg3epzv7pz9o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/cecc/live/97946820-71bc-11ee-a44e-71f38a720542.jpg", + "description": "Argentina se encamina hacia un balotaje de resultado incierto entre un candidato oficialista y otro libertario. Analizamos qué podría inclinar la balanza hacia uno de ellos.", + "imageAlt": "Milei y Massa durante un debate de campaña.", + "id": "cg3epzv7pz9o" + }, + { + "type": "article", + "title": "Los hitos en la vida y la carrera de Javier Milei, el \"rara avis\" que promete dinamitar el sistema y un cambio radical para Argentina", + "firstPublished": "2023-10-23T03:58:50.739Z", + "lastPublished": "2023-10-23T03:58:50.739Z", + "link": "https://www.bbc.com/mundo/articles/cpe87jl17yjo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/68ff/live/13a97110-712e-11ee-9456-d511b84977e4.jpg", + "description": "Javier Milei, el segundo candidato más votado este domingo, peleará el 19 de noviembre contra Sergio Massa por la presidencia de Argentina. Su intensa vida explica su fenómeno político. ", + "imageAlt": "Javier Milei, del partido La Libertad Avanza, en un acto de campaña.", + "id": "cpe87jl17yjo" + }, + { + "type": "article", + "title": "Elecciones en Argentina: el \"superministro\" centrista Sergio Massa y el libertario Javier Milei se disputarán la presidencia en segunda vuelta", + "firstPublished": "2023-10-23T00:23:26.334Z", + "lastPublished": "2023-10-23T00:23:26.334Z", + "link": "https://www.bbc.com/mundo/articles/cy912v1kjn5o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/c34c/live/6d575dd0-713d-11ee-a503-4588075e3427.jpg", + "description": "El actual ministro de Economía, Sergio Massa, y el economista libertario Javier Milei se disputarían la presidencia el 19 de noviembre.", + "imageAlt": "Sergio Massa y Javier Milei.", + "id": "cy912v1kjn5o" + }, + { + "type": "article", + "title": "Por qué las elecciones de este domingo en Argentina desafían la lógica política de ese país", + "firstPublished": "2023-10-20T11:50:38.284Z", + "lastPublished": "2023-10-22T08:44:13.738Z", + "link": "https://www.bbc.com/mundo/articles/crg1eyzj0eeo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/f69a/live/9c7e8910-70f9-11ee-b28c-3bb76357dce9.jpg", + "description": "Con tres candidatos que podrían alcanzar la presidencia según las encuestas y un descalabro económico de fondo, los argentinos van a las urnas en unos comicios que podrían marcar cambios sustanciales en la historia reciente argentina.", + "imageAlt": "Sergio Massa, Patricia Bullrich y Javier Milei", + "id": "crg1eyzj0eeo" + }, + { + "type": "article", + "title": "La alianza con la que México y la Gran Colombia buscaron defenderse del imperialismo europeo y expulsar a España del Caribe hace 200 años", + "firstPublished": "2023-10-21T15:45:07.644Z", + "lastPublished": "2023-10-21T15:45:07.644Z", + "link": "https://www.bbc.com/mundo/articles/cldx79wq4v5o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/b7b5/live/d0a5e0a0-6823-11ee-bae1-0f3ee25a3569.jpg", + "description": "Con el Tratado de Amistad, Liga y Confederación los nacientes estados se comprometieron a ayudarse mutuamente a asegurar su independencia frente a cualquier enemigo.", + "imageAlt": "Pintura de un asalto al puerto de La Habana en el siglo XVIII", + "id": "cldx79wq4v5o" + }, + { + "type": "article", + "title": "Manzanas del Cuidado: el pionero plan de Bogotá para atender a mujeres que cuidan de otros (y que varias ciudades latinoamericanas quieren replicar)", + "firstPublished": "2023-10-20T11:29:29.037Z", + "lastPublished": "2023-10-20T11:29:29.037Z", + "link": "https://www.bbc.com/mundo/articles/cqeq38je3qro", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/0aa9/live/cce9caa0-566a-11ee-943d-91ce654583c0.jpg", + "description": "En Bogotá, un tercio de las mujeres se dedican de manera exclusiva al cuidado doméstico no remunerado. Por eso la alcaldía de la capital colombiana creó unos centros de ayuda para ellas.", + "imageAlt": "Natalia Moreno", + "id": "cqeq38je3qro" + }, + { + "type": "article", + "title": "\"Argenchina\": ¿por qué Argentina superó a Brasil y se convirtió en el \"favorito\" de China en América Latina?", + "firstPublished": "2023-10-18T11:04:07.702Z", + "lastPublished": "2023-10-18T11:04:07.702Z", + "link": "https://www.bbc.com/mundo/articles/cxe3kl5z1y7o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/2a2c/live/c35e7b80-6cdf-11ee-a93c-cdee1fd6cb4f.jpg", + "description": "Argentina superó a Brasil como el principal socio comercial de China en América Latina en 2022. Expertos consideran que la relación continuará sin importar el resultado de las próximas elecciones presidenciales en Argentina.", + "imageAlt": "Banderas china y argentina", + "id": "cxe3kl5z1y7o" + }, + { + "type": "article", + "title": "3 claves para entender la crisis de violencia en Ecuador y las propuestas para combatirla de Daniel Noboa, el nuevo presidente electo", + "firstPublished": "2023-10-16T13:11:20.367Z", + "lastPublished": "2023-10-16T13:11:20.367Z", + "link": "https://www.bbc.com/mundo/articles/cnd89g0zxleo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/0eb5/live/ab9db0b0-6c17-11ee-a6b3-61dda6c6fec7.jpg", + "description": "Daniel Noboa tendrá hasta mayo de 2025 para enfrentar el gran desafío de combatir la grave violencia que sacude a Ecuador. ", + "imageAlt": "Daniel Noboa durante la campaña electoral. ", + "id": "cnd89g0zxleo" + }, + { + "type": "article", + "title": "Daniel Noboa, la meteórica carrera política del joven empresario que triunfó en lo que su padre fracasó 5 veces y será presidente de Ecuador", + "firstPublished": "2023-10-16T01:35:27.040Z", + "lastPublished": "2023-10-16T01:35:27.040Z", + "link": "https://www.bbc.com/mundo/articles/c3g34ny7wn5o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/e9e0/live/eef2da90-6bc0-11ee-b092-a3cef6940fcc.jpg", + "description": "Daniel Noboa dio la sorpresa en la primera vuelta y ahora la consolidó al confirmarse como presidente tras una corta carrera política que comenzó en 2021. Te contamos quién es.", + "imageAlt": "Daniel Noboa", + "id": "c3g34ny7wn5o" + }, + { + "type": "article", + "title": "El empresario Daniel Noboa da un giro a la política de Ecuador: será el presidente más joven del país tras derrotar a la abogada correísta Luisa González", + "firstPublished": "2023-10-15T23:24:55.415Z", + "lastPublished": "2023-10-16T01:03:33.895Z", + "link": "https://www.bbc.com/mundo/articles/cv20j810w93o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/17f0/live/1d292870-6bc5-11ee-b46b-573a36e0a10c.jpg", + "description": "Ecuador eligió al joven empresario Daniel Noboa como presidente hasta mayo de 2025 al imponerse en las elecciones anticipadas a la abogada correísta Luisa González.", + "imageAlt": "Daniel Noboa", + "id": "cv20j810w93o" + }, + { + "type": "article", + "title": "Las espectaculares imágenes del eclipse solar \"anillo de fuego\" que pudo verse en América Latina", + "firstPublished": "2023-10-14T18:30:07.292Z", + "lastPublished": "2023-10-14T18:30:07.292Z", + "link": "https://www.bbc.com/mundo/articles/c6p599l418jo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/4fef/live/26f28080-6ad1-11ee-8073-5b93bd1aa7db.jpg", + "description": "El fenómeno pudo verse en Estados Unidos y varios países de la región. ", + "imageAlt": "El \"anillo de fuego\" desde Panamá", + "id": "c6p599l418jo" + }, + { + "type": "article", + "title": "Desde antiguas guerras hasta Cristóbal Colón: cómo los eclipses han cambiado el rumbo de la historia", + "firstPublished": "2023-10-13T18:44:13.458Z", + "lastPublished": "2023-10-13T18:44:13.458Z", + "link": "https://www.bbc.com/mundo/articles/cn0q54zeje4o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/1e20/live/52a5cfe0-69cb-11ee-a013-7d20b5f37524.jpg", + "description": "Algunas guerras antiguas y hasta la Teoría de la Relatividad de Einstein tienen alguna relación con los eclipses. La astronomía ha amparado a muchos para tomar decisiones que pudieron haber cambiado la historia.", + "imageAlt": "Cristóbal Colón", + "id": "cn0q54zeje4o" + }, + { + "type": "article", + "title": "Cuán ordenadas deja realmente las cuentas el presidente Guillermo Lasso en Ecuador (y cuál ha sido el costo social de su política económica)", + "firstPublished": "2023-08-17T12:30:34.829Z", + "lastPublished": "2023-10-13T13:23:34.825Z", + "link": "https://www.bbc.com/mundo/articles/cv25gq93pvro", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/6fd8/live/4489c580-3c1a-11ee-8d1a-e90bdc09a724.jpg", + "description": "El gobierno de Guillermo Lasso consiguió reducir la deuda pública, pero a cambio tuvo que recortar en inversión social. Ahora deja la presidencia antes del final de su mandato. ", + "imageAlt": "Guillermo Lasso", + "id": "cv25gq93pvro" + }, + { + "type": "article", + "title": "Muere Luis Alfredo Garavito: los crímenes contra casi 200 niños del mayor asesino en serie de la historia de Colombia", + "firstPublished": "2023-10-13T01:44:41.867Z", + "lastPublished": "2023-10-13T01:44:41.867Z", + "link": "https://www.bbc.com/mundo/articles/cw0k974k4xko", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/4fda/live/e065b060-6968-11ee-999a-17d3ba52098c.jpg", + "description": "Pese a que sus condenas sumaban 1.853 años, ciertas disposiciones de la ley hacían posible que recuperara su libertad en dos meses. Con su muerte se cierra esa posibilidad, así como uno de los capítulos más traumáticos de la vida colombiana.", + "imageAlt": "Retrato Luis Alfredo Garavito", + "id": "cw0k974k4xko" + }, + { + "type": "article", + "title": "\"Hasta para pedir un préstamo en el banco\": la alucinante cantidad de cosas para las que necesitan el certificado de votación los ecuatorianos", + "firstPublished": "2023-10-12T13:48:18.602Z", + "lastPublished": "2023-10-12T13:48:18.602Z", + "link": "https://www.bbc.com/mundo/articles/cn48rj2486jo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/d589/live/2a578a20-6934-11ee-a305-894244efd71e.png", + "description": "En Ecuador el voto es obligatorio y el certificado de votación es casi tan importante como la cédula de identidad. ", + "imageAlt": "Cédula", + "id": "cn48rj2486jo" + }, + { + "type": "article", + "title": "Qué es un eclipse \"anillo de fuego\" y dónde se verá en América Latina este 14 de octubre ", + "firstPublished": "2023-10-12T11:57:06.188Z", + "lastPublished": "2023-10-12T11:57:06.188Z", + "link": "https://www.bbc.com/mundo/articles/cw0k12lkwjjo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/da61/live/fda5dbd0-69b0-11ee-97a6-f13289207f54.png", + "description": "Desde México, Centroamérica y partes de Sudamérica, el eclipse anular del 14 de octubre podrá ser visto de manera total o parcial.", + "imageAlt": "Eclipse anular", + "id": "cw0k12lkwjjo" + }, + { + "type": "article", + "title": "\"Mi hija me llamó y me dijo: ‘Papi, estamos en guerra’. Fue la última vez que hablé con ella\": la desesperación de los familiares de latinoamericanos desaparecidos en Israel", + "firstPublished": "2023-10-09T23:53:57.595Z", + "lastPublished": "2023-10-09T23:53:57.595Z", + "link": "https://www.bbc.com/mundo/articles/ce9d7d67lgqo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/57fe/live/b624cda0-67b9-11ee-a097-ff42e411b681.jpg", + "description": "Decenas de latinoamericanos han muerto o desaparecido durante el ataque de Hamás a Israel. Una pareja colombiana estaba en la fiesta electrónica donde fueron encontrados 260 cadáveres. Familiares que buscan a sus seres queridos temen que sean rehenes.", + "imageAlt": "Ivonne Rubio y Antonio Macías, pareja de colombianos desaparecidos en Israel.", + "id": "ce9d7d67lgqo" + }, + { + "type": "article", + "title": "El faro sin luz que honra a Cristóbal Colón y las maldiciones atribuidas a sus restos en República Dominicana", + "firstPublished": "2023-10-08T13:31:05.909Z", + "lastPublished": "2023-10-08T13:31:05.909Z", + "link": "https://www.bbc.com/mundo/articles/czv945d0xgjo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/672a/live/23508740-6452-11ee-a0c8-ab8a89e71afa.jpg", + "description": "En Santo Domingo se inauguró en los 90 un faro para honrar a Cristóbal Colón. El autor de esta crónica, que lo conoció de niño, regresa y comparte su curiosa historia. ", + "imageAlt": "Vista aérea del Faro a Colón", + "id": "czv945d0xgjo" + }, + { + "type": "article", + "title": "Por qué los migrantes venezolanos se volvieron un asunto clave para el presidente Biden", + "firstPublished": "2023-10-06T23:34:50.416Z", + "lastPublished": "2023-10-06T23:34:50.416Z", + "link": "https://www.bbc.com/mundo/articles/c6p4zp4re6lo", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/7020/live/82863a00-649a-11ee-999b-452f629fee80.jpg", + "description": "La llegada masiva de migrantes venezolanos ha llevado a la Casa Blanca a reenfocar su política migratoria, en medio de fuertes presiones de republicanos y demócratas.", + "imageAlt": "Un grupo de migrantes en la frontera con TExas", + "id": "c6p4zp4re6lo" + }, + { + "type": "article", + "title": "\"Aquí los que deberían estar pidiendo perdón por el asesinato de mi hijo son Álvaro Uribe y Juan Manuel Santos\": Rubiela Giraldo, madre de una de las víctimas de los falsos positivos en Colombia", + "firstPublished": "2023-10-06T11:01:56.599Z", + "lastPublished": "2023-10-06T11:01:56.599Z", + "link": "https://www.bbc.com/mundo/articles/c4nd2jde111o", + "imageUrl": "https://ichef.bbci.co.uk/ace/standard/{width}/cpsprodpb/dab7/live/2cbb2b70-6388-11ee-8c6f-1d8308af9ab5.jpg", + "description": "Esta semana el gobierno colombiano realizó un acto de petición de perdón público por la muerte de miles de jóvenes que fueron ejecutados extrajudicialmente por el ejército.", + "imageAlt": "Rubiela Giraldo.", + "id": "c4nd2jde111o" + } + ], + "activePage": 1, + "pageCount": 40, + "curationId": "urn:bbc:vivo:curation:3dbcb38a-f8c3-4661-a7fe-146105a493f9", + "curationType": "vivo-stream", + "position": 0, + "visualProminence": "NORMAL", + "visualStyle": "FEED" + } ], "activePage": 1, "pageCount": 40, - "curationId": "urn:bbc:vivo:curation:3dbcb38a-f8c3-4661-a7fe-146105a493f9", - "curationType": "vivo-stream", - "position": 0, - "visualProminence": "NORMAL", - "visualStyle": "FEED" - } - ], - "activePage": 1, - "pageCount": 40, - "metadata": { - "analytics": { - "name": "mundo.topics.c7zp57yyz25t.page", - "producer": "MUNDO" - }, - "atiAnalytics": { - "contentId": "urn:bbc:tipo:topic:c7zp57yyz25t", - "contentType": "index-category", - "pageIdentifier": "mundo.topics.c7zp57yyz25t.page", - "pageTitle": "América Latina" - } - } - }, - "contentType": "application/json; charset=utf-8" -} + "variantTopicId": null, + "metadata": { + "analytics": { + "name": "mundo.topics.c7zp57yyz25t.page", + "producer": "MUNDO" + }, + "atiAnalytics": { + "contentId": "urn:bbc:tipo:topic:c7zp57yyz25t", + "contentType": "index-category", + "pageIdentifier": "mundo.topics.c7zp57yyz25t.page", + "pageTitle": "América Latina" + } + } + }, + "contentType": "application/json; charset=utf-8" +} \ No newline at end of file diff --git a/data/mundo/topics/cw90edn9kw4t.json b/data/mundo/topics/cw90edn9kw4t.json index b3d65b4778a..89f9df9297d 100644 --- a/data/mundo/topics/cw90edn9kw4t.json +++ b/data/mundo/topics/cw90edn9kw4t.json @@ -136,6 +136,7 @@ ], "activePage": 1, "pageCount": 1, + "variantTopicId": null, "metadata": { "analytics": { "name": "mundo.topics.cw90edn9kw4t.page", diff --git a/data/persian/topics/crezq2dg9zwt.json b/data/persian/topics/crezq2dg9zwt.json index 05df9c131a7..e099408ebe4 100644 --- a/data/persian/topics/crezq2dg9zwt.json +++ b/data/persian/topics/crezq2dg9zwt.json @@ -230,6 +230,7 @@ ], "activePage": 1, "pageCount": 1, + "variantTopicId": null, "metadata": { "analytics": { "name": "persian.topics.crezq2dg9zwt.page", diff --git a/data/persian/topics/cyy2zqnqn67t.json b/data/persian/topics/cyy2zqnqn67t.json index e29e0076514..bd89fb7c918 100644 --- a/data/persian/topics/cyy2zqnqn67t.json +++ b/data/persian/topics/cyy2zqnqn67t.json @@ -126,7 +126,8 @@ } ], "activePage": 1, - "pageCount": 1 + "pageCount": 1, + "variantTopicId": null }, "contentType": "application/json; charset=utf-8" } diff --git a/data/pidgin/live/c7p765ynk9qt.json b/data/pidgin/live/c7p765ynk9qt.json index 19daf4b1214..810d6b69355 100644 --- a/data/pidgin/live/c7p765ynk9qt.json +++ b/data/pidgin/live/c7p765ynk9qt.json @@ -15,7 +15,7 @@ "sportDataEvent": { "id": null }, "eavisEvent": { "id": null }, "article": { "id": null }, - "mediaCollections": null, + "mediaCollections": [{ "id": null }], "keyHighlight": { "id": null }, "electionBanner": { "id": null }, "supportingLinks": { "id": null }, @@ -207,15 +207,15 @@ "id": "cb0f3228", "type": "video", "model": { - "locator": "urn:bbc:pips:pid:p0gh4n67", + "locator": "urn:bbc:pips:pid:p01vz9cq", "blocks": [ { "id": "2c039c31", "type": "clipMedia", "model": { - "id": "urn:bbc:pips:pid:p0gh4n67", + "id": "urn:bbc:pips:pid:p01vz9cq", "urns": { - "pipsPid": "urn:bbc:pips:pid:p0gh4n67" + "pipsPid": "urn:bbc:pips:pid:p01vz9cq" }, "images": [ { @@ -233,7 +233,7 @@ "source": "pipsImage" } ], - "assetPath": "p0gh4n67", + "assetPath": "p01vz9cq", "type": "video", "headlines": { "primaryHeadline": "Lionel Messi Skills", @@ -243,8 +243,8 @@ }, "analytics": { "page": { - "name": "programmes.av.p0gh4n67.page", - "contentId": "urn:bbc:pips:pid:p0gh4n67", + "name": "programmes.av.p01vz9cq.page", + "contentId": "urn:bbc:pips:pid:p01vz9cq", "producer": "PROGRAMMES" } }, @@ -274,17 +274,16 @@ "lastPublished": "2024-02-28T10:32:08Z", "firstPublished": null, "video": { - "id": "p0gh4n67", + "id": "p01vz9cq", "title": "Lionel Messi Skills", "holdingImage": { "id": "https://ichef.test.bbci.co.uk/images/ic/$recipe/p01vzbd6.jpg", "altText": "Gary Lineker talks Messi" }, "version": { - "id": "p0gh4n67", + "id": "p01vz9cs", "duration": "PT34S", "kind": "programme", - "simulcast": "true", "guidance": "Contains strong language and some upsetting scenes.", "territories": ["nonuk", "uk"] }, @@ -293,7 +292,7 @@ "isUnavailable": false }, "attributions": null, - "link": { "path": "/programmes/p0gh4n67" }, + "link": { "path": "/programmes/p01vz9cq" }, "section": null, "isSharingAllowed": true } diff --git a/data/pidgin/topics/c95y35941vrt.json b/data/pidgin/topics/c95y35941vrt.json index 46bb0acf080..f32f43000b5 100644 --- a/data/pidgin/topics/c95y35941vrt.json +++ b/data/pidgin/topics/c95y35941vrt.json @@ -256,6 +256,7 @@ ], "activePage": 1, "pageCount": 7, + "variantTopicId": null, "metadata": { "analytics": { "name": "pidgin.topics.c95y35941vrt.page", diff --git a/data/serbian/live/media-23179005/lat.json b/data/serbian/live/media-23179005/lat.json index 4768d439f7c..dd9a966fc2a 100644 --- a/data/serbian/live/media-23179005/lat.json +++ b/data/serbian/live/media-23179005/lat.json @@ -22,7 +22,7 @@ "article": { "id": null }, - "mediaCollections": null, + "mediaCollections": [], "keyHighlight": { "id": null }, diff --git a/data/tamil/topics/c03dm2xmzzpt.json b/data/tamil/topics/c03dm2xmzzpt.json index 8c54efbb888..42d7493b0f9 100644 --- a/data/tamil/topics/c03dm2xmzzpt.json +++ b/data/tamil/topics/c03dm2xmzzpt.json @@ -126,7 +126,8 @@ } ], "activePage": 1, - "pageCount": 1 + "pageCount": 1, + "variantTopicId": null }, "contentType": "application/json; charset=utf-8" } diff --git a/docs/Test-Strategy-Info.mdx b/docs/Test-Strategy-Info.mdx index 30aaef95d7d..765f5f2d394 100644 --- a/docs/Test-Strategy-Info.mdx +++ b/docs/Test-Strategy-Info.mdx @@ -1,183 +1,31 @@ import { Meta } from '@storybook/blocks'; -import { Link } from 'react-router-dom'; - + -# World Service - Test Automation strategy +# Test Strategy for adding integration/cypress tests +This document gives a brief overview on what basis integration and e2e tests are added. -## Table of Contents -- [Background](#background) -- [Why should we automate?](#why-should-we-automate) -- [Where should we automate?](#where-should-we-automate) -- [What levels to prioritise?](#what-levels-to-prioritise) -- [What should we automate?](#what-should-we-automate) -- [When should we automate?](#when-should-we-automate) -- [How should we automate?](#how-should-we-automate) -- [Who should automate?](#who-should-automate) +![TestingPyramid](https://user-images.githubusercontent.com/9802855/83626408-42541580-a58d-11ea-9891-30dcd2e5b936.png) +## Factors to be considered before deciding on integration or cypress tests -## Background +* Test runtime +* How flaky the test would be (how often the test will fail due to external conditions such as timing out) +* Realistic environment (does it mock the browser behaviour) +* Client/Server side rendered (as with JSDom in integration testing, it doesn’t entirely emulate a web browser so client side rendered components have to be tested using cypress) -This document aims to provide high-level guidance on approaching test automation for World Service teams. It specifically seeks to answer the following questions: +## Integration tests: +* Integration tests should cover all the test scenarios (happy and unhappy scenarios) at the component level (but in our case its more likely to be page types). +* Evaluate whether cases can be covered by integration tests before considering adding cypress tests due to the the time taken by cypress. +* Mock endpoints if it’s a third party component. Ideally, we should try not to mock our own components. -- Why should we automate? (Why we write tests) -- Where should we automate? (What levels) -- What should we automate? -- When should we automate? -- How should we automate? (Patterns, Anti-Patterns and Practices) -- Who should automate? (Responsibilities) +## E2E Tests: -While this document focuses primarily on E2E test strategy, it touches on other levels of testing to give a holistic understanding and context for the decisions we make when it comes to testing. It documents some of our existing approaches, while suggesting gradual improvements for the future. It’s a living collaborative document that we should refer to and update continuously. - - - - -## Why should we automate? - -There are many reasons why we write automated tests. Some of the most common are: - - -1. Verify the code is working correctly -2. Prevent future regressions -3. Document code behaviour -4. Provides (code) design guidance -5. Support refactoring - -[source: https://madeintandem.com/blog/five-factor-testing/] - - -> 🔊 Discussion point with team: -> - Any other reasons? -> - What are for you the most important reasons? - - -It is important to think about why we write tests because it influences what types of testing we should prioritise. For example, unit tests might be good for improving the system design as the units become more independent and easier to test, but having only unit tests doesn’t necessarily verify the code is working correctly (beyond the single unit) as it won’t necessarily reveal problems that occur in the integration of components. On the other hand, E2E tests can provide better verification of the software, but they’re expensive and would not necessarily improve software design, nor document the code behaviour at unit or component level. - -## Where should we automate? -### Defining the test levels - -Broadly speaking, we follow the test pyramid. We have a large set of unit tests, a smaller subset of integration tests, and an even smaller subset of UI tests. We should continue to follow this pattern. - -![Image](https://blog.getmason.io/content/images/2020/11/Testing-pyramid--6--1.jpg) -Image [Source](https://blog.getmason.io/content/images/2020/11/Testing-pyramid--6--1.jpg) - -To avoid confusion, we will clarify what the test levels mean in our context. - -### Unit tests -We use jest to write unit tests. They normally exercise a single component (UI components, hooks, helper methods). Here is an [example unit test](https://github.com/bbc/simorgh/tree/bfbed54d2b1fe880e8f171023054c16d1c274ca2/src/app/components/Heading). -Some patterns: - -- We use [data-driven tests](https://github.com/bbc/simorgh/blob/bfbed54d2b1fe880e8f171023054c16d1c274ca2/src/app/hooks/useImageColour/index.test.js#L10) to define inputs to tests -- We use [snapshot tests](https://github.com/bbc/simorgh/blob/4f385d94bee96af48eff2eca49c24cf382a3a494/src/app/components/FrostedGlassPromo/index.test.tsx#L76) to easily test certain scenarios. - - -### Component tests -We conduct visual regression testing using Chromatic, which alongside our unit tests, comprise our component tests layer. - -### Integration tests -These test the integration between the different components to ensure a page is rendered properly. -They typically check that what’s rendered matches local fixture data in a flexible generic way, i.e. check that a page renders “most read” articles but not the exact items rendered, and that each topic has an image and header but not the content of either. -Examples can be found in [src/integration](https://github.com/bbc/simorgh/tree/bfbed54d2b1fe880e8f171023054c16d1c274ca2/src/integration). -Check [here](https://github.com/bbc/simorgh/blob/cbf7c2b3d08a33775c51eeb94de750d0993a988b/src/integration/README.mdx#integration) for more details about our integration tests. - -### E2E tests -These tests run against the application and can check that everything renders correctly. For example, an integration test might check that “most read” articles are rendered with the correct HTML tags, but that does not necessarily mean they appear correctly (some CSS value might hide them). With the E2E tests, we check that everything shows as expected in the UI. -Examples and instructions can be found in [cypress](https://github.com/bbc/simorgh/tree/latest/cypress). - - - -#### What levels to prioritise? - -Unit tests responsibility and scope are relatively easy to define: we already have a high level of code coverage and developers should aim to maintain that. - -For integration and E2E tests, the general rule of thumb should be to only add an E2E test when an integration test is not enough to ensure coverage. Integration tests are less expensive and more stable and they should be prioritised. - - -> [!NOTE] - Think about the maintenance cost of adding a new E2E test. Always favour an integration test if possible instead of an end to end test. - -For new features, we should decide as part of the planning or shaping of work/tickets whether an E2E test is necessary for the feature. - -### What should we automate? -The decision to write an E2E test should be taken during planning as a team, but these are general guidelines to what should go into E2E tests - -#### Critical paths -The critical path is the set of components and workflows that is required for the application to serve its core function. For example, an article page displaying the content and title for the given service is critical in our context, but links to related topics or top stories, can be considered non-critical. These should be tested as well, but they’re better candidates for a less expensive layer of testing like integration tests. - -#### Smoke tests -We should have smoke tests for validating that core functionality is not broken, for example, that the page loads, has titles and content. These should run as often as possible (on each PR), they should be quick, reliable and repeatable (not flaky). - -#### As little as possible -The rule of thumb is that we should avoid writing an end to end test if there is an alternative to validating the functionality. The alternative can be a combination of layers, for example, an integration test combined with an API test (which doesn’t exist right now) can be enough to reach the same confidence as an E2E test with less cost. - -### PUMA approach -Adopting the [PUMA](https://confluence.dev.bbc.co.uk/display/podtest/PUMA) approach can provide a framework to decide if a proposed test genuinely meets the definition of an E2E. According to PUMA, an E2E test should: - -P - Prove core functionality - -U - be Understood by all - -M - Mandatory - -A - Automated - - -### How should we automate? -Patterns, anti-patterns and practices for E2E tests - -In this section, we will document some of the patterns we use and the best practices to write better E2E tests. - -##### Cypress best practices -A good place to start is the Cypress best practices page: https://docs.cypress.io/guides/references/best-practices - -Custom commands for common functionalities -We have many custom commands that make common tasks easier, these are located within [cypress/e2e/commands](https://github.com/bbc/simorgh/blob/latest/cypress/support/commands/application.js). It’s good to have a look at these to have an overview of what’s possible, and consider abstracting common functionality as a [cypress command](https://docs.cypress.io/api/cypress-api/custom-commands) when it makes sense. - -#### Do not ignore “flaky” tests - - -> There is no such thing as a flaky test. -> -> Any test is designed to provide insight into the functionality of the piece of code it exercises. As a result, when the test completes it provides information about how the code performed the actions detailed in the test. -> -> If the test is “green” (i.e. it confirms the actual behaviour matches the expected behaviour), then we are provided with the knowledge that at that time the system behaved as defined by the test. -> -> If the test is “red” (i.e. it confirms the actual behaviour did not match expectation), then we are provided with the knowledge that at that time the system did not behave as defined by the test. -> -> If a test remains permanently “red”, then we either misunderstood the purpose of the code and so need to review the expectations of the checks in the test or we have uncovered unexpected behaviour in the system under test. -> -> If a test remains permanently “green”, then we confirm current behaviour continues to meet the expectations of the test. -> -> However, a test that flickers between the two states indicates something is not right and should not be ignored! I believe describing these as flaky makes it easy to ignore potential problems either in the functionality of the system under test or the test itself. -> -> quote from [Simon Frampton, Principal Test Engineer, BBC](@sframpton) - - - -> 🔊 Discussion with team: -> - What are other good practices? -> - Any anti-patterns? - - -#### Who should automate? -#### Responsibilities -| | Unit tests | Integration | E2E | -| ----------- | ------------------ | ------------------ | ------------------ | -| Responsible | Developers | Developers and QA (^) | QA and Developers (^) | -| Accountable | Developers | Developers | QA | -| Consulted (+) | Developers and QA# | Developers and QA | Developers and QA | -| Informed (+) | Developers and QA# | Developers and QA | Developers and QA | - -(^) For integration tests, developers are accountable for them, but sometimes QA can be involved in their implementation. For E2E, QA are accountable for them but developers can be involved in the implementation too. - -(+) Consulting and informing in this context happens mainly during the sprint planning or three amigos. As a team, we can decide on whether a feature requires an integration or E2E test (or both). Consulting also includes code reviews of tests being added. - -> [!NOTE] -There is no reason why QA cannot be consulted and informed about the content of unit tests. As specialists, they might consider and uncover edge cases/scenarios that do not occur to developers and can also help to review the language used to frame the tests (think about `describe` and `it` blocks) even if they are not confident writing the test code itself. - - -##### References -- [Five Factor Testing](https://madeintandem.com/blog/five-factor-testing/) -- [Practical test pyramid](https://martinfowler.com/articles/practical-test-pyramid.html) +* Cypress tests to be used for simulating real user journeys. +* Cypress tests to be used for actions that change the state of the page (e.g script switching, clicking play on media, changing viewport) +* Most of our current cypress test have been replaced by integration tests which saves time in CI, however integration tests are not run against real browser environments so core functionality tests for page types which affect users should remain in cypress tests. +* It could be good to have either one or multiple user journeys that navigates through all page types (e.g front page → MAP → another MAP → front page → article etc.). This simulate a real user experience and could also serve as a sanity check as mentioned above. This is currently limited by some page types still being on PAL, but there are still some user journeys currently possible without needing to touch PAL pages. Another issue is that some page types require an override on the URL to be used in the test environment as they do not have many/any assets on test (e.g On Demand Radio brands and episodes) +* Layout changes at different viewport to be considered adding to cypress tests. +* Cypress tests to be run on a subset of services (services having script switcher, RTL, different layout) and not for all services. diff --git a/docs/User-Experience/colours.tsx b/docs/User-Experience/colours.tsx index 8cd845c59ec..22a97048384 100644 --- a/docs/User-Experience/colours.tsx +++ b/docs/User-Experience/colours.tsx @@ -55,7 +55,6 @@ export const CROSS_PLATFORM = [ { colorName: 'SERVICE_NEUTRAL_DARK', colorCode: '#0051AD' }, { colorName: 'LIVE_CORE', colorCode: '#009E9E' }, { colorName: 'LIVE_LIGHT', colorCode: '#00CCC7' }, - { colorName: 'LIVE_MEDIUM', colorCode: '#008282' }, { colorName: 'LIVE_DARK', colorCode: '#006666' }, { colorName: 'SUCCESS_CORE', colorCode: '#148A00' }, { colorName: 'SUCCESS_LIGHT', colorCode: '#49CC29' }, diff --git a/src/app/components/ATIAnalytics/atiUrl/index.test.ts b/src/app/components/ATIAnalytics/atiUrl/index.test.ts index f782af45a9f..4391a8cfc8b 100644 --- a/src/app/components/ATIAnalytics/atiUrl/index.test.ts +++ b/src/app/components/ATIAnalytics/atiUrl/index.test.ts @@ -1,12 +1,6 @@ import { resetWindowValue } from '#psammead/psammead-test-helpers/src'; -import { Platforms } from '#app/models/types/global'; import * as genericLabelHelpers from '../../../lib/analyticsUtils'; -import { - buildATIPageTrackPath, - buildATIEventTrackUrl, - buildReverbAnalyticsModel, - buildReverbPageSectionEventModel, -} from '.'; +import { buildATIPageTrackPath, buildATIEventTrackUrl } from '.'; // @ts-expect-error required for testing purposes const mockAndSet = ({ name, source }, response) => { @@ -56,7 +50,6 @@ describe('getThingAttributes', () => { afterEach(() => { jest.resetAllMocks(); - resetWindowValue('location', windowLocation); }); @@ -107,8 +100,8 @@ describe('getThingAttributes', () => { producerId: 'producerId', timePublished: 'timePublished', timeUpdated: 'timeUpdated', - ampExperimentName: 'someAmpExperiment', -}} | ${'https://www.bbc.com/news'} | ${['s2=producerId', 'p=pageIdentifier', 'x1=[contentId]', 'x3=[appName]', 'x4=[language]', 'x7=[contentType]', 'x11=[timePublished]', 'x12=[timeUpdated]', 'x13=[ldpThingLabels]', 'x14=[ldpThingIds]', 'xto=SEC------', 'mv_test=someAmpExperiment', 'mv_creation=VARIANT(someAmpExperiment)']} + ampExperimentName: 'someExperiment', +}} | ${'https://www.bbc.com/news'} | ${['s2=producerId', 'p=pageIdentifier', 'x1=[contentId]', 'x3=[appName]', 'x4=[language]', 'x7=[contentType]', 'x11=[timePublished]', 'x12=[timeUpdated]', 'x13=[ldpThingLabels]', 'x14=[ldpThingIds]', 'xto=SEC------', 'mv_test=Google Discover', 'mv_experiment_id=someExperiment', 'mv_creation=VARIANT(someExperiment)']} `( 'should take in optional props and add them as correct query params', ({ props, currentUrl, expectedValues }) => { @@ -279,177 +272,42 @@ describe('buildATIEventTrackUrl', () => { 'hl=getCurrentTime', 'lng=getDeviceLanguage', 'atc=PUB-[campaignID]-[component]-[variant_1]-[format]-[pageIdentifier]-[detailedPlacement]-[]-[url]', - 'mv_test=JumpTo Onward Journeys experiment', - 'mv_creation=variant_1', 'type=AT', ]); }); -}); -describe('Reverb', () => { - describe('buildReverbAnalyticsModel', () => { - beforeEach(() => { - analyticsUtilFunctions.forEach(func => { - mockAndSet(func, func.name); - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); + it('should return the correct url with mvt properties if ampExperimentName is present', () => { + process.env.SIMORGH_ATI_BASE_URL = 'http://foobar.com?'; - const input = { - appName: 'news', - campaigns: [ - { - campaignId: '1', - campaignName: 'campaign1', - }, - { - campaignId: '2', - campaignName: 'campaign2', - }, - ], - categoryName: 'categoryName', - contentId: 'contentId', - contentType: 'contentType', - language: 'language', - ldpThingIds: 'ldpThingIds', - ldpThingLabels: 'ldpThingLabels', - libraryVersion: 'libraryVersion', + const atiEventTrackUrl = buildATIEventTrackUrl({ pageIdentifier: 'pageIdentifier', - pageTitle: 'pageTitle', - platform: 'canonical' as Platforms, - previousPath: '', - producerName: 'producerName', - origin: 'http://localhost', - nationsProducer: '', - statsDestination: 'statsDestination', - timePublished: 'timePublished', - timeUpdated: 'timeUpdated', - }; - - it('should return the correct Reverb analytics model', () => { - const reverbAnalyticsModel = buildReverbAnalyticsModel(input); - - const pageParams = { - contentId: 'contentId', - contentType: 'contentType', - destination: 'statsDestination', - name: 'pageIdentifier', - producer: 'producerName', - additionalProperties: { - app_name: 'news', - app_type: 'getAppType', - content_language: 'language', - product_platform: null, - referrer_url: 'getReferrer', - x5: 'getHref', - x8: 'libraryVersion', - x9: 'sanitise', - x10: '', - x11: 'timePublished', - x12: 'timeUpdated', - x13: 'ldpThingLabels', - x14: 'ldpThingIds', - x16: 'campaign1~campaign2', - x17: 'categoryName', - x18: 'isLocServeCookieSet', - }, - }; - const userParans = { isSignedIn: false }; - - expect(reverbAnalyticsModel.params.page).toEqual(pageParams); - expect(reverbAnalyticsModel.params.user).toEqual(userParans); - - expect(reverbAnalyticsModel.eventDetails).toEqual({ - eventName: 'pageView', - }); - }); - }); - - describe('buildReverbPageSectionEventModel', () => { - const input = { - pageIdentifier: 'mundo.page', - producerName: 'MUNDO', + service: 'news', + platform: 'canonical', statsDestination: 'statsDestination', - componentName: 'top-stories', - campaignID: '1234', + componentName: 'component', + type: 'type', + campaignID: 'campaignID', format: 'format', - type: 'view', - advertiserID: 'advertiserID', - url: 'http://localhost', - }; - - it('should return the correct Reverb page section view event model', () => { - const reverbPageSectionViewEventModel = - buildReverbPageSectionEventModel(input); - - const pageSectionViewEventParams = { - destination: 'statsDestination', - name: 'mundo.page', - producer: 'MUNDO', - additionalProperties: { - ati: 'PUB-[1234]-[top-stories]-[]-[format]-[mundo.page]-[]-[advertiserID]-[http://localhost]', - type: 'AT', - }, - }; - - expect(reverbPageSectionViewEventModel.params.page).toEqual( - pageSectionViewEventParams, - ); - }); - - it('should return the correct eventName for the Reverb page section view event model', () => { - const reverbPageSectionViewEventModel = - buildReverbPageSectionEventModel(input); - - expect(reverbPageSectionViewEventModel.eventDetails).toEqual({ - eventName: 'sectionView', - }); - }); - - it('should return the correct Reverb page section click event model', () => { - const reverbPageSectionViewEventModel = buildReverbPageSectionEventModel({ - ...input, - type: 'click', - }); - - const pageSectionViewEventParams = { - destination: 'statsDestination', - name: 'mundo.page', - producer: 'MUNDO', - additionalProperties: { - atc: 'PUB-[1234]-[top-stories]-[]-[format]-[mundo.page]-[]-[advertiserID]-[http://localhost]', - type: 'AT', - }, - }; - - expect(reverbPageSectionViewEventModel.params.page).toEqual( - pageSectionViewEventParams, - ); - }); - - it('should return the correct event details for the Reverb page section click event model', () => { - const reverbPageSectionViewEventModel = buildReverbPageSectionEventModel({ - ...input, - type: 'click', - }); - - expect(reverbPageSectionViewEventModel.eventDetails).toEqual({ - eventName: 'sectionClick', - componentName: 'top-stories', - container: '1234', - }); + url: 'url', + detailedPlacement: 'detailedPlacement', + experimentVariant: 'variant_1', + ampExperimentName: 'someExperiment', }); - it('should return the correct Reverb user object configuration', () => { - const reverbPageSectionViewEventModel = - buildReverbPageSectionEventModel(input); - - expect(reverbPageSectionViewEventModel.params.user).toEqual({ - isSignedIn: false, - }); - }); + expect(splitUrl(atiEventTrackUrl)).toEqual([ + 'http://foobar.com', + 'idclient=getAtUserId', + 's=getDestination', + 'p=pageIdentifier', + 'r=getScreenInfo', + 're=getBrowserViewPort', + 'hl=getCurrentTime', + 'lng=getDeviceLanguage', + 'atc=PUB-[campaignID]-[component]-[variant_1]-[format]-[pageIdentifier]-[detailedPlacement]-[]-[url]', + 'mv_test=Google Discover', + 'mv_experiment_id=someExperiment', + 'mv_creation=variant_1', + 'type=AT', + ]); }); }); diff --git a/src/app/components/ATIAnalytics/atiUrl/index.ts b/src/app/components/ATIAnalytics/atiUrl/index.ts index 9b40df85e19..26a34048806 100644 --- a/src/app/components/ATIAnalytics/atiUrl/index.ts +++ b/src/app/components/ATIAnalytics/atiUrl/index.ts @@ -46,7 +46,6 @@ export const buildATIPageTrackPath = ({ campaigns, nationsProducer, ampExperimentName, - experimentVariant, }: ATIPageTrackingProps) => { const href = getHref(platform); const referrer = getReferrer(platform, origin, previousPath); @@ -216,28 +215,17 @@ export const buildATIPageTrackPath = ({ value: getATIMarketingString(href, campaignType), wrap: false, }, - ...(experimentVariant + ...(ampExperimentName ? [ { key: 'mv_test', - description: 'JumpTo Onward Journeys experiment', - value: `JumpTo Onward Journeys experiment`, + description: 'AMP experiment project name', + value: `Google Discover`, wrap: false, disableEncoding: true, }, { - key: 'mv_creation', - description: 'JumpTo Onward Journeys variant', - value: `${experimentVariant}`, - wrap: false, - disableEncoding: true, - }, - ] - : []), - ...(ampExperimentName - ? [ - { - key: 'mv_test', + key: 'mv_experiment_id', description: 'AMP experiment name', value: `${ampExperimentName}`, wrap: false, @@ -368,24 +356,6 @@ export const buildATIEventTrackUrl = ({ wrap: false, disableEncoding: true, }, - ...(experimentVariant - ? [ - { - key: 'mv_test', - description: 'JumpTo Onward Journeys experiment', - value: `JumpTo Onward Journeys experiment`, - wrap: false, - disableEncoding: true, - }, - { - key: 'mv_creation', - description: 'JumpTo Onward Journeys variant', - value: `${experimentVariant}`, - wrap: false, - disableEncoding: true, - }, - ] - : []), ...(ampExperimentName ? [ { @@ -405,7 +375,7 @@ export const buildATIEventTrackUrl = ({ { key: 'mv_creation', description: 'AMP experiment variant name', - value: `VARIANT(${ampExperimentName})`, + value: `${experimentVariant}`, wrap: false, disableEncoding: true, }, @@ -417,120 +387,3 @@ export const buildATIEventTrackUrl = ({ eventTrackingBeaconValues, )}&type=AT`; }; - -export const buildReverbAnalyticsModel = ({ - appName, - campaigns, - categoryName, - contentId, - contentType, - language, - ldpThingIds, - ldpThingLabels, - libraryVersion, - pageIdentifier, - pageTitle, - platform, - previousPath, - producerName, - origin, - nationsProducer, - statsDestination, - timePublished, - timeUpdated, -}: ATIPageTrackingProps) => { - const href = getHref(platform); - const referrer = getReferrer(platform, origin, previousPath); - - const aggregatedCampaigns = (Array.isArray(campaigns) ? campaigns : []) - .map(({ campaignName }) => campaignName) - .join('~'); - - const eventDetails = { - eventName: 'pageView', - }; - - const reverbVariables = { - params: { - page: { - contentId, - contentType, - destination: statsDestination, - name: pageIdentifier, - producer: producerName, - additionalProperties: { - app_name: platform === 'app' ? `${appName}-app` : appName, - app_type: getAppType(platform), - content_language: language, - product_platform: onOnionTld() ? 'tor-bbc' : null, - referrer_url: - referrer && encodeURIComponent(encodeURIComponent(referrer)), - x5: href && encodeURIComponent(encodeURIComponent(href)), - x8: libraryVersion, - x9: sanitise(pageTitle), - x10: nationsProducer && nationsProducer, - x11: timePublished, - x12: timeUpdated, - x13: ldpThingLabels, - x14: ldpThingIds, - x16: aggregatedCampaigns, - x17: categoryName, - x18: isLocServeCookieSet(), - }, - }, - user: { - isSignedIn: false, - }, - }, - eventDetails, - }; - - return reverbVariables; -}; - -export const buildReverbPageSectionEventModel = ({ - pageIdentifier, - producerName, - statsDestination, - componentName, - campaignID, - format, - type, - advertiserID, - url, -}: ATIEventTrackingProps) => { - const eventPublisher = type === 'view' ? 'ati' : 'atc'; - - const eventDetails = { - eventName: type === 'view' ? 'sectionView' : 'sectionClick', - ...(type === 'click' && { - componentName, - container: campaignID, - }), - }; - - return { - params: { - page: { - destination: statsDestination, - name: pageIdentifier, - producer: producerName, - additionalProperties: { - [eventPublisher]: getEventInfo({ - campaignID, - componentName, - format, - pageIdentifier, - advertiserID, - url, - }), - type: 'AT', - }, - }, - user: { - isSignedIn: false, - }, - }, - eventDetails, - }; -}; diff --git a/src/app/components/ATIAnalytics/beacon/index.test.ts b/src/app/components/ATIAnalytics/beacon/index.test.ts index df2e8dca97b..c352e9b5c18 100644 --- a/src/app/components/ATIAnalytics/beacon/index.test.ts +++ b/src/app/components/ATIAnalytics/beacon/index.test.ts @@ -11,18 +11,6 @@ const sendBeaconSpy = jest.spyOn(sendBeacon, 'default'); .fn() .mockReturnValue('00-00-00'); -const reverbMock = { - isReady: jest.fn(), - initialise: jest.fn(() => Promise.resolve()), - viewEvent: jest.fn(), - userActionEvent: jest.fn(), -}; - -// eslint-disable-next-line no-underscore-dangle -window.__reverb = { - __reverbLoadedPromise: Promise.resolve(reverbMock), -}; - describe('beacon', () => { const originalATIBaseUrl = process.env.SIMORGH_ATI_BASE_URL; const atiBaseUrl = 'https://foobar.com?'; diff --git a/src/app/components/ATIAnalytics/beacon/index.ts b/src/app/components/ATIAnalytics/beacon/index.ts index 28a5fa35933..902dd856295 100644 --- a/src/app/components/ATIAnalytics/beacon/index.ts +++ b/src/app/components/ATIAnalytics/beacon/index.ts @@ -1,8 +1,5 @@ import sendBeacon from '../../../lib/analyticsUtils/sendBeacon'; -import { - buildATIEventTrackUrl, - buildReverbPageSectionEventModel, -} from '../atiUrl'; +import { buildATIEventTrackUrl } from '../atiUrl'; import { ATIEventTrackingProps } from '../types'; export const sendEventBeacon = async ({ @@ -12,47 +9,29 @@ export const sendEventBeacon = async ({ pageIdentifier, platform, producerId, - producerName, service, statsDestination, type, advertiserID, url, detailedPlacement, - experimentVariant, - useReverb, }: ATIEventTrackingProps) => { - const atiClickTrackingUrl = buildATIEventTrackUrl({ - campaignID, - componentName, - format, - pageIdentifier, - platform, - producerId, - service, - statsDestination, - type, - advertiserID, - url, - detailedPlacement, - experimentVariant, - }); - - const reverbParams = useReverb - ? buildReverbPageSectionEventModel({ - pageIdentifier, - producerName, - statsDestination, - componentName, - campaignID, - format, - type, - advertiserID, - url, - }) - : null; - - await sendBeacon(atiClickTrackingUrl, reverbParams); + await sendBeacon( + buildATIEventTrackUrl({ + campaignID, + componentName, + format, + pageIdentifier, + platform, + producerId, + service, + statsDestination, + type, + advertiserID, + url, + detailedPlacement, + }), + ); }; export default sendEventBeacon; diff --git a/src/app/components/ATIAnalytics/canonical/index.test.tsx b/src/app/components/ATIAnalytics/canonical/index.test.tsx index 61ff5e9b1ea..536ef8e1457 100644 --- a/src/app/components/ATIAnalytics/canonical/index.test.tsx +++ b/src/app/components/ATIAnalytics/canonical/index.test.tsx @@ -22,14 +22,13 @@ describe('Canonical ATI Analytics', () => { it('calls atiBaseURL and sendBeacon with required params', () => { const expectedUrl = `${atiBaseUrl}${mockPageviewParams}`; - const reverbConfig = undefined; act(() => { render(); }); expect(mockSendBeacon).toHaveBeenCalledTimes(1); - expect(mockSendBeacon).toHaveBeenCalledWith(expectedUrl, reverbConfig); + expect(mockSendBeacon).toHaveBeenCalledWith(expectedUrl); }); it('should render lite Helmet script when isLite is true', () => { diff --git a/src/app/components/ATIAnalytics/canonical/index.tsx b/src/app/components/ATIAnalytics/canonical/index.tsx index f34dbc15b83..394b0bd4c83 100644 --- a/src/app/components/ATIAnalytics/canonical/index.tsx +++ b/src/app/components/ATIAnalytics/canonical/index.tsx @@ -50,22 +50,17 @@ const addLiteScript = (atiPageViewUrlString: string) => { ); }; -const CanonicalATIAnalytics = ({ - pageviewParams, - reverbParams, -}: ATIAnalyticsProps) => { +const CanonicalATIAnalytics = ({ pageviewParams }: ATIAnalyticsProps) => { const { isLite } = useContext(RequestContext); const atiPageViewUrlString = getEnvConfig().SIMORGH_ATI_BASE_URL + pageviewParams; - const [reverbBeaconConfig] = useState(reverbParams); - const [atiPageViewUrl] = useState(atiPageViewUrlString); useEffect(() => { - if (!isOperaProxy()) sendBeacon(atiPageViewUrl, reverbBeaconConfig); - }, [atiPageViewUrl, reverbBeaconConfig]); + if (!isOperaProxy()) sendBeacon(atiPageViewUrl); + }, [atiPageViewUrl]); return ( <> diff --git a/src/app/components/ATIAnalytics/index.test.tsx b/src/app/components/ATIAnalytics/index.test.tsx index 0b5f2ace134..b321d344a59 100644 --- a/src/app/components/ATIAnalytics/index.test.tsx +++ b/src/app/components/ATIAnalytics/index.test.tsx @@ -9,8 +9,6 @@ import { setWindowValue, resetWindowValue, } from '#psammead/psammead-test-helpers/src'; -import { ServiceContext } from '#contexts/ServiceContext'; -import { ServiceConfig } from '#models/types/serviceConfig'; import styAssetData from './fixtures/storyPage.json'; import pglAssetData from './fixtures/photoGalleryPage.json'; import mapAssetData from './fixtures/mediaAssetPage.json'; diff --git a/src/app/components/ATIAnalytics/index.tsx b/src/app/components/ATIAnalytics/index.tsx index 9992f269f48..26dcbf18730 100644 --- a/src/app/components/ATIAnalytics/index.tsx +++ b/src/app/components/ATIAnalytics/index.tsx @@ -4,40 +4,28 @@ import { ServiceContext } from '../../contexts/ServiceContext'; import CanonicalATIAnalytics from './canonical'; import AmpATIAnalytics from './amp'; import { ATIProps } from './types'; -import { buildATIUrl, buildReverbParams } from './params'; +import buildATIUrl from './params'; const ATIAnalytics = ({ data, atiData }: ATIProps) => { const requestContext = useContext(RequestContext); const serviceContext = useContext(ServiceContext); const { isAmp } = requestContext; - const { useReverb } = serviceContext; - const urlPageViewParams = buildATIUrl({ + const pageviewParams = buildATIUrl({ requestContext, serviceContext, data, atiData, }) as string; - const reverbParams = useReverb - ? buildReverbParams({ - requestContext, - serviceContext, - atiData: atiData || {}, - }) - : null; - - if (!urlPageViewParams) { + if (!pageviewParams) { return null; } return isAmp ? ( - + ) : ( - + ); }; diff --git a/src/app/components/ATIAnalytics/params/buildParams/index.test.ts b/src/app/components/ATIAnalytics/params/buildParams/index.test.ts index d6e857fc9e3..918fa932a07 100644 --- a/src/app/components/ATIAnalytics/params/buildParams/index.test.ts +++ b/src/app/components/ATIAnalytics/params/buildParams/index.test.ts @@ -23,7 +23,6 @@ const requestContext: RequestContextProps = { const serviceContext: ServiceConfig = { atiAnalyticsAppName: 'atiAnalyticsAppName', atiAnalyticsProducerId: 'atiAnalyticsProducerId', - atiAnalyticsProducerName: 'atiAnalyticsProducerName', service: 'pidgin', lang: 'pcm', }; @@ -54,7 +53,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'pidgin', statsDestination: 'statsDestination', timePublished: undefined, @@ -151,7 +149,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: 'previousPath', producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'burmese', statsDestination: 'statsDestination', timePublished: '2023-07-13T05:03:56.214Z', @@ -237,7 +234,7 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { const result = buildPageATIParams({ atiData: { ...articlePageAtiData, - ampExperimentName: 'someAmpExperiment', + ampExperimentName: 'topStoriesExperiment', }, requestContext: { ...requestContext, @@ -250,7 +247,7 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { }); expect(result).toEqual({ ...validPageURLParams, - ampExperimentName: 'someAmpExperiment', + ampExperimentName: 'topStoriesExperiment', }); }); }); @@ -290,7 +287,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: 'previousPath', producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'hausa', statsDestination: 'statsDestination', timePublished: '2023-07-11T17:42:48.771Z', @@ -383,7 +379,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'pidgin', statsDestination: 'statsDestination', timePublished: undefined, @@ -470,7 +465,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'pidgin', statsDestination: 'statsDestination', timePublished: '2023-08-01T12:00:00Z', @@ -586,7 +580,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'mundo', statsDestination: 'statsDestination', timePublished: '2023-02-10T02:00:41.000Z', @@ -701,7 +694,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'mundo', statsDestination: 'statsDestination', timePublished: '2017-09-14T14:09:14.000Z', @@ -814,7 +806,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'mundo', statsDestination: 'statsDestination', timePublished: '2016-08-07T09:21:02.000Z', @@ -922,7 +913,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: '64', - producerName: 'atiAnalyticsProducerName', service: 'news', statsDestination: 'statsDestination', timePublished: '2021-03-05T13:37:50.000Z', @@ -1052,7 +1042,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'urdu', statsDestination: 'statsDestination', timePublished: '2020-01-31T08:48:32.000Z', @@ -1138,7 +1127,6 @@ describe('implementation of buildPageATIParams and buildPageATIUrl', () => { platform: 'canonical', previousPath: undefined, producerId: 'atiAnalyticsProducerId', - producerName: 'atiAnalyticsProducerName', service: 'serbian', statsDestination: 'statsDestination', timePublished: '2018-01-19T14:09:41.000Z', diff --git a/src/app/components/ATIAnalytics/params/buildParams/index.ts b/src/app/components/ATIAnalytics/params/buildParams/index.ts index f418bc8e0b5..447a45c06f6 100644 --- a/src/app/components/ATIAnalytics/params/buildParams/index.ts +++ b/src/app/components/ATIAnalytics/params/buildParams/index.ts @@ -1,5 +1,5 @@ import { LIBRARY_VERSION } from '../../../../lib/analyticsUtils'; -import { buildATIPageTrackPath, buildReverbAnalyticsModel } from '../../atiUrl'; +import { buildATIPageTrackPath } from '../../atiUrl'; import { ATIDataWithContexts } from '../../types'; export const buildPageATIParams = ({ @@ -9,13 +9,8 @@ export const buildPageATIParams = ({ }: ATIDataWithContexts) => { const { isUK, origin, platform, previousPath, statsDestination } = requestContext; - const { - atiAnalyticsAppName, - atiAnalyticsProducerId, - atiAnalyticsProducerName, - lang, - service, - } = serviceContext; + const { atiAnalyticsAppName, atiAnalyticsProducerId, lang, service } = + serviceContext; const { campaigns, categoryName, @@ -31,7 +26,6 @@ export const buildPageATIParams = ({ timePublished, timeUpdated, ampExperimentName, - experimentVariant, } = atiData; return { @@ -52,13 +46,11 @@ export const buildPageATIParams = ({ platform, previousPath, producerId: producerId || atiAnalyticsProducerId, - producerName: atiAnalyticsProducerName, service, statsDestination, timePublished, timeUpdated, ...(ampExperimentName && { ampExperimentName }), - ...(experimentVariant && { experimentVariant }), }; }; @@ -70,12 +62,3 @@ export const buildPageATIUrl = ({ buildATIPageTrackPath( buildPageATIParams({ atiData, requestContext, serviceContext }), ); - -export const buildPageReverbParams = ({ - atiData, - requestContext, - serviceContext, -}: ATIDataWithContexts) => - buildReverbAnalyticsModel( - buildPageATIParams({ atiData, requestContext, serviceContext }), - ); diff --git a/src/app/components/ATIAnalytics/params/index.test.ts b/src/app/components/ATIAnalytics/params/index.test.ts index 0f3f14ee9b1..1208534f1b5 100644 --- a/src/app/components/ATIAnalytics/params/index.test.ts +++ b/src/app/components/ATIAnalytics/params/index.test.ts @@ -3,6 +3,7 @@ import * as analyticsUtils from '../../../lib/analyticsUtils'; import { ARTICLE_PAGE, FRONT_PAGE, + MEDIA_PAGE, MEDIA_ASSET_PAGE, PHOTO_GALLERY_PAGE, MEDIA_ARTICLE_PAGE, @@ -57,6 +58,14 @@ const frontPage: PageData = { }, }; +const media: PageData = { + id: 'id', + language: 'language', + pageIdentifier: 'pageIdentifier', + pageTitle: 'pageTitle', + contentType: 'player-live', +}; + const homePageAnalyticsData: ATIData = { contentId: 'urn:bbc:tipo:topic:cm7682qz7v1t', contentType: 'index-home', @@ -251,6 +260,36 @@ describe('ATIAnalytics params', () => { }); }); + it('should return the correct media url', () => { + const url = buildATIUrl({ + requestContext: { ...requestContext, pageType: MEDIA_PAGE }, + data: media, + serviceContext, + }); + + const parsedATIParams = Object.fromEntries( + new URLSearchParams(url as string), + ); + + expect(parsedATIParams).toEqual({ + s: '598285', + s2: 'atiAnalyticsProducerId', + p: 'pageIdentifier', + r: '0x0x24x24', + re: '1024x768', + hl: '00-00-00', + lng: 'en-US', + x1: '[id]', + x2: '[responsive]', + x3: '[atiAnalyticsAppName]', + x4: '[language]', + x5: '[http%3A%2F%2Flocalhost%2F]', + x7: '[player-live]', + x8: '[simorgh]', + x9: '[pageTitle]', + }); + }); + it('should return the correct MAP url', () => { const url = buildATIUrl({ requestContext: { ...requestContext, pageType: MEDIA_ASSET_PAGE }, @@ -431,9 +470,9 @@ describe('ATIAnalytics params', () => { ); }); - it('should not invoke buildPageATIUrl for an unmigrated page type with no atiData', () => { + it('should not invoke buildPageATIUrl for an unsupported page type with no atiData', () => { buildATIUrl({ - requestContext: { ...requestContext, pageType: FRONT_PAGE }, + requestContext: { ...requestContext, pageType: MEDIA_PAGE }, atiData: undefined, serviceContext, }); @@ -543,6 +582,27 @@ describe('ATIAnalytics params', () => { }); }); + it('should return the correct media params', () => { + const params = buildATIEventTrackingParams({ + requestContext: { ...requestContext, pageType: MEDIA_PAGE }, + data: media, + serviceContext, + }); + expect(params).toEqual({ + appName: 'atiAnalyticsAppName', + contentId: 'id', + contentType: 'player-live', + language: 'language', + pageIdentifier: 'pageIdentifier', + pageTitle: 'pageTitle', + libraryVersion: 'simorgh', + platform: 'canonical', + producerId: 'atiAnalyticsProducerId', + service: 'pidgin', + statsDestination: 'statsDestination', + }); + }); + it('should return the correct MAP params', () => { const params = buildATIEventTrackingParams({ requestContext: { ...requestContext, pageType: MEDIA_ASSET_PAGE }, @@ -683,15 +743,60 @@ describe('ATIAnalytics params', () => { ); }); - it('should not invoke buildPageATIParams for an unmigrated page type with no atiData', () => { + it('should not invoke buildPageATIParams for an unsupported page type with no atiData', () => { buildATIEventTrackingParams({ - requestContext: { ...requestContext, pageType: FRONT_PAGE }, + requestContext: { ...requestContext, pageType: MEDIA_PAGE }, atiData: undefined, serviceContext, }); + expect(console.error) + .toHaveBeenCalledWith(`ATI Event Tracking Error: Could not parse tracking values from page data: +Cannot read properties of undefined (reading 'id')`); expect(buildPageATIParamsSpy).not.toHaveBeenCalled(); }); }); + + it('should not throw exception and return empty object if no pageData is passed in', () => { + const { error } = console; + console.error = jest.fn(); + + const pageData = null; + const params = buildATIEventTrackingParams({ + requestContext: { ...requestContext, pageType: MEDIA_PAGE }, + // @ts-expect-error - pass in null value to ensure error handling working as expected + data: pageData, + serviceContext, + }); + + expect(params).toEqual({}); + expect(console.error).toHaveBeenCalledWith( + expect.stringContaining( + 'ATI Event Tracking Error: Could not parse tracking values from page data:', + ), + ); + console.error = error; + }); + + it('should not throw exception and return empty object if no atiData is passed in', () => { + const { error } = console; + console.error = jest.fn(); + + const atiData = null; + const params = buildATIEventTrackingParams({ + requestContext: { ...requestContext, pageType: MEDIA_PAGE }, + // @ts-expect-error - pass in null value to ensure error handling working as expected + atiData, + serviceContext, + }); + + expect(params).toEqual({}); + expect(console.error).toHaveBeenCalledWith( + expect.stringContaining( + 'ATI Event Tracking Error: Could not parse tracking values from page data:', + ), + ); + console.error = error; + }); }); }); diff --git a/src/app/components/ATIAnalytics/params/index.ts b/src/app/components/ATIAnalytics/params/index.ts index 19624ac6e2a..17eddfb1775 100644 --- a/src/app/components/ATIAnalytics/params/index.ts +++ b/src/app/components/ATIAnalytics/params/index.ts @@ -10,6 +10,7 @@ import { FEATURE_INDEX_PAGE, MOST_READ_PAGE, PHOTO_GALLERY_PAGE, + MEDIA_PAGE, ERROR_PAGE, LIVE_PAGE, CPS_ASSET, @@ -22,10 +23,10 @@ import { TV_PAGE, } from '../../../routes/utils/pageTypes'; import { - buildPageATIUrl, - buildPageATIParams, - buildPageReverbParams, -} from './buildParams'; + buildTvRadioATIParams, + buildTvRadioATIUrl, +} from './tvRadioPage/buildParams'; +import { buildPageATIUrl, buildPageATIParams } from './buildParams'; import { buildIndexPageATIParams, buildIndexPageATIUrl, @@ -36,7 +37,6 @@ import { PageData, ATIPageTrackingProps, ATIConfigurationDetailsProviders, - ReverbDetailsProviders, } from '../types'; import { PageTypes } from '../../../models/types/global'; @@ -68,6 +68,7 @@ const pageTypeUrlBuilders = { [MEDIA_ARTICLE_PAGE]: noOp, [STORY_PAGE]: noOp, [FRONT_PAGE]: buildIndexPageATIUrl, + [MEDIA_PAGE]: buildTvRadioATIUrl, [MOST_READ_PAGE]: noOp, [FEATURE_INDEX_PAGE]: noOp, [TOPIC_PAGE]: noOp, @@ -91,6 +92,7 @@ const pageTypeParamBuilders = { [ARTICLE_PAGE]: noOp, [MEDIA_ARTICLE_PAGE]: noOp, [FRONT_PAGE]: buildIndexPageATIParams, + [MEDIA_PAGE]: buildTvRadioATIParams, [MOST_READ_PAGE]: noOp, [FEATURE_INDEX_PAGE]: noOp, [TOPIC_PAGE]: noOp, @@ -121,7 +123,7 @@ type BuilderFunction = { }; type PageTypeHandlers = { - [_key in PageTypes]: BuilderFunction; + [key in PageTypes]: BuilderFunction; }; const isMigrated = (pageType: PageTypes) => @@ -159,33 +161,38 @@ export const buildATIUrl = ({ return null; }; -export const buildReverbParams = ({ - requestContext, - serviceContext, - atiData, -}: ReverbDetailsProviders) => { - return buildPageReverbParams({ atiData, requestContext, serviceContext }); -}; - export const buildATIEventTrackingParams = ({ requestContext, serviceContext, data, atiData, }: ATIConfigurationDetailsProviders) => { - const { pageType } = requestContext; - if (atiData && isMigrated(pageType)) { - return buildPageATIParams({ - atiData, + try { + const { pageType } = requestContext; + if (atiData && isMigrated(pageType)) { + return buildPageATIParams({ + atiData, + requestContext, + serviceContext, + }); + } + + const buildParams = createBuilderFactory( requestContext, - serviceContext, - }); - } + pageTypeParamBuilders, + ); - const buildParams = createBuilderFactory( - requestContext, - pageTypeParamBuilders, - ); + return buildParams(data as PageData, requestContext, serviceContext); + } catch (error: unknown) { + const { message } = error as Error; - return buildParams(data as PageData, requestContext, serviceContext); + // eslint-disable-next-line no-console + console.error( + `ATI Event Tracking Error: Could not parse tracking values from page data:\n${message}`, + ); + + return {}; + } }; + +export default buildATIUrl; diff --git a/src/app/components/ATIAnalytics/types.ts b/src/app/components/ATIAnalytics/types.ts index 14e4dda85a0..04d4fc06652 100644 --- a/src/app/components/ATIAnalytics/types.ts +++ b/src/app/components/ATIAnalytics/types.ts @@ -31,7 +31,6 @@ export interface ATIData { timePublished?: string | null; timeUpdated?: string | null; ampExperimentName?: string; - experimentVariant?: string | null; } export interface PageData { @@ -93,16 +92,9 @@ export interface ATIConfigurationDetailsProviders { atiData?: ATIData; } -export interface ReverbDetailsProviders { - requestContext: RequestContextProps; - serviceContext: ServiceConfig; - atiData: ATIData; -} - export interface ATIAnalyticsProps { baseUrl?: string; pageviewParams: string; - reverbParams?: object | null; } export interface ATIEventTrackingProps { @@ -112,14 +104,12 @@ export interface ATIEventTrackingProps { pageIdentifier?: string; platform?: Platforms; producerId?: string; - producerName?: string; service?: Services; statsDestination?: string; type?: string; advertiserID?: string; url?: string; detailedPlacement?: string; - useReverb?: boolean; experimentVariant?: string; ampExperimentName?: string; } @@ -134,7 +124,6 @@ export interface ATIPageTrackingProps { pageIdentifier?: string; pageTitle?: string | null; producerId?: string; - producerName?: string; libraryVersion?: string; platform?: Platforms; statsDestination?: string; @@ -146,7 +135,6 @@ export interface ATIPageTrackingProps { campaigns?: { campaignId?: string; campaignName?: string }[] | null; nationsProducer?: string | null; ampExperimentName?: string; - experimentVariant?: string | null; } export interface ATIProps { diff --git a/src/app/components/Ad/Amp/AdSlot/index.tsx b/src/app/components/Ad/Amp/AdSlot/index.tsx index ff0d851b6bd..f5188aedeb1 100644 --- a/src/app/components/Ad/Amp/AdSlot/index.tsx +++ b/src/app/components/Ad/Amp/AdSlot/index.tsx @@ -68,8 +68,8 @@ interface DeviceSettings { } const slotConfigurations: { - [_slot in SlotType]: { - [_device in Device]?: DeviceSettings; + [slot in SlotType]: { + [device in Device]?: DeviceSettings; }; } = { leaderboard: { diff --git a/src/app/components/AmpExperiment/README.md b/src/app/components/AmpExperiment/README.md deleted file mode 100644 index 2dc6d80414e..00000000000 --- a/src/app/components/AmpExperiment/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# AMP Experiment - -This directory contains code for a generic wrapper component that contains an `` and an ``. This component should be used when running experiments on AMP pages. - - -## Usage -This component can be placed anywhere on a page that the experiment runs on and does not render any visual components. - -It accepts `experimentConfig` and `analyticsConfig` as props. These are objects that should be formatted as such: - -**`experimentConfig`** -``` -{ - "experimentName": { - "variants": { - "control": 12.5, - "variant_1": 12.5, - "variant_2": 25.0 - } - } -} -``` - -**`analyticsConfig`** -``` -{ - "requests": { // Endpoints to send requests to - "request-name": request-value, - ... - }, - "triggers": { // Triggers for when to send events - "trigger-name": trigger-object, - } -} -``` - -## Analytics configuration -This component is designed to have a separate `` component from existing ones on a page for better isolation of code/events specific to a given experiment. - -**Note: This component will not add AMP experiment name/variant information to pageview events. This should be done separately by passing `ampExperimentName` to the `atiData` used.** diff --git a/src/app/components/AmpExperiment/index.test.tsx b/src/app/components/AmpExperiment/index.test.tsx index f176673934f..a9a1ae55353 100644 --- a/src/app/components/AmpExperiment/index.test.tsx +++ b/src/app/components/AmpExperiment/index.test.tsx @@ -3,7 +3,7 @@ import { render, waitFor } from '../react-testing-library-with-providers'; import AmpExperiment from './index'; const experimentConfig = { - someAmpExperiment: { + someExperiment: { variants: { control: 33, variant_1: 33, @@ -55,7 +55,7 @@ describe('Amp experiment container on Amp pages', () => { @@ -94,7 +94,7 @@ describe('Amp experiment container on Amp pages', () => { css({ textDecoration: 'none', - display: 'block', color: palette.WHITE, cursor: 'pointer', '&:hover, &:focus': { diff --git a/src/app/components/Billboard/index.tsx b/src/app/components/Billboard/index.tsx index fe4d64066a4..6321d802444 100644 --- a/src/app/components/Billboard/index.tsx +++ b/src/app/components/Billboard/index.tsx @@ -6,11 +6,10 @@ import useViewTracker from '#app/hooks/useViewTracker'; import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler'; import { EventTrackingMetadata } from '#app/models/types/eventTracking'; import Heading from '../Heading'; +import LiveLabel from '../LiveLabel'; import MaskedImage from '../MaskedImage'; import styles from './index.styles'; import Text from '../Text'; -import LivePulse from '../LivePulse'; -import LiveText from '../LiveText'; interface BillboardProps { heading: string; @@ -53,20 +52,25 @@ const Billboard = forwardRef( />
- + {showLiveLabel ? (
- - +
{heading}
-
+
) : ( -
{heading}
+ heading )}
diff --git a/src/app/components/ChartbeatAnalytics/utils/index.test.ts b/src/app/components/ChartbeatAnalytics/utils/index.test.ts index 75fb5252f96..ec5e726ede9 100644 --- a/src/app/components/ChartbeatAnalytics/utils/index.test.ts +++ b/src/app/components/ChartbeatAnalytics/utils/index.test.ts @@ -3,6 +3,7 @@ import onClient from '../../../lib/utilities/onClient'; import { ARTICLE_PAGE, FRONT_PAGE, + MEDIA_PAGE, MOST_READ_PAGE, FEATURE_INDEX_PAGE, MEDIA_ASSET_PAGE, @@ -83,6 +84,11 @@ describe('Chartbeat utilities', () => { expectedDefaultType: 'article-media-asset', expectedShortType: 'article-media-asset', }, + { + pageType: MEDIA_PAGE, + expectedDefaultType: 'Radio', + expectedShortType: 'Radio', + }, { pageType: AUDIO_PAGE, expectedDefaultType: 'Radio', @@ -297,6 +303,7 @@ describe('Chartbeat utilities', () => { ${MOST_READ_PAGE} | ${'Most Read Page Title'} | ${'BBC News Pidgin'} | ${'Most Read Page Title - BBC News Pidgin'} ${TOPIC_PAGE} | ${'Topic Page Title'} | ${'BBC News Pidgin'} | ${'Topic Page Title - BBC News Pidgin'} ${LIVE_PAGE} | ${'Live Page Title'} | ${'BBC News Pidgin'} | ${'Live Page Title - BBC News Pidgin'} + ${MEDIA_PAGE} | ${'Media Page Title'} | ${'BBC News Pidgin'} | ${'Media Page Title - BBC News Pidgin'} ${AUDIO_PAGE} | ${'Audio Page Title'} | ${'BBC News Pidgin'} | ${'Audio Page Title - BBC News Pidgin'} ${TV_PAGE} | ${'TV Page Title'} | ${'BBC News Pidgin'} | ${'TV Page Title - BBC News Pidgin'} ${'index'} | ${'index Page Title'} | ${'BBC News Pidgin'} | ${'index Page Title - BBC News Pidgin'} diff --git a/src/app/components/ChartbeatAnalytics/utils/index.ts b/src/app/components/ChartbeatAnalytics/utils/index.ts index c3f833f382e..a97066209db 100644 --- a/src/app/components/ChartbeatAnalytics/utils/index.ts +++ b/src/app/components/ChartbeatAnalytics/utils/index.ts @@ -7,6 +7,7 @@ import { getReferrer } from '../../../lib/analyticsUtils'; import { ARTICLE_PAGE, FRONT_PAGE, + MEDIA_PAGE, MOST_READ_PAGE, FEATURE_INDEX_PAGE, MEDIA_ASSET_PAGE, @@ -33,7 +34,7 @@ export const chartbeatUID = 50924; export const useCanonical = true; export const chartbeatSource = '//static.chartbeat.com/js/chartbeat.js'; -const capitalize = (s = '') => `${s?.charAt(0).toUpperCase()}${s?.slice(1)}`; +const capitalize = (s: string) => s?.charAt(0).toUpperCase() + s?.slice(1); const buildSectionArr = (service: Services, value: string, type: string) => [ `${capitalize(service)} - ${value}`, @@ -60,6 +61,7 @@ export const getType = (pageType: PageTypes | 'index', shorthand = false) => { return 'article-media-asset'; case LIVE_RADIO_PAGE: case AUDIO_PAGE: + case MEDIA_PAGE: return 'Radio'; case TV_PAGE: return 'TV'; @@ -148,6 +150,7 @@ export const buildSections = ({ : []), ].join(', '); case LIVE_RADIO_PAGE: + case MEDIA_PAGE: case AUDIO_PAGE: case TV_PAGE: return [ @@ -187,6 +190,7 @@ export const getTitle = ({ pageType, title, brandName }: GetTitleProps) => { case TOPIC_PAGE: case LIVE_PAGE: case LIVE_RADIO_PAGE: + case MEDIA_PAGE: case AUDIO_PAGE: case TV_PAGE: case 'index': diff --git a/src/app/components/Curation/CurationPromo/index.tsx b/src/app/components/Curation/CurationPromo/index.tsx index a24c0717e1d..3cd2f99cdf1 100644 --- a/src/app/components/Curation/CurationPromo/index.tsx +++ b/src/app/components/Curation/CurationPromo/index.tsx @@ -62,7 +62,11 @@ const CurationPromo = ({ )} {isMedia ? ( - + {typeTranslated} @@ -74,7 +78,7 @@ const CurationPromo = ({ ) : ( - + {isLive ? {title} : title} )} diff --git a/src/app/components/Curation/HierarchicalGrid/index.tsx b/src/app/components/Curation/HierarchicalGrid/index.tsx index 4bc83fb4df8..fcba2d918e2 100644 --- a/src/app/components/Curation/HierarchicalGrid/index.tsx +++ b/src/app/components/Curation/HierarchicalGrid/index.tsx @@ -107,7 +107,11 @@ const HiearchicalGrid = ({ })} > {isMedia ? ( - + {typeTranslated} @@ -121,7 +125,10 @@ const HiearchicalGrid = ({ ) : ( - + {isLive ? ( ( // eslint-disable-next-line react/no-array-index-key -
  • {elem}
  • +
  • + {elem} +
  • ))} ); diff --git a/src/app/components/Footer/index.test.tsx b/src/app/components/Footer/index.test.tsx index 5faf165f1be..4bf48529faa 100644 --- a/src/app/components/Footer/index.test.tsx +++ b/src/app/components/Footer/index.test.tsx @@ -21,14 +21,6 @@ describe('Footer', () => { '#', ); }); - - it('should not render an empty list item when the "Do not share or sell my info" link is not present', () => { - render(