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

Add storing single charts to local R2 bucket to syncGraphersToR2 tool #3868

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cc5515e
🔨 rename R2 settings to generic names
danyx23 Jul 30, 2024
bc2f041
🐛 undo accidental rename
danyx23 Aug 1, 2024
9a61bc8
✨ add saving chart configs to R2
danyx23 Aug 2, 2024
73901f0
🚧 WIP tool to sync grapher configs to R2
danyx23 Aug 5, 2024
93703dd
🔨 switch from sha1 to md5 and implement remaining sync logic
danyx23 Aug 5, 2024
04304f1
💄 add progress bar
danyx23 Aug 5, 2024
e9f965c
🐝 fix prettier issue
danyx23 Aug 7, 2024
bc54d76
🐝 fix minor issues
danyx23 Aug 7, 2024
7260140
🔨 remove SHA-1 functions
danyx23 Aug 7, 2024
93a34ff
🔨 add tests for base64/hex conversion
danyx23 Aug 8, 2024
8c1b00e
💄 minor improvements
danyx23 Aug 8, 2024
2fa1810
🖊️ document env vars
danyx23 Aug 8, 2024
bf1cf37
🔨 fix compile errors
danyx23 Aug 8, 2024
2125a46
🐛 fix sync issue not taking published status into account
danyx23 Aug 13, 2024
1821c66
🔨 make deletes parallel as well since during testing that can easily …
danyx23 Aug 13, 2024
f683d47
🐛 fix chart saving bug
danyx23 Aug 13, 2024
811d410
🔨 configure r2 bindings
danyx23 Aug 5, 2024
01900ce
🚧 start work on fetching grapher configs for pages functions from R2
danyx23 Aug 6, 2024
5770590
✨ finish cf functions fetching from R2
danyx23 Aug 6, 2024
0042a26
🔨 add fallback to branch name as directory in R2
danyx23 Aug 7, 2024
72aa0f2
📜 update readme
danyx23 Aug 13, 2024
3177d4a
Create a PR that changes the script in ./devTools/syncGraphersToR2 so…
danyx23 Aug 12, 2024
cc54317
🐝 reformat, make sure process exits
danyx23 Aug 13, 2024
bbf9268
Add storing single charts to local R2 bucket to syncGraphersToR2 tool
danyx23 Aug 13, 2024
424faea
🔨 make implementation work
danyx23 Aug 13, 2024
13bceeb
🔨 add readme, tweak script
danyx23 Aug 13, 2024
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
6 changes: 3 additions & 3 deletions .env.devcontainer
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ GDOCS_CLIENT_ID=''
GDOCS_BASIC_ARTICLE_TEMPLATE_URL=''
GDOCS_SHARED_DRIVE_ID=''

IMAGE_HOSTING_R2_ENDPOINT=''
R2_ENDPOINT=''
IMAGE_HOSTING_R2_CDN_URL=''
IMAGE_HOSTING_R2_BUCKET_PATH=''
IMAGE_HOSTING_R2_ACCESS_KEY_ID=''
IMAGE_HOSTING_R2_SECRET_ACCESS_KEY=''
R2_ACCESS_KEY_ID=''
R2_SECRET_ACCESS_KEY=''
12 changes: 9 additions & 3 deletions .env.example-full
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ GDOCS_BASIC_ARTICLE_TEMPLATE_URL=
GDOCS_SHARED_DRIVE_ID=
GDOCS_DONATE_FAQS_DOCUMENT_ID= # optional

IMAGE_HOSTING_R2_ENDPOINT= # optional
R2_ENDPOINT= # optional
IMAGE_HOSTING_R2_CDN_URL=
IMAGE_HOSTING_R2_BUCKET_PATH=
IMAGE_HOSTING_R2_ACCESS_KEY_ID= # optional
IMAGE_HOSTING_R2_SECRET_ACCESS_KEY= # optional
R2_ACCESS_KEY_ID= # optional
R2_SECRET_ACCESS_KEY= # optional
# These two GRAPHER_CONFIG_ settings are used to store grapher configs in an R2 bucket.
# The cloudflare workers for thumbnail rendering etc use these settings to fetch the grapher configs.
# This means that for most local dev it is not necessary to set these.
GRAPHER_CONFIG_R2_BUCKET= # optional - for local dev set it to "owid-grapher-configs-staging"
GRAPHER_CONFIG_R2_BUCKET_PATH= # optional - for local dev set it to "devs/YOURNAME"


OPENAI_API_KEY=

Expand Down
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
115 changes: 96 additions & 19 deletions 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 @@ -155,6 +156,13 @@ import { GdocDataInsight } from "../db/model/Gdoc/GdocDataInsight.js"
import { GdocHomepage } from "../db/model/Gdoc/GdocHomepage.js"
import { GdocAuthor } from "../db/model/Gdoc/GdocAuthor.js"
import path from "path"
import {
deleteGrapherConfigFromR2,
deleteGrapherConfigFromR2ByUUID,
saveGrapherConfigToR2,
saveGrapherConfigToR2ByUUID,
getMd5HashBase64,
} from "./chartConfigR2Helpers.js"

const apiRouter = new FunctionalRouter()

Expand Down Expand Up @@ -275,7 +283,7 @@ const expectChartById = async (
const saveNewChart = async (
knex: db.KnexReadWriteTransaction,
{ config, user }: { config: GrapherInterface; user: DbPlainUser }
): Promise<GrapherInterface> => {
): Promise<{ patchConfig: GrapherInterface; fullConfig: GrapherInterface }> => {
// if the schema version is missing, assume it's the latest
if (!config["$schema"]) {
config["$schema"] = defaultGrapherConfig["$schema"]
Expand All @@ -285,16 +293,25 @@ const saveNewChart = async (
const parentConfig = defaultGrapherConfig
const patchConfig = diffGrapherConfigs(config, parentConfig)
const fullConfig = mergeGrapherConfigs(parentConfig, patchConfig)
const fullConfigStringified = JSON.stringify(fullConfig)

// compute a sha-1 hash of the full config
const fullConfigMd5 = await getMd5HashBase64(fullConfigStringified)

// insert patch & full configs into the chart_configs table
const configId = uuidv7()
const chartConfigId = uuidv7()
await db.knexRaw(
knex,
`-- sql
INSERT INTO chart_configs (id, patch, full)
VALUES (?, ?, ?)
INSERT INTO chart_configs (id, patch, full, fullMd5)
VALUES (?, ?, ?, ?)
`,
[configId, JSON.stringify(patchConfig), JSON.stringify(fullConfig)]
[
chartConfigId,
JSON.stringify(patchConfig),
fullConfigStringified,
fullConfigMd5,
]
)

// add a new chart to the charts table
Expand All @@ -304,7 +321,7 @@ const saveNewChart = async (
INSERT INTO charts (configId, lastEditedAt, lastEditedByUserId)
VALUES (?, ?, ?)
`,
[configId, new Date(), user.id]
[chartConfigId, new Date(), user.id]
)

// The chart config itself has an id field that should store the id of the chart - update the chart now so this is true
Expand All @@ -324,7 +341,9 @@ const saveNewChart = async (
[chartId, chartId, chartId]
)

return patchConfig
await saveGrapherConfigToR2ByUUID(chartConfigId, fullConfigStringified)

return { patchConfig, fullConfig }
}

const updateExistingChart = async (
Expand All @@ -334,7 +353,7 @@ const updateExistingChart = async (
user,
chartId,
}: { config: GrapherInterface; user: DbPlainUser; chartId: number }
): Promise<GrapherInterface> => {
): Promise<{ patchConfig: GrapherInterface; fullConfig: GrapherInterface }> => {
// make sure that the id of the incoming config matches the chart id
config.id = chartId

Expand All @@ -347,19 +366,36 @@ const updateExistingChart = async (
const parentConfig = defaultGrapherConfig
const patchConfig = diffGrapherConfigs(config, parentConfig)
const fullConfig = mergeGrapherConfigs(parentConfig, patchConfig)
const fullConfigStringified = JSON.stringify(fullConfig)

const fullConfigMd5 = await getMd5HashBase64(fullConfigStringified)

const chartConfigId = await db.knexRawFirst<Pick<DbPlainChart, "configId">>(
knex,
`SELECT configId FROM charts WHERE id = ?`,
[chartId]
)

if (!chartConfigId)
throw new JsonError(`No chart config found for id ${chartId}`, 404)

// update configs
await db.knexRaw(
knex,
`-- sql
UPDATE chart_configs cc
JOIN charts c ON c.configId = cc.id
UPDATE chart_configs
SET
cc.patch=?,
cc.full=?
WHERE c.id = ?
patch=?,
full=?,
fullMd5=?
WHERE id = ?
`,
[JSON.stringify(patchConfig), JSON.stringify(fullConfig), chartId]
[
JSON.stringify(patchConfig),
fullConfigStringified,
fullConfigMd5,
chartConfigId.configId,
]
)

// update charts row
Expand All @@ -373,7 +409,12 @@ const updateExistingChart = async (
[new Date(), user.id, chartId]
)

return patchConfig
await saveGrapherConfigToR2ByUUID(
chartConfigId.configId,
fullConfigStringified
)

return { patchConfig, fullConfig }
}

const saveGrapher = async (
Expand Down Expand Up @@ -443,6 +484,11 @@ const saveGrapher = async (
`INSERT INTO chart_slug_redirects (chart_id, slug) VALUES (?, ?)`,
[existingConfig.id, existingConfig.slug]
)
// When we rename grapher configs, make sure to delete the old one (the new one will be saved below)
await deleteGrapherConfigFromR2(
R2GrapherConfigDirectory.publishedGrapherBySlug,
`${existingConfig.slug}.json`
)
}
}

Expand All @@ -457,20 +503,27 @@ const saveGrapher = async (

// Execute the actual database update or creation
let chartId: number
let patchConfig: GrapherInterface
let fullConfig: GrapherInterface
if (existingConfig) {
chartId = existingConfig.id!
newConfig = await updateExistingChart(knex, {
const configs = await updateExistingChart(knex, {
config: newConfig,
user,
chartId,
})
patchConfig = configs.patchConfig
fullConfig = configs.fullConfig
} else {
newConfig = await saveNewChart(knex, {
const configs = await saveNewChart(knex, {
config: newConfig,
user,
})
chartId = newConfig.id!
patchConfig = configs.patchConfig
fullConfig = configs.fullConfig
chartId = fullConfig.id!
}
newConfig = patchConfig

// Record this change in version history
const chartRevisionLog = {
Expand Down Expand Up @@ -515,6 +568,17 @@ const saveGrapher = async (
newDimensions.map((d) => d.variableId)
)

if (newConfig.isPublished) {
const configStringified = JSON.stringify(fullConfig)
const configMd5 = await getMd5HashBase64(configStringified)
await saveGrapherConfigToR2(
configStringified,
R2GrapherConfigDirectory.publishedGrapherBySlug,
`${newConfig.slug}.json`,
configMd5
)
}

if (
newConfig.isPublished &&
(!existingConfig || !existingConfig.isPublished)
Expand All @@ -537,6 +601,10 @@ const saveGrapher = async (
`DELETE FROM chart_slug_redirects WHERE chart_id = ?`,
[existingConfig.id]
)
await deleteGrapherConfigFromR2(
R2GrapherConfigDirectory.publishedGrapherBySlug,
`${existingConfig.slug}.json`
)
await triggerStaticBuild(user, `Unpublishing chart ${newConfig.slug}`)
} else if (newConfig.isPublished)
await triggerStaticBuild(user, `Updating chart ${newConfig.slug}`)
Expand Down Expand Up @@ -883,11 +951,13 @@ deleteRouteWithRWTransaction(
[chart.id]
)

const row = await db.knexRawFirst<{ configId: number }>(
const row = await db.knexRawFirst<Pick<DbPlainChart, "configId">>(
trx,
`SELECT configId FROM charts WHERE id = ?`,
[chart.id]
)
if (!row)
throw new JsonError(`No chart config found for id ${chart.id}`, 404)
if (row) {
await db.knexRaw(trx, `DELETE FROM charts WHERE id=?`, [chart.id])
await db.knexRaw(trx, `DELETE FROM chart_configs WHERE id=?`, [
Expand All @@ -901,6 +971,13 @@ deleteRouteWithRWTransaction(
`Deleting chart ${chart.slug}`
)

await deleteGrapherConfigFromR2ByUUID(row.configId)
if (chart.isPublished)
await deleteGrapherConfigFromR2(
R2GrapherConfigDirectory.publishedGrapherBySlug,
`${chart.slug}.json`
)

return { success: true }
}
)
Expand Down
Loading