diff --git a/adminSiteClient/gdocsValidation.ts b/adminSiteClient/gdocsValidation.ts index c8ea4077ec3..cc3d2e8458b 100644 --- a/adminSiteClient/gdocsValidation.ts +++ b/adminSiteClient/gdocsValidation.ts @@ -5,7 +5,7 @@ import { OwidGdocErrorMessageType, OwidGdocType, checkIsOwidGdocType, - traverseEnrichedBlocks, + traverseEnrichedBlock, OwidGdocErrorMessageProperty, OwidGdoc, checkIsGdocPost, @@ -58,7 +58,7 @@ function validateBody(gdoc: OwidGdoc, errors: OwidGdocErrorMessage[]) { errors.push(getMissingContentPropertyError("body")) } else { for (const block of gdoc.content.body) { - traverseEnrichedBlocks(block, (block) => { + traverseEnrichedBlock(block, (block) => { errors.push( ...block.parseErrors.map((parseError) => ({ message: parseError.message, @@ -86,7 +86,7 @@ function validateRefs( if (gdoc.content.refs.definitions) { Object.values(gdoc.content.refs.definitions).map((definition) => { definition.content.map((block) => { - traverseEnrichedBlocks(block, (node) => { + traverseEnrichedBlock(block, (node) => { if (node.parseErrors.length) { for (const parseError of node.parseErrors) { errors.push({ diff --git a/baker/GrapherBaker.tsx b/baker/GrapherBaker.tsx index 0b29e38753f..6a4a6da548a 100644 --- a/baker/GrapherBaker.tsx +++ b/baker/GrapherBaker.tsx @@ -11,7 +11,6 @@ import { keyBy, mergePartialGrapherConfigs, compact, - merge, partition, } from "@ourworldindata/utils" import fs from "fs-extra" @@ -38,7 +37,6 @@ import { DimensionProperty, OwidVariableWithSource, OwidChartDimensionInterface, - OwidGdocPostInterface, EnrichedFaq, FaqEntryData, FaqDictionary, @@ -73,6 +71,15 @@ const renderDatapageIfApplicable = async ( if (!variable) return undefined + // When baking from `bakeSingleGrapherChart`, we cache imageMetadata to avoid fetching every image for every chart + // But when rendering a datapage from the mockSiteRouter we want to be able to fetch imageMetadata on the fly + // And this function is the point in the two paths where it makes sense to do so + if (!imageMetadataDictionary) { + imageMetadataDictionary = await getAllImages(knex).then((images) => + keyBy(images, "filename") + ) + } + return await renderDataPageV2( { variableId: variable.id, @@ -168,23 +175,6 @@ export async function renderDataPageV2( gdocIdToFragmentIdToBlock[gdoc.id] = faqs.faqs }) - const linkedCharts: OwidGdocPostInterface["linkedCharts"] = merge( - {}, - ...compact(gdocs.map((gdoc) => gdoc?.linkedCharts)) - ) - const linkedDocuments: OwidGdocPostInterface["linkedDocuments"] = merge( - {}, - ...compact(gdocs.map((gdoc) => gdoc?.linkedDocuments)) - ) - const imageMetadata: OwidGdocPostInterface["imageMetadata"] = merge( - {}, - imageMetadataDictionary, - ...compact(gdocs.map((gdoc) => gdoc?.imageMetadata)) - ) - const relatedCharts: OwidGdocPostInterface["relatedCharts"] = gdocs.flatMap( - (gdoc) => gdoc?.relatedCharts ?? [] - ) - const resolvedFaqsResults: EnrichedFaqLookupResult[] = variableMetadata .presentation?.faqs ? variableMetadata.presentation.faqs.map((faq) => { @@ -219,10 +209,6 @@ export async function renderDataPageV2( } const faqEntries: FaqEntryData = { - linkedCharts, - linkedDocuments, - imageMetadata, - relatedCharts, faqs: resolvedFaqs?.flatMap((faq) => faq.enrichedFaq.content) ?? [], } @@ -300,6 +286,15 @@ export async function renderDataPageV2( datapageData.relatedResearch = await getRelatedResearchAndWritingForVariable(knex, variableId) + const relatedResearchFilenames = datapageData.relatedResearch + .map((r) => r.imageUrl) + .filter((f): f is string => !!f) + + const imageMetadata = lodash.pick( + imageMetadataDictionary, + uniq(relatedResearchFilenames) + ) + const tagToSlugMap = await getTagToSlugMap(knex) return renderToHtmlPage( @@ -309,6 +304,7 @@ export async function renderDataPageV2( baseUrl={BAKED_BASE_URL} baseGrapherUrl={BAKED_GRAPHER_URL} isPreviewing={isPreviewing} + imageMetadata={imageMetadata} faqEntries={faqEntries} tagToSlugMap={tagToSlugMap} /> diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 1f314a7ab07..6459cf362fe 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -9,10 +9,9 @@ import { OwidGdocErrorMessage, OwidGdocErrorMessageType, excludeNullish, - traverseEnrichedBlocks, + traverseEnrichedBlock, OwidEnrichedGdocBlock, Span, - EnrichedBlockResearchAndWritingLink, traverseEnrichedSpan, uniq, identity, @@ -42,6 +41,7 @@ import { import { EXPLORERS_ROUTE_FOLDER } from "../../../explorer/ExplorerConstants.js" import { match, P } from "ts-pattern" import { + extractFilenamesFromBlock, extractUrl, getAllLinksFromResearchAndWritingBlock, spansToSimpleString, @@ -137,45 +137,7 @@ export class GdocBase implements OwidGdocBaseInterface { for (const enrichedBlockSource of this.enrichedBlockSources) { enrichedBlockSource.forEach((block) => - traverseEnrichedBlocks(block, (item) => { - if ("type" in item) { - if ("filename" in item && item.filename) { - filenames.add(item.filename) - } - if (item.type === "image" && item.smallFilename) { - filenames.add(item.smallFilename) - } - if (item.type === "prominent-link" && item.thumbnail) { - filenames.add(item.thumbnail) - } - if (item.type === "research-and-writing") { - const allLinks = - getAllLinksFromResearchAndWritingBlock(item) - allLinks.forEach( - (link: EnrichedBlockResearchAndWritingLink) => { - if (link.value.filename) { - filenames.add(link.value.filename) - } - } - ) - } - if (item.type === "key-insights") { - item.insights.forEach((insight) => { - if (insight.filename) { - filenames.add(insight.filename) - } - }) - } - if (item.type === "homepage-intro") { - item.featuredWork.forEach((featuredWork) => { - if (featuredWork.filename) { - filenames.add(featuredWork.filename) - } - }) - } - } - return item - }) + traverseEnrichedBlock(block, extractFilenamesFromBlock) ) } @@ -187,7 +149,7 @@ export class GdocBase implements OwidGdocBaseInterface { for (const enrichedBlockSource of this.enrichedBlockSources) { enrichedBlockSource.forEach((block) => - traverseEnrichedBlocks( + traverseEnrichedBlock( block, (x) => x, (span) => { @@ -220,7 +182,7 @@ export class GdocBase implements OwidGdocBaseInterface { for (const enrichedBlockSource of this.enrichedBlockSources) { enrichedBlockSource.forEach((block) => - traverseEnrichedBlocks( + traverseEnrichedBlock( block, (block) => { const extractedLinks = this.extractLinksFromBlock(block) @@ -261,7 +223,7 @@ export class GdocBase implements OwidGdocBaseInterface { const slugs = new Set() for (const enrichedBlockSource of this.enrichedBlockSources) { for (const block of enrichedBlockSource) { - traverseEnrichedBlocks(block, (block) => { + traverseEnrichedBlock(block, (block) => { if (block.type === "key-indicator") { slugs.add(urlToSlug(block.datapageUrl)) } @@ -295,7 +257,7 @@ export class GdocBase implements OwidGdocBaseInterface { for (const enrichedBlockSource of this.enrichedBlockSources) { for (const block of enrichedBlockSource) { if (hasAllChartsBlock) break - traverseEnrichedBlocks(block, (block) => { + traverseEnrichedBlock(block, (block) => { if (block.type === "all-charts") { hasAllChartsBlock = true } @@ -438,7 +400,7 @@ export class GdocBase implements OwidGdocBaseInterface { .with({ type: "key-insights" }, (block) => { const links: DbInsertPostGdocLink[] = [] - // insights content is traversed by traverseEnrichedBlocks + // insights content is traversed by traverseEnrichedBlock block.insights.forEach((insight) => { if (insight.url) { const insightLink = createLinkFromUrl({ @@ -535,7 +497,7 @@ export class GdocBase implements OwidGdocBaseInterface { .with( { // no urls directly on any of these blocks - // their children may contain urls, but they'll be addressed by traverseEnrichedBlocks + // their children may contain urls, but they'll be addressed by traverseEnrichedBlock type: P.union( "additional-charts", "align", @@ -793,7 +755,7 @@ export class GdocBase implements OwidGdocBaseInterface { const contentErrors: OwidGdocErrorMessage[] = [] for (const enrichedBlockSource of this.enrichedBlockSources) { enrichedBlockSource.forEach((block) => - traverseEnrichedBlocks(block, (block) => { + traverseEnrichedBlock(block, (block) => { if (block.type === "key-indicator" && block.datapageUrl) { const slug = urlToSlug(block.datapageUrl) const linkedChart = this.linkedCharts?.[slug] diff --git a/db/model/Gdoc/archieToEnriched.ts b/db/model/Gdoc/archieToEnriched.ts index e2cba5f4d20..55dc94e93fb 100644 --- a/db/model/Gdoc/archieToEnriched.ts +++ b/db/model/Gdoc/archieToEnriched.ts @@ -12,7 +12,7 @@ import { EnrichedBlockSimpleText, lowercaseObjectKeys, OwidEnrichedGdocBlock, - traverseEnrichedBlocks, + traverseEnrichedBlock, ALL_CHARTS_ID, KEY_INSIGHTS_ID, ENDNOTES_ID, @@ -137,7 +137,7 @@ export function generateToc( const toc: TocHeadingWithTitleSupertitle[] = [] body.forEach((block) => - traverseEnrichedBlocks(block, (child) => { + traverseEnrichedBlock(block, (child) => { if (child.type === "heading") { const { level, text, supertitle } = child const titleString = spansToSimpleString(text) diff --git a/db/model/Gdoc/gdocUtils.ts b/db/model/Gdoc/gdocUtils.ts index f35bf2b2c07..0c9f32abff8 100644 --- a/db/model/Gdoc/gdocUtils.ts +++ b/db/model/Gdoc/gdocUtils.ts @@ -4,6 +4,8 @@ import { excludeNullish, EnrichedBlockResearchAndWritingLink, DATA_INSIGHTS_INDEX_PAGE_SIZE, + OwidEnrichedGdocBlock, + noop, } from "@ourworldindata/utils" import { match, P } from "ts-pattern" import cheerio from "cheerio" @@ -162,3 +164,93 @@ export function calculateDataInsightIndexPageCount( ): number { return Math.ceil(publishedDataInsightCount / DATA_INSIGHTS_INDEX_PAGE_SIZE) } + +export function extractFilenamesFromBlock( + item: OwidEnrichedGdocBlock +): string[] { + const filenames = new Set() + match(item) + .with({ type: "image" }, (item) => { + if (item.filename) filenames.add(item.filename) + if (item.smallFilename) filenames.add(item.smallFilename) + }) + .with({ type: "prominent-link" }, (item) => { + if (item.thumbnail) filenames.add(item.thumbnail) + }) + .with({ type: "video" }, (item) => { + if (item.filename) filenames.add(item.filename) + }) + .with({ type: "research-and-writing" }, (item) => { + getAllLinksFromResearchAndWritingBlock(item).forEach( + (link: EnrichedBlockResearchAndWritingLink) => { + if (link.value.filename) { + filenames.add(link.value.filename) + } + } + ) + }) + .with({ type: "key-insights" }, (item) => { + item.insights.forEach((insight) => { + if (insight.filename) { + filenames.add(insight.filename) + } + }) + }) + .with( + { + type: "homepage-intro", + }, + (item) => { + item.featuredWork.forEach((featuredWork) => { + if (featuredWork.filename) { + filenames.add(featuredWork.filename) + } + }) + } + ) + .with( + { + type: P.union( + "additional-charts", + "align", + "all-charts", + "aside", + "blockquote", + "callout", + "chart-story", + "chart", + "entry-summary", + "expandable-paragraph", + "explorer-tiles", + "gray-section", + "heading", + "homepage-search", + "horizontal-rule", + "html", + "key-indicator-collection", + "key-indicator", + "latest-data-insights", + "list", + "missing-data", + "numbered-list", + "pill-row", + "pull-quote", + "recirc", + "scroller", + "sdg-grid", + "sdg-toc", + "side-by-side", + "simple-text", + "socials", + "sticky-left", + "sticky-right", + "table", + "text", + "topic-page-intro" + ), + }, + noop + ) + .exhaustive() + return [...filenames] +} diff --git a/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts b/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts index 3ffa2a226d9..cf5a856eeed 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts @@ -7,8 +7,8 @@ import { } from "../OwidVariable.js" import { RelatedChart } from "../grapherTypes/GrapherTypes.js" import { Static, Type } from "@sinclair/typebox" -import { OwidGdocPostInterface } from "./Gdoc.js" import { OwidEnrichedGdocBlock } from "./ArchieMlComponents.js" +import { ImageMetadata } from "./Image.js" export interface FaqLink { gdocId: string @@ -144,14 +144,8 @@ export type DataPageJson = Static export type DataPageParseError = { message: string; path?: string } -export type FaqEntryData = Pick< - OwidGdocPostInterface, - | "linkedCharts" - | "linkedIndicators" - | "linkedDocuments" - | "relatedCharts" - | "imageMetadata" -> & { +// TODO: https://github.com/owid/owid-grapher/issues/3426 +export type FaqEntryData = { faqs: OwidEnrichedGdocBlock[] } @@ -162,6 +156,7 @@ export interface DataPageV2ContentFields { isPreviewing?: boolean canonicalUrl: string tagToSlugMap: Record + imageMetadata: Record } export interface DisplaySource { label: string diff --git a/packages/@ourworldindata/utils/src/Util.test.ts b/packages/@ourworldindata/utils/src/Util.test.ts index 72850fb868c..c5395366ca8 100755 --- a/packages/@ourworldindata/utils/src/Util.test.ts +++ b/packages/@ourworldindata/utils/src/Util.test.ts @@ -27,7 +27,7 @@ import { slugifySameCase, greatestCommonDivisor, findGreatestCommonDivisorOfArray, - traverseEnrichedBlocks, + traverseEnrichedBlock, cartesian, } from "./Util.js" import { @@ -569,7 +569,7 @@ describe(findGreatestCommonDivisorOfArray, () => { }) }) -describe(traverseEnrichedBlocks, () => { +describe(traverseEnrichedBlock, () => { const enrichedBlocks: OwidEnrichedGdocBlock[] = [ { type: "prominent-link", @@ -689,7 +689,7 @@ describe(traverseEnrichedBlocks, () => { const seen: string[] = [] enrichedBlocks.forEach((block) => { - traverseEnrichedBlocks(block, (block) => { + traverseEnrichedBlock(block, (block) => { seen.push(block.type) }) }) @@ -712,7 +712,7 @@ describe(traverseEnrichedBlocks, () => { const seen: string[] = [] enrichedBlocks.forEach((block) => { - traverseEnrichedBlocks( + traverseEnrichedBlock( block, (block) => { seen.push(block.type) diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index 794d202962e..be1dc85613a 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1506,7 +1506,7 @@ export function traverseEnrichedSpan( // If your node has children that are Spans, the spanCallback will apply to them // If your node has children that aren't OwidEnrichedGdocBlocks or Spans, e.g. EnrichedBlockScroller & EnrichedScrollerItem // you'll have to handle those children yourself in your callback -export function traverseEnrichedBlocks( +export function traverseEnrichedBlock( node: OwidEnrichedGdocBlock, callback: (x: OwidEnrichedGdocBlock) => void, spanCallback?: (x: Span) => void @@ -1517,24 +1517,24 @@ export function traverseEnrichedBlocks( (container) => { callback(container) container.left.forEach((leftNode) => - traverseEnrichedBlocks(leftNode, callback, spanCallback) + traverseEnrichedBlock(leftNode, callback, spanCallback) ) container.right.forEach((rightNode) => - traverseEnrichedBlocks(rightNode, callback, spanCallback) + traverseEnrichedBlock(rightNode, callback, spanCallback) ) } ) .with({ type: "gray-section" }, (graySection) => { callback(graySection) graySection.items.forEach((node) => - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) ) }) .with({ type: "key-insights" }, (keyInsights) => { callback(keyInsights) keyInsights.insights.forEach((insight) => insight.content.forEach((node) => - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) ) ) }) @@ -1542,7 +1542,7 @@ export function traverseEnrichedBlocks( callback(callout) if (spanCallback) { callout.text.forEach((textBlock) => - traverseEnrichedBlocks(textBlock, callback, spanCallback) + traverseEnrichedBlock(textBlock, callback, spanCallback) ) } }) @@ -1558,7 +1558,7 @@ export function traverseEnrichedBlocks( callback(list) if (spanCallback) { list.items.forEach((textBlock) => - traverseEnrichedBlocks(textBlock, callback, spanCallback) + traverseEnrichedBlock(textBlock, callback, spanCallback) ) } }) @@ -1566,7 +1566,7 @@ export function traverseEnrichedBlocks( callback(numberedList) if (spanCallback) { numberedList.items.forEach((textBlock) => - traverseEnrichedBlocks(textBlock, callback, spanCallback) + traverseEnrichedBlock(textBlock, callback, spanCallback) ) } }) @@ -1604,13 +1604,13 @@ export function traverseEnrichedBlocks( .with({ type: "expandable-paragraph" }, (expandableParagraph) => { callback(expandableParagraph) expandableParagraph.items.forEach((textBlock) => { - traverseEnrichedBlocks(textBlock, callback, spanCallback) + traverseEnrichedBlock(textBlock, callback, spanCallback) }) }) .with({ type: "align" }, (align) => { callback(align) align.content.forEach((node) => { - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) }) }) .with({ type: "table" }, (table) => { @@ -1618,7 +1618,7 @@ export function traverseEnrichedBlocks( table.rows.forEach((row) => { row.cells.forEach((cell) => { cell.content.forEach((node) => { - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) }) }) }) @@ -1626,7 +1626,7 @@ export function traverseEnrichedBlocks( .with({ type: "blockquote" }, (blockquote) => { callback(blockquote) blockquote.text.forEach((node) => { - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) }) }) .with( @@ -1636,7 +1636,7 @@ export function traverseEnrichedBlocks( (keyIndicator) => { callback(keyIndicator) keyIndicator.text.forEach((node) => { - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) }) } ) @@ -1645,7 +1645,7 @@ export function traverseEnrichedBlocks( (keyIndicatorCollection) => { callback(keyIndicatorCollection) keyIndicatorCollection.blocks.forEach((node) => - traverseEnrichedBlocks(node, callback, spanCallback) + traverseEnrichedBlock(node, callback, spanCallback) ) } ) diff --git a/packages/@ourworldindata/utils/src/image.ts b/packages/@ourworldindata/utils/src/image.ts index 7a60aff6402..2b02c4fc467 100644 --- a/packages/@ourworldindata/utils/src/image.ts +++ b/packages/@ourworldindata/utils/src/image.ts @@ -2,7 +2,7 @@ Common utlities for deriving properties from image metadata. */ -import { traverseEnrichedBlocks } from "./Util.js" +import { traverseEnrichedBlock } from "./Util.js" import { OwidGdoc, OwidGdocType, ImageMetadata } from "@ourworldindata/types" import { match, P } from "ts-pattern" @@ -123,7 +123,7 @@ export function getFeaturedImageFilename(gdoc: OwidGdoc): string | undefined { // Use the first image in the document as the featured image let filename: string | undefined = undefined for (const block of gdoc.content.body) { - traverseEnrichedBlocks(block, (block) => { + traverseEnrichedBlock(block, (block) => { if (!filename && block.type === "image") { filename = block.smallFilename || block.filename } diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 9e585bbab97..3ddd92bec09 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -101,7 +101,7 @@ export { isPositiveInfinity, imemo, recursivelyMapArticleContent, - traverseEnrichedBlocks, + traverseEnrichedBlock, checkNodeIsSpan, checkNodeIsSpanLink, spansToUnformattedPlainText, diff --git a/site/DataPageV2.tsx b/site/DataPageV2.tsx index 5ac3e869382..167639b1a72 100644 --- a/site/DataPageV2.tsx +++ b/site/DataPageV2.tsx @@ -14,6 +14,7 @@ import { FaqEntryData, pick, GrapherInterface, + ImageMetadata, } from "@ourworldindata/utils" import { MarkdownTextWrap } from "@ourworldindata/components" import React from "react" @@ -40,6 +41,7 @@ export const DataPageV2 = (props: { baseGrapherUrl: string isPreviewing: boolean faqEntries?: FaqEntryData + imageMetadata: Record tagToSlugMap: Record }) => { const { @@ -50,6 +52,7 @@ export const DataPageV2 = (props: { isPreviewing, faqEntries, tagToSlugMap, + imageMetadata, } = props const pageTitle = grapher?.title ?? datapageData.title.title const canonicalUrl = grapher?.slug @@ -149,6 +152,7 @@ export const DataPageV2 = (props: { faqEntries, canonicalUrl, tagToSlugMap: minimalTagToSlugMap, + imageMetadata, } )}`, }} @@ -158,6 +162,7 @@ export const DataPageV2 = (props: { +const DatapageResearchThumbnail = ({ + urlOrFilename, +}: { + urlOrFilename: string | undefined | null +}) => { + if (!urlOrFilename) { + urlOrFilename = `${BAKED_BASE_URL}/default-thumbnail.jpg` + } + if (urlOrFilename.startsWith("http")) { + return ( + + ) + } + return ( + + ) +} + export const DataPageV2Content = ({ datapageData, grapherConfig, @@ -65,8 +90,10 @@ export const DataPageV2Content = ({ faqEntries, canonicalUrl = "{URL}", // when we bake pages to their proper url this will be set correctly but on preview pages we leave this undefined tagToSlugMap, + imageMetadata, }: DataPageV2ContentFields & { grapherConfig: GrapherInterface + imageMetadata: Record }) => { const [grapher, setGrapher] = React.useState(undefined) @@ -159,14 +186,6 @@ export const DataPageV2Content = ({ canonicalUrl ) - const { - linkedDocuments = {}, - imageMetadata = {}, - linkedCharts = {}, - linkedIndicators = {}, - relatedCharts = [], - } = faqEntries ?? {} - const adaptedFrom = producers.length > 0 ? producers.join(", ") : datapageData.source?.name @@ -186,16 +205,6 @@ export const DataPageV2Content = ({ `Retrieved from ${canonicalUrl} [online resource]`, ]).join(" ") - const getImageUrl = (research: DataPageRelatedResearch) => { - if (research.imageUrl && research.imageUrl.startsWith("http")) - return research.imageUrl - else if (!isEmpty(research.imageUrl)) - return encodeURI( - `${IMAGE_HOSTING_R2_CDN_URL}/production/${research.imageUrl}` - ) - return `${BAKED_BASE_URL}/default-thumbnail.jpg` - } - const relatedResearchCandidates = datapageData.relatedResearch const relatedResearch = relatedResearchCandidates.length > 3 && @@ -233,11 +242,11 @@ export const DataPageV2Content = ({ return ( @@ -421,24 +430,10 @@ export const DataPageV2Content = ({ key={research.url} className="related-research__item grid grid-cols-4 grid-lg-cols-6 grid-sm-cols-12 span-cols-4 span-lg-cols-6 span-sm-cols-12" > - {/*
- -
*/} - {/* // TODO: switch this to use the Image component and put the required information for the thumbnails into hte attachment context or similar */} -