diff --git a/functions/_common/grapherRenderer.ts b/functions/_common/grapherRenderer.ts index 7dd979a085a..a57afdae1be 100644 --- a/functions/_common/grapherRenderer.ts +++ b/functions/_common/grapherRenderer.ts @@ -1,6 +1,6 @@ import { svg2png, initialize as initializeSvg2Png } from "svg2png-wasm" import { TimeLogger } from "./timeLogger.js" -import { png } from "itty-router" +import { png, StatusError } from "itty-router" import svg2png_wasm from "../../node_modules/svg2png-wasm/svg2png_wasm_bg.wasm" @@ -11,7 +11,8 @@ import LatoBold from "../_common/fonts/LatoLatin-Bold.ttf.bin" import PlayfairSemiBold from "../_common/fonts/PlayfairDisplayLatin-SemiBold.ttf.bin" import { Env } from "./env.js" import { ImageOptions, extractOptions } from "./imageOptions.js" -import { GrapherIdentifier, initGrapher } from "./grapherTools.js" +import { GrapherIdentifier, initGrapher, MultiDimSlug } from "./grapherTools.js" +import { Grapher } from "@ourworldindata/grapher" declare global { // eslint-disable-next-line no-var @@ -62,7 +63,27 @@ async function fetchAndRenderGrapherToSvg( env: Env ) { const grapherLogger = new TimeLogger("grapher") - const grapher = await initGrapher(identifier, options, searchParams, env) + let grapher: Grapher + try { + grapher = await initGrapher(identifier, options, searchParams, env) + } catch (e) { + if ( + identifier.type === "slug" && + e instanceof StatusError && + e.status === 404 + ) { + // Normal graphers and multi-dims have the same URL namespace, but + // we have no way of knowing which of them was requested, so we try + // again with a multi-dim identifier. + const multiDimId: MultiDimSlug = { + type: "multi-dim-slug", + id: identifier.id, + } + grapher = await initGrapher(multiDimId, options, searchParams, env) + } else { + throw e + } + } grapherLogger.log("initGrapher") const promises = [] diff --git a/functions/_common/grapherTools.ts b/functions/_common/grapherTools.ts index 51bedac65db..b09b4a1482c 100644 --- a/functions/_common/grapherTools.ts +++ b/functions/_common/grapherTools.ts @@ -1,9 +1,14 @@ import { Grapher } from "@ourworldindata/grapher" import { GrapherInterface, + MultiDimDataPageConfigEnriched, R2GrapherConfigDirectory, } from "@ourworldindata/types" -import { excludeUndefined, Bounds } from "@ourworldindata/utils" +import { + excludeUndefined, + Bounds, + searchParamsToMultiDimView, +} from "@ourworldindata/utils" import { StatusError } from "itty-router" import { Env } from "./env.js" import { fetchFromR2, grapherBaseUrl } from "./grapherRenderer.js" @@ -83,11 +88,30 @@ export async function fetchUnparsedGrapherConfig( return fetchFromR2(requestUrl, etag, fallbackUrl) } -export async function fetchGrapherConfig( - identifier: GrapherIdentifier, - env: Env, +async function fetchMultiDimGrapherConfig( + multiDimConfig: MultiDimDataPageConfigEnriched, + searchParams: URLSearchParams, + env: Env +) { + const view = searchParamsToMultiDimView(multiDimConfig, searchParams) + const response = await fetchUnparsedGrapherConfig( + { type: "uuid", id: view.fullConfigId }, + env + ) + return await response.json() +} + +export async function fetchGrapherConfig({ + identifier, + env, + etag, + searchParams, +}: { + identifier: GrapherIdentifier + env: Env etag?: string -): Promise { + searchParams?: URLSearchParams +}): Promise { const fetchResponse = await fetchUnparsedGrapherConfig( identifier, env, @@ -113,7 +137,17 @@ export async function fetchGrapherConfig( } } - const grapherConfig: GrapherInterface = await fetchResponse.json() + const config = await fetchResponse.json() + let grapherConfig: GrapherInterface + if (identifier.type === "multi-dim-slug") { + grapherConfig = await fetchMultiDimGrapherConfig( + config as MultiDimDataPageConfigEnriched, + searchParams, + env + ) + } else { + grapherConfig = config + } console.log("grapher title", grapherConfig.title) return { grapherConfig, @@ -127,7 +161,11 @@ export async function initGrapher( searchParams: URLSearchParams, env: Env ): Promise { - const grapherConfigResponse = await fetchGrapherConfig(identifier, env) + const grapherConfigResponse = await fetchGrapherConfig({ + identifier, + env, + searchParams, + }) if (grapherConfigResponse.status === 404) { // we throw 404 errors instad of returning a 404 response so that the router diff --git a/functions/grapher/by-uuid/[uuid].ts b/functions/grapher/by-uuid/[uuid].ts index 7530381ad54..03f528f1eba 100644 --- a/functions/grapher/by-uuid/[uuid].ts +++ b/functions/grapher/by-uuid/[uuid].ts @@ -74,11 +74,11 @@ async function handleConfigRequest( const shouldCache = searchParams.get("nocache") === null console.log("Preparing json response for uuid ", uuid) - const grapherPageResp = await fetchGrapherConfig( - { type: "uuid", id: uuid }, + const grapherPageResp = await fetchGrapherConfig({ + identifier: { type: "uuid", id: uuid }, env, - etag - ) + etag, + }) if (grapherPageResp.status === 304) { return new Response(null, { status: 304 }) diff --git a/packages/@ourworldindata/types/src/siteTypes/MultiDimDataPage.ts b/packages/@ourworldindata/types/src/siteTypes/MultiDimDataPage.ts index 6819bea4dfc..14729086e9c 100644 --- a/packages/@ourworldindata/types/src/siteTypes/MultiDimDataPage.ts +++ b/packages/@ourworldindata/types/src/siteTypes/MultiDimDataPage.ts @@ -118,6 +118,5 @@ export interface MultiDimDataPageProps { primaryTopic?: PrimaryTopic relatedResearchCandidates: DataPageRelatedResearch[] imageMetadata: Record - initialQueryStr?: string isPreviewing?: boolean } diff --git a/packages/@ourworldindata/utils/src/MultiDimDataPageConfig.ts b/packages/@ourworldindata/utils/src/MultiDimDataPageConfig.ts index 352d1abc3a7..a059f9c2e34 100644 --- a/packages/@ourworldindata/utils/src/MultiDimDataPageConfig.ts +++ b/packages/@ourworldindata/utils/src/MultiDimDataPageConfig.ts @@ -217,3 +217,27 @@ export const extractMultiDimChoicesFromQueryStr = ( return dimensionChoices } + +export function searchParamsToMultiDimView( + config: MultiDimDataPageConfigEnriched, + searchParams: URLSearchParams +): ViewEnriched { + const mdimConfig = MultiDimDataPageConfig.fromObject(config) + let dimensions: MultiDimDimensionChoices + if (searchParams.size > 0) { + dimensions = extractMultiDimChoicesFromQueryStr( + searchParams.toString(), + mdimConfig + ) + } else { + // Get the default dimensions. + dimensions = mdimConfig.filterToAvailableChoices({}).selectedChoices + } + const view = mdimConfig.findViewByDimensions(dimensions) + if (!view) { + throw new Error( + `No view found for dimensions ${JSON.stringify(dimensions)}` + ) + } + return view +} diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 1a98410e6ce..0da9e6e3e7f 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -350,4 +350,5 @@ export { MultiDimDataPageConfig, extractMultiDimChoicesFromQueryStr, multiDimStateToQueryStr, + searchParamsToMultiDimView, } from "./MultiDimDataPageConfig.js" diff --git a/site/multiDim/MultiDimDataPage.tsx b/site/multiDim/MultiDimDataPage.tsx index 5cfcdd12db0..580c3dec5e5 100644 --- a/site/multiDim/MultiDimDataPage.tsx +++ b/site/multiDim/MultiDimDataPage.tsx @@ -1,3 +1,4 @@ +import urljoin from "url-join" import { Head } from "../Head.js" import { IFrameDetector } from "../IframeDetector.js" import { SiteHeader } from "../SiteHeader.js" @@ -18,7 +19,6 @@ export function MultiDimDataPage({ primaryTopic, relatedResearchCandidates, imageMetadata, - initialQueryStr, isPreviewing, }: MultiDimDataPageProps) { const canonicalUrl = `${baseGrapherUrl}/${slug}` @@ -31,19 +31,24 @@ export function MultiDimDataPage({ relatedResearchCandidates, imageMetadata, tagToSlugMap, - initialQueryStr, } + // Due to thumbnails not taking into account URL parameters, they are often inaccurate on + // social media. We decided to remove them and use a single thumbnail for all charts. + // See https://github.com/owid/owid-grapher/issues/1086 + const imageUrl: string = urljoin(baseUrl, "default-grapher-thumbnail.png") + const imageWidth = "1200" + const imageHeight = "628" return ( - {/* - */} + +