Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔨 render thumbnails in CF workers using chart configs from R2 #3847

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ dist/
.nx/workspace-data
.dev.vars
**/tsup.config.bundled*.mjs
cfstorage
2 changes: 1 addition & 1 deletion adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import {
DbInsertUser,
FlatTagGraph,
DbRawChartConfig,
R2GrapherConfigDirectory,
} from "@ourworldindata/types"
import { uuidv7 } from "uuidv7"
import {
Expand Down Expand Up @@ -158,7 +159,6 @@ import path from "path"
import {
deleteGrapherConfigFromR2,
deleteGrapherConfigFromR2ByUUID,
R2GrapherConfigDirectory,
saveGrapherConfigToR2,
saveGrapherConfigToR2ByUUID,
getMd5HashBase64,
Expand Down
5 changes: 1 addition & 4 deletions adminSiteServer/chartConfigR2Helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
S3Client,
} from "@aws-sdk/client-s3"
import { Base64String, JsonError } from "@ourworldindata/utils"
import { R2GrapherConfigDirectory } from "@ourworldindata/types"
import { logErrorAndMaybeSendToBugsnag } from "../serverUtils/errorLog.js"
import { createHash } from "crypto"

Expand All @@ -25,10 +26,6 @@ export function getMd5HashBase64(data: string): Base64String {
.update(data, "utf-8")
.digest("base64") as Base64String
}
export enum R2GrapherConfigDirectory {
byUUID = "config/by-uuid",
publishedGrapherBySlug = "grapher/by-slug",
}

let s3Client: S3Client | undefined = undefined

Expand Down
2 changes: 1 addition & 1 deletion devTools/syncGraphersToR2/syncGraphersToR2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
KnexReadonlyTransaction,
knexReadonlyTransaction,
} from "../../db/db.js"
import { R2GrapherConfigDirectory } from "../../adminSiteServer/chartConfigR2Helpers.js"
import {
base64ToBytes,
bytesToBase64,
Expand All @@ -32,6 +31,7 @@ import {
excludeUndefined,
HexString,
hexToBytes,
R2GrapherConfigDirectory,
} from "@ourworldindata/utils"
import { string } from "ts-pattern/dist/patterns.js"
import { chunk, take } from "lodash"
Expand Down
47 changes: 35 additions & 12 deletions functions/_common/grapherRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Grapher, GrapherInterface } from "@ourworldindata/grapher"
import { Bounds, deserializeJSONFromHTML } from "@ourworldindata/utils"
import { Grapher } from "@ourworldindata/grapher"
import {
Bounds,
excludeUndefined,
GrapherInterface,
R2GrapherConfigDirectory,
} from "@ourworldindata/utils"
import { svg2png, initialize as initializeSvg2Png } from "svg2png-wasm"
import { TimeLogger } from "./timeLogger"
import { png } from "itty-router"
Expand Down Expand Up @@ -143,19 +148,33 @@ async function fetchAndRenderGrapherToSvg({
}) {
const grapherLogger = new TimeLogger("grapher")

// Fetch grapher config and extract it from the HTML
const grapherConfig: GrapherInterface = await env.ASSETS.fetch(
new URL(`/grapher/${slug}`, env.url)
)
.then((r) => (r.ok ? r : Promise.reject("Failed to load grapher page")))
.then((r) => r.text())
.then((html) => deserializeJSONFromHTML(html))
const url = new URL(`/grapher/${slug}`, env.url)
const slugOnly = url.pathname.split("/").pop()

if (!grapherConfig) {
throw new Error("Could not find grapher config")
// The top level directory is either the bucket path (should be set in dev environments and production)
// or the branch name on preview staging environments
console.log("branch", env.CF_PAGES_BRANCH)
const topLevelDirectory = env.GRAPHER_CONFIG_R2_BUCKET_PATH
? [env.GRAPHER_CONFIG_R2_BUCKET_PATH]
: ["by-branch", env.CF_PAGES_BRANCH]

const key = excludeUndefined([
...topLevelDirectory,
R2GrapherConfigDirectory.publishedGrapherBySlug,
`${slugOnly}.json`,
]).join("/")

console.log("fetching grapher config from this key", key)

// Fetch grapher config
const fetchResponse = await env.r2ChartConfigs.get(key)

if (!fetchResponse) {
return null
}

grapherLogger.log("fetchGrapherConfig")
const grapherConfig: GrapherInterface = await fetchResponse.json()
console.log("grapher title", grapherConfig.title)

const bounds = new Bounds(0, 0, options.svgWidth, options.svgHeight)
const grapher = new Grapher({
Expand Down Expand Up @@ -206,6 +225,10 @@ export const fetchAndRenderGrapher = async (
env,
})

if (!svg) {
return new Response("Not found", { status: 404 })
}

switch (outType) {
case "png":
return png(await renderSvgToPng(svg, options))
Expand Down
5 changes: 5 additions & 0 deletions functions/grapher/thumbnail/[slug].ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ export interface Env {
ASSETS: {
fetch: typeof fetch
}
r2ChartConfigs: {
get: (url: string) => Promise<R2ObjectBody>
}
url: URL
GRAPHER_CONFIG_R2_BUCKET_PATH: string
CF_PAGES_BRANCH: string
ENV: string
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"revertLastDbMigration": "tsx --tsconfig tsconfig.tsx.json node_modules/typeorm/cli.js migration:revert -d db/dataSource.ts",
"startAdminServer": "node --enable-source-maps ./itsJustJavascript/adminSiteServer/app.js",
"startAdminDevServer": "tsx watch --ignore '**.mjs' --tsconfig tsconfig.tsx.json adminSiteServer/app.tsx",
"startLocalCloudflareFunctions": "wrangler pages dev",
"startLocalCloudflareFunctions": "wrangler pages dev --local --persist-to ./cfstorage",
"startDeployQueueServer": "node --enable-source-maps ./itsJustJavascript/baker/startDeployQueueServer.js",
"startLernaWatcher": "lerna watch --scope '@ourworldindata/*' -- lerna run build --scope=\\$LERNA_PACKAGE_NAME --include-dependents",
"startTmuxServer": "node_modules/tmex/tmex dev \"yarn startLernaWatcher\" \"yarn startAdminDevServer\" \"yarn startViteServer\"",
Expand Down
5 changes: 5 additions & 0 deletions packages/@ourworldindata/types/src/domainTypes/Various.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ export class JsonError extends Error {
export interface QueryParams {
[key: string]: string | undefined
}

export enum R2GrapherConfigDirectory {
byUUID = "config/by-uuid",
publishedGrapherBySlug = "grapher/by-slug",
}
1 change: 1 addition & 0 deletions packages/@ourworldindata/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export {
type RawPageview,
type UserCountryInformation,
type QueryParams,
R2GrapherConfigDirectory,
} from "./domainTypes/Various.js"
export { type BreadcrumbItem, type KeyValueProps } from "./domainTypes/Site.js"
export {
Expand Down
15 changes: 15 additions & 0 deletions wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,26 @@ MAILGUN_DOMAIN = "mg.ourworldindata.org"
SLACK_ERROR_CHANNEL_ID = "C016H0BNNB1"
ENV = "preview"

[[r2_buckets]]
binding = "r2ChartConfigs"
bucket_name = "owid-grapher-configs-staging"

# Overrides for CF production deployment
[env.production]
compatibility_date = "2024-04-29"

[[env.production.r2_buckets]]
binding = "r2ChartConfigs"
bucket_name = "owid-grapher-configs"

[env.production.vars]
ENV = "production"
MAILGUN_DOMAIN = "mg.ourworldindata.org"
SLACK_ERROR_CHANNEL_ID = "C5JJW19PS"
GRAPHER_CONFIG_R2_BUCKET_PATH = "v1"


[[env.preview.r2_buckets]]
binding = "r2ChartConfigs"
bucket_name = "owid-grapher-configs-staging"

Loading