diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 2add7d1553a..d03f7a86fac 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -26,6 +26,9 @@ import { fetchS3MetadataByPath, fetchS3DataValuesByPath, searchVariables, + getGrapherConfigsForVariable, + updateExistingGrapherConfigForVariable, + insertNewGrapherConfigForVariable, } from "../db/model/Variable.js" import { getCanonicalUrl } from "@ourworldindata/components" import { @@ -91,6 +94,7 @@ import { defaultGrapherConfig, getVariableDataRoute, getVariableMetadataRoute, + DEFAULT_GRAPHER_CONFIG_SCHEMA, } from "@ourworldindata/grapher" import { getDatasetById, setTagsForDataset } from "../db/model/Dataset.js" import { getUserById, insertUser, updateUser } from "../db/model/User.js" @@ -272,16 +276,6 @@ const expectChartById = async ( throw new JsonError(`No chart found for id ${chartId}`, 404) } -const getBinaryUUID = async ( - knex: db.KnexReadonlyTransaction -): Promise => { - const { id } = (await db.knexRawFirst<{ id: Buffer }>( - knex, - `SELECT UUID_TO_BIN(UUID(), 1) AS id` - ))! - return id -} - const saveNewChart = async ( knex: db.KnexReadWriteTransaction, { config, user }: { config: GrapherInterface; user: DbPlainUser } @@ -292,12 +286,12 @@ const saveNewChart = async ( } // compute patch and full configs - const baseConfig = getBaseLayerConfig() + const baseConfig = await getBaseLayerConfigForChart(knex) const patchConfig = diffGrapherConfigs(config, baseConfig) const fullConfig = mergeGrapherConfigs(baseConfig, patchConfig) // insert patch & full configs into the chart_configs table - const configId = await getBinaryUUID(knex) + const configId = await db.getBinaryUUID(knex) await db.knexRaw( knex, `-- sql @@ -354,7 +348,7 @@ const updateExistingChart = async ( } // compute patch and full configs - const baseConfig = getBaseLayerConfig() + const baseConfig = await getBaseLayerConfigForChart(knex, chartId) const patchConfig = diffGrapherConfigs(config, baseConfig) const fullConfig = mergeGrapherConfigs(baseConfig, patchConfig) @@ -633,14 +627,81 @@ getRouteWithROTransaction( async (req, res, trx) => expectChartById(trx, req.params.chartId) ) -function getBaseLayerConfig(): GrapherInterface { +async function getBaseLayerConfigForIndicatorChartAdmin( + trx: db.KnexReadonlyTransaction, + variableId: number +): Promise { + // check if there is an ETL-authored indicator chart + const variable = await db.knexRawFirst<{ config: GrapherInterface }>( + trx, + `-- sql + SELECT cc.full AS config + FROM chart_configs cc + JOIN variables v ON v.grapherConfigIdETL = cc.id + WHERE v.id = ? + `, + [variableId] + ) + return variable?.config ?? {} +} + +async function getBaseLayerConfigForChart( + trx: db.KnexReadonlyTransaction, + chartId?: number +): Promise { + if (chartId === undefined) return defaultGrapherConfig + + // check if the chart inherits settings from an indicator + const baseIndicator = await db.knexRawFirst<{ id: number }>( + trx, + `-- sql + SELECT indicatorId AS id + FROM inheriting_charts + WHERE chartId = ? + `, + [chartId] + ) + + if (!baseIndicator) return defaultGrapherConfig + + // check if there is an admin-authored indicator chart + const variableAdmin = await db.knexRawFirst<{ config: GrapherInterface }>( + trx, + `-- sql + SELECT cc.full AS config + FROM chart_configs cc + JOIN variables v ON v.grapherConfigIdAdmin = cc.id + WHERE v.id = ? + `, + [baseIndicator.id] + ) + if (variableAdmin) + return mergeGrapherConfigs(defaultGrapherConfig, variableAdmin.config) + + // check if there is an ETL-authored indicator chart + const variableETL = await db.knexRawFirst<{ config: GrapherInterface }>( + trx, + `-- sql + SELECT cc.full AS config + FROM chart_configs cc + JOIN variables v ON v.grapherConfigIdETL = cc.id + WHERE v.id = ? + `, + [baseIndicator.id] + ) + if (variableETL) + return mergeGrapherConfigs(defaultGrapherConfig, variableETL.config) + return defaultGrapherConfig } getRouteWithROTransaction( apiRouter, "/charts/:chartId.base.json", - async (req, res, trx) => getBaseLayerConfig() + async (req, res, trx) => { + const chartId = expectInt(req.params.chartId) + return getBaseLayerConfigForChart(trx, chartId) + } ) getRouteWithROTransaction( @@ -1141,21 +1202,25 @@ getRouteWithROTransaction( const whereClause = filterSExpr?.toSql() ?? "true" const resultsWithStringGrapherConfigs = await db.knexRaw( trx, - `SELECT variables.id as id, - variables.name as name, - variables.grapherConfigAdmin as config, - d.name as datasetname, - namespaces.name as namespacename, - variables.createdAt as createdAt, - variables.updatedAt as updatedAt, - variables.description as description -FROM variables -LEFT JOIN active_datasets as d on variables.datasetId = d.id -LEFT JOIN namespaces on d.namespace = namespaces.name -WHERE ${whereClause} -ORDER BY variables.id DESC -LIMIT 50 -OFFSET ${offset.toString()}` + `-- sql + SELECT + variables.id as id, + variables.name as name, + chart_configs.patch as config, + d.name as datasetname, + namespaces.name as namespacename, + variables.createdAt as createdAt, + variables.updatedAt as updatedAt, + variables.description as description + FROM variables + LEFT JOIN active_datasets as d on variables.datasetId = d.id + LEFT JOIN namespaces on d.namespace = namespaces.name + LEFT JOIN chart_configs on variables.grapherConfigIdAdmin = chart_configs.id + WHERE ${whereClause} + ORDER BY variables.id DESC + LIMIT 50 + OFFSET ${offset.toString()} + ` ) const results = resultsWithStringGrapherConfigs.map((row: any) => ({ @@ -1164,11 +1229,14 @@ OFFSET ${offset.toString()}` })) const resultCount = await db.knexRaw<{ count: number }>( trx, - `SELECT count(*) as count -FROM variables -LEFT JOIN active_datasets as d on variables.datasetId = d.id -LEFT JOIN namespaces on d.namespace = namespaces.name -WHERE ${whereClause}` + `-- sql + SELECT count(*) as count + FROM variables + LEFT JOIN active_datasets as d on variables.datasetId = d.id + LEFT JOIN namespaces on d.namespace = namespaces.name + LEFT JOIN chart_configs on variables.grapherConfigIdAdmin = chart_configs.id + WHERE ${whereClause} + ` ) return { rows: results, numTotalRows: resultCount[0].count } } @@ -1182,14 +1250,25 @@ patchRouteWithRWTransaction( const variableIds = new Set(patchesList.map((patch) => patch.id)) const configsAndIds = await db.knexRaw< - Pick - >(trx, `SELECT id, grapherConfigAdmin FROM variables where id IN (?)`, [ - [...variableIds.values()], - ]) + Pick & { + grapherConfigAdmin: DbRawChartConfig["patch"] + } + >( + trx, + `-- sql + SELECT v.id, cc.patch AS grapherConfigAdmin + FROM variables v + LEFT JOIN chart_configs cc ON v.grapherConfigIdAdmin = cc.id + WHERE v.id IN (?) + `, + [[...variableIds.values()]] + ) const configMap = new Map( configsAndIds.map((item: any) => [ item.id, - item.grapherConfigAdmin ? JSON.parse(item.grapherConfig) : {}, + item.grapherConfigAdmin + ? JSON.parse(item.grapherConfigAdmin) + : {}, ]) ) // console.log("ids", configsAndIds.map((item : any) => item.id)) @@ -1199,11 +1278,58 @@ patchRouteWithRWTransaction( } for (const [variableId, newConfig] of configMap.entries()) { - await db.knexRaw( - trx, - `UPDATE variables SET grapherConfigAdmin = ? where id = ?`, - [JSON.stringify(newConfig), variableId] - ) + let configId = ( + await db.knexRawFirst<{ configId: Buffer }>( + trx, + `-- sql + SELECT cc.id AS configId + FROM chart_configs cc + JOIN variables v ON v.grapherConfigIdAdmin = cc.id + WHERE v.id = ? + `, + [variableId] + ) + )?.configId + if (configId) { + await db.knexRaw( + trx, + `-- sql + UPDATE chart_configs + SET + patch = ?, + full = ? + WHERE id = ? + `, + [ + JSON.stringify(newConfig), + JSON.stringify(newConfig), + configId, + ] // TODO: full is wrong!! + ) + } else { + configId = await db.getBinaryUUID(trx) + await db.knexRaw( + trx, + `-- sql + INSERT INTO chart_configs (id, patch, full) + VALUES (?, ?, ?) + `, + [ + configId, + JSON.stringify(newConfig), + JSON.stringify(newConfig), + ] // TODO: full is wrong + ) + await db.knexRaw( + trx, + `-- sql + UPDATE variables + SET grapherConfigIdAdmin = ? + WHERE id = ? + `, + [configId, variableId] + ) + } } return { success: true } @@ -1267,22 +1393,22 @@ getRouteWithROTransaction( await assignTagsForCharts(trx, charts) const grapherConfig = await getMergedGrapherConfigForVariable( - variableId, - trx - ) - if ( - grapherConfig && - (!grapherConfig.dimensions || grapherConfig.dimensions.length === 0) - ) { - const dimensions: OwidChartDimensionInterface[] = [ - { - variableId: variableId, - property: DimensionProperty.y, - display: variable.display, - }, - ] - grapherConfig.dimensions = dimensions - } + trx, + variableId + ) + // if ( + // grapherConfig && + // (!grapherConfig.dimensions || grapherConfig.dimensions.length === 0) + // ) { + // const dimensions: OwidChartDimensionInterface[] = [ + // { + // variableId: variableId, + // property: DimensionProperty.y, + // display: variable.display, + // }, + // ] + // grapherConfig.dimensions = dimensions + // } const variablesWithCharts: OwidVariableWithSource & { charts: Record @@ -1299,82 +1425,188 @@ getRouteWithROTransaction( } ) +function makeConfigValidForIndicator({ + config, + variableId, +}: { + config: GrapherInterface + variableId: number +}): GrapherInterface { + const updatedConfig = { ...config } + + // if no schema is given, assume it's the latest + if (!updatedConfig.$schema) { + updatedConfig.$schema = DEFAULT_GRAPHER_CONFIG_SCHEMA + } + + // check if the given dimensions are correct + if (updatedConfig.dimensions && updatedConfig.dimensions.length >= 1) { + // make sure there is only a single entry + updatedConfig.dimensions = updatedConfig.dimensions.slice(0, 1) + // make sure the variable id matches + updatedConfig.dimensions[0].variableId = variableId + } + + // fill dimensions if not given to make the updatedConfig plottable + if (!updatedConfig.dimensions || updatedConfig.dimensions.length === 0) { + updatedConfig.dimensions = [ + { property: DimensionProperty.y, variableId }, + ] + } + + return updatedConfig +} + postRouteWithRWTransaction( apiRouter, - "/variables/:variableId/grapherConfigETL/new", + "/variables/:variableId/grapherConfigETL/update", async (req, res, trx) => { const variableId = expectInt(req.params.variableId) - // const user = res.locals.user - const config = req.body - - // if no schema is given, assume it's the latest - if (!config.$schema) { - config.$schema = defaultGrapherConfig.$schema - } + const configETL = makeConfigValidForIndicator({ + config: req.body, + variableId, + }) - // check if the given dimensions are correct - if (config.dimensions && config.dimensions.length >= 1) { - // make sure there is only a single entry - config.dimensions = config.dimensions.slice(0, 1) - // make sure the variable id matches - config.dimensions[0].variableId = variableId + const variable = await getGrapherConfigsForVariable(trx, variableId) + if (!variable) { + throw new JsonError(`Variable with id ${variableId} not found`, 500) } - // fill dimensions if not given to make the config plottable - if (!config.dimensions || config.dimensions.length === 0) { - config.dimensions = [{ property: DimensionProperty.y, variableId }] + if (variable.grapherConfigIdETL) { + await updateExistingGrapherConfigForVariable(trx, { + type: "etl", + variableId, + config: configETL, + configId: variable.grapherConfigIdETL, + }) + } else { + await insertNewGrapherConfigForVariable(trx, { + type: "etl", + variableId, + config: configETL, + }) } - // ETL configs inherit from the default - const patchConfigETL = diffGrapherConfigs(config, defaultGrapherConfig) - const fullConfigETL = mergeGrapherConfigs( - defaultGrapherConfig, - patchConfigETL - ) + // // grab the admin-authored indicator chart and update it if there is one + // if (variable.grapherConfigAdmin) { + // const fullConfigAdmin = mergeGrapherConfigs( + // configETL, + // patchConfigAdmin + // ) + + // // update the admin-authored indicator chart + // await db.knexRaw( + // trx, + // `-- sql + // UPDATE variables + // SET full = ? + // WHERE grapherConfigIdAdmin = ? + // `, + // [JSON.stringify(fullConfigAdmin), variableRowAdmin.configId] + // ) + // } - // insert chart config into the database - const configId = await getBinaryUUID(trx) - await db.knexRaw( + // find all charts that inherit from the indicator + const children = await db.knexRaw<{ + chartId: number + patchConfig: string + }>( trx, `-- sql - INSERT INTO chart_configs (id, patch, full) - VALUES (?, ?, ?) + SELECT c.id as chartId, cc.patch as patchConfig + FROM inheriting_charts ic + JOIN charts c ON c.id = ic.chartId + JOIN chart_configs cc ON cc.id = c.configId + WHERE ic.variableId = ? `, - [ - configId, - JSON.stringify(patchConfigETL), - JSON.stringify(fullConfigETL), - ] + [variableId] ) - // make a reference to the config from the variables table + for (const child of children) { + const patchConfigChild = JSON.parse(child.patchConfig) + const fullConfigChild = mergeGrapherConfigs( + defaultGrapherConfig, + configETL, + variable.grapherConfigAdmin ?? {}, + patchConfigChild + ) + await db.knexRaw( + trx, + `-- sql + UPDATE chart_configs cc + JOIN charts c ON c.configId = cc.id + SET cc.full = ? + WHERE c.id = ? + `, + [JSON.stringify(fullConfigChild), child.chartId] + ) + } + } +) + +deleteRouteWithRWTransaction( + apiRouter, + "/variables/:variableId/grapherConfigETL/delete", + async (req, res, trx) => { + const variableId = expectInt(req.params.variableId) + + const variable = await getGrapherConfigsForVariable(trx, variableId) + if (!variable) { + throw new JsonError(`Variable with id ${variableId} not found`, 500) + } + + // remove reference in the variables table await db.knexRaw( trx, `-- sql UPDATE variables - SET grapherConfigIdETL = ? + SET grapherConfigIdETL = NULL WHERE id = ? `, - [configId, variableId] + [variableId] ) - // grab the admin-authored indicator chart if there is one - let patchConfigAdmin: GrapherInterface = {} - const row = await db.knexRawFirst<{ - adminConfig: DbRawChartConfig["full"] // TODO: type - }>( + // delete row in the chart_configs table + await db.knexRaw( trx, `-- sql - SELECT cc.patch as adminConfig - FROM chart_configs cc - JOIN variables v ON cc.id = v.grapherConfigIdAdmin - WHERE v.id = ? + DELETE FROM chart_configs + WHERE id = ? `, - [variableId] - ) - if (row) { - patchConfigAdmin = parseChartConfig(row.adminConfig) - } + [variable.grapherConfigIdETL] + ) + + // // grab the admin-authored indicator chart and update it if there is one + // let patchConfigAdmin: GrapherInterface = {} + // const variableRowAdmin = await db.knexRawFirst<{ + // configId: DbRawChartConfig["id"] + // patchConfig: DbRawChartConfig["patch"] + // }>( + // trx, + // `-- sql + // SELECT + // cc.id as configId, + // cc.patch as patchConfig + // FROM chart_configs cc + // JOIN variables v ON cc.id = v.grapherConfigIdAdmin + // WHERE v.id = ? + // `, + // [variableId] + // ) + // if (variableRowAdmin) { + // patchConfigAdmin = parseChartConfig(variableRowAdmin.patchConfig) + + // // update the admin-authored indicator chart + // await db.knexRaw( + // trx, + // `-- sql + // UPDATE variables + // SET full = ? + // WHERE grapherConfigIdAdmin = ? + // `, + // [variableRowAdmin.patchConfig, variableRowAdmin.configId] + // ) + // } // find all charts that inherit from the indicator const children = await db.knexRaw<{ @@ -1396,8 +1628,7 @@ postRouteWithRWTransaction( const patchConfigChild = JSON.parse(child.patchConfig) const fullConfigChild = mergeGrapherConfigs( defaultGrapherConfig, - patchConfigETL, - patchConfigAdmin, + variable.grapherConfigAdmin ?? {}, patchConfigChild ) await db.knexRaw( @@ -1411,6 +1642,8 @@ postRouteWithRWTransaction( [JSON.stringify(fullConfigChild), child.chartId] ) } + + return { success: true } } ) diff --git a/baker/GrapherBaker.tsx b/baker/GrapherBaker.tsx index 9046c9cc82c..5d77cfd9658 100644 --- a/baker/GrapherBaker.tsx +++ b/baker/GrapherBaker.tsx @@ -150,8 +150,8 @@ export async function renderDataPageV2( knex: db.KnexReadWriteTransaction ) { const grapherConfigForVariable = await getMergedGrapherConfigForVariable( - variableId, - knex + knex, + variableId ) // Only merge the grapher config on the indicator if the caller tells us to do so - // this is true for preview pages for datapages on the indicator level but false diff --git a/baker/siteRenderers.tsx b/baker/siteRenderers.tsx index afe9054586e..d3760b8bb55 100644 --- a/baker/siteRenderers.tsx +++ b/baker/siteRenderers.tsx @@ -48,6 +48,7 @@ import { OwidGdoc, OwidGdocDataInsightInterface, DbRawPost, + mergeGrapherConfigs, } from "@ourworldindata/utils" import { extractFormattingOptions } from "../serverUtils/wordpressUtils.js" import { @@ -751,7 +752,16 @@ export const renderExplorerPage = async ( if (requiredVariableIds.length) { partialGrapherConfigRows = await knexRaw( knex, - `SELECT id, grapherConfigETL, grapherConfigAdmin FROM variables WHERE id IN (?)`, + `-- sql + SELECT + v.id, + cc_etl.full AS grapherConfigETL, + cc_admin.full AS grapherConfigAdmin + FROM variables v + JOIN chart_configs cc_admin ON cc.id=v.grapherConfigIdAdmin + JOIN chart_configs cc_etl ON cc.id=v.grapherConfigIdETL + WHERE v.id IN (?) + `, [requiredVariableIds] ) @@ -779,20 +789,17 @@ export const renderExplorerPage = async ( const partialGrapherConfigs = partialGrapherConfigRows .filter((row) => row.grapherConfigAdmin || row.grapherConfigETL) .map((row) => { - const adminConfig = row.grapherConfigAdmin - ? parseGrapherConfigFromRow({ - id: row.id, - config: row.grapherConfigAdmin as string, - }) - : {} - const etlConfig = row.grapherConfigETL - ? parseGrapherConfigFromRow({ - id: row.id, - config: row.grapherConfigETL as string, - }) - : {} - // TODO(inheritance): use mergeGrapherConfigs instead - return mergePartialGrapherConfigs(etlConfig, adminConfig) + if (row.grapherConfigAdmin) { + return parseGrapherConfigFromRow({ + id: row.id, + config: row.grapherConfigAdmin as string, + }) + } else { + return parseGrapherConfigFromRow({ + id: row.id, + config: row.grapherConfigETL as string, + }) + } }) const wpContent = transformedProgram.wpBlockId diff --git a/db/db.ts b/db/db.ts index 71726c286c4..fb3aa5aac69 100644 --- a/db/db.ts +++ b/db/db.ts @@ -668,3 +668,13 @@ export async function getLinkedIndicatorSlugs({ .then((gdocs) => gdocs.flatMap((gdoc) => gdoc.linkedKeyIndicatorSlugs)) .then((slugs) => new Set(slugs)) } + +export const getBinaryUUID = async ( + knex: KnexReadonlyTransaction +): Promise => { + const { id } = (await knexRawFirst<{ id: Buffer }>( + knex, + `SELECT UUID_TO_BIN(UUID(), 1) AS id` + ))! + return id +} diff --git a/db/migration/1721134584504-MoveIndicatorChartsToTheChartsConfigTable.ts b/db/migration/1721134584504-MoveIndicatorChartsToTheChartsConfigTable.ts index 02d4ded66a0..062bb2de123 100644 --- a/db/migration/1721134584504-MoveIndicatorChartsToTheChartsConfigTable.ts +++ b/db/migration/1721134584504-MoveIndicatorChartsToTheChartsConfigTable.ts @@ -71,10 +71,6 @@ export class MoveIndicatorChartsToTheChartsConfigTable1721134584504 config.$schema = defaultGrapherConfig.$schema } - // ETL configs inherit from the default config - const patchConfig = diffGrapherConfigs(config, defaultGrapherConfig) - const fullConfig = mergeGrapherConfigs(defaultGrapherConfig, config) - // insert config into the chart_configs table const configId = await getBinaryUUID(queryRunner) await queryRunner.query( @@ -82,11 +78,7 @@ export class MoveIndicatorChartsToTheChartsConfigTable1721134584504 INSERT INTO chart_configs (id, patch, full) VALUES (?, ?, ?) `, - [ - configId, - JSON.stringify(patchConfig), - JSON.stringify(fullConfig), - ] + [configId, JSON.stringify(config), JSON.stringify(config)] ) // update reference in the variables table @@ -108,7 +100,7 @@ export class MoveIndicatorChartsToTheChartsConfigTable1721134584504 `) await queryRunner.query(`-- sql - CREATE VIEW inheritance_indicators_x_charts AS ( + CREATE VIEW inheriting_charts AS ( WITH y_dimensions AS ( SELECT * @@ -145,6 +137,11 @@ export class MoveIndicatorChartsToTheChartsConfigTable1721134584504 } public async down(queryRunner: QueryRunner): Promise { + // drop view + await queryRunner.query(`-- sql + DROP VIEW inheriting_charts + `) + // add back the `grapherConfigAdmin` and `grapherConfigETL` columns await queryRunner.query(`-- sql ALTER TABLE variables diff --git a/db/model/Variable.ts b/db/model/Variable.ts index 8bcd74abe0e..ec8677b7064 100644 --- a/db/model/Variable.ts +++ b/db/model/Variable.ts @@ -1,7 +1,13 @@ import _ from "lodash" import { Writable } from "stream" import * as db from "../db.js" -import { retryPromise, isEmpty } from "@ourworldindata/utils" +import { + retryPromise, + isEmpty, + excludeUndefined, + omitUndefinedValues, + mergeGrapherConfigs, +} from "@ourworldindata/utils" import { getVariableDataRoute, getVariableMetadataRoute, @@ -20,33 +26,144 @@ import { GrapherInterface, DbRawVariable, VariablesTableName, + DbRawChartConfig, + parseChartConfig, + DbEnrichedChartConfig, } from "@ourworldindata/types" -import { knexRaw } from "../db.js" +import { knexRaw, knexRawFirst } from "../db.js" + +export async function getGrapherConfigsForVariable( + knex: db.KnexReadonlyTransaction, + variableId: number +): Promise< + | (Pick< + DbRawVariable, + "id" | "grapherConfigIdAdmin" | "grapherConfigIdETL" + > & { + grapherConfigAdmin?: DbEnrichedChartConfig["patch"] + grapherConfigETL?: DbEnrichedChartConfig["patch"] + }) + | undefined +> { + const variable = await knexRawFirst< + Pick< + DbRawVariable, + "id" | "grapherConfigIdAdmin" | "grapherConfigIdETL" + > & { + grapherConfigAdmin?: DbRawChartConfig["patch"] + grapherConfigETL?: DbRawChartConfig["patch"] + } + >( + knex, + `-- sql + SELECT + v.id, + v.grapherConfigIdAdmin, + v.grapherConfigIdETL, + cc_admin.patch AS grapherConfigAdmin, + cc_etl.patch AS grapherConfigETL + FROM variables v + LEFT JOIN chart_configs cc_admin ON v.grapherConfigIdAdmin = cc_admin.id + LEFT JOIN chart_configs cc_etl ON v.grapherConfigIdETL = cc_etl.id + WHERE v.id = ? + `, + [variableId] + ) + + if (!variable) return -//export type Field = keyof VariableRow + return omitUndefinedValues({ + ...variable, + grapherConfigAdmin: variable.grapherConfigAdmin + ? parseChartConfig(variable.grapherConfigAdmin) + : undefined, + grapherConfigETL: variable.grapherConfigETL + ? parseChartConfig(variable.grapherConfigETL) + : undefined, + }) +} export async function getMergedGrapherConfigForVariable( - variableId: number, - knex: db.KnexReadonlyTransaction + knex: db.KnexReadonlyTransaction, + variableId: number ): Promise { - const rows: Pick< - DbRawVariable, - "grapherConfigAdmin" | "grapherConfigETL" - >[] = await knexRaw( + const variable = await getGrapherConfigsForVariable(knex, variableId) + if (!variable) return + if (!variable.grapherConfigETL && !variable.grapherConfigAdmin) return + return mergeGrapherConfigs( + variable.grapherConfigETL ?? {}, + variable.grapherConfigAdmin ?? {} + ) +} + +export async function insertNewGrapherConfigForVariable( + knex: db.KnexReadonlyTransaction, + params: { + type: "admin" | "etl" + variableId: number + config: GrapherInterface + } +): Promise { + const { type, variableId, config } = params + + // insert chart config into the database + const configId = await db.getBinaryUUID(knex) + await db.knexRaw( knex, - `SELECT grapherConfigAdmin, grapherConfigETL FROM variables WHERE id = ?`, - [variableId] + `-- sql + INSERT INTO chart_configs (id, patch, full) + VALUES (?, ?, ?) + `, + [configId, JSON.stringify(config), JSON.stringify(config)] + ) + + // make a reference to the config from the variables table + const column = + type === "admin" ? "grapherConfigIdAdmin" : "grapherConfigIdETL" + await db.knexRaw( + knex, + `-- sql + UPDATE variables + SET ?? = ? + WHERE id = ? + `, + [column, configId, variableId] + ) +} + +export async function updateExistingGrapherConfigForVariable( + knex: db.KnexReadonlyTransaction, + params: { + type: "admin" | "etl" + variableId: number + config: GrapherInterface + configId: Buffer + } +): Promise { + const { variableId, config, configId, type } = params + + // let configId: Buffer | undefined + // if (!params.configId) { + // const variable = await getGrapherConfigsForVariable(knex, variableId) + // if (!variable) return + // configId = + // type === "admin" + // ? variable.grapherConfigIdAdmin ?? undefined + // : variable.grapherConfigIdETL ?? undefined + // } + // if (!configId) return + + await db.knexRaw( + knex, + `-- sql + UPDATE chart_configs + SET + patch = ?, + full = ? + WHERE id = ? + `, + [JSON.stringify(config), JSON.stringify(config), configId] ) - if (!rows.length) return - const row = rows[0] - const grapherConfigAdmin = row.grapherConfigAdmin - ? JSON.parse(row.grapherConfigAdmin) - : undefined - const grapherConfigETL = row.grapherConfigETL - ? JSON.parse(row.grapherConfigETL) - : undefined - // TODO(inheritance): use mergeGrapherConfigs instead - return _.merge({}, grapherConfigAdmin, grapherConfigETL) } // TODO: these are domain functions and should live somewhere else diff --git a/packages/@ourworldindata/grapher/src/index.ts b/packages/@ourworldindata/grapher/src/index.ts index 9e9d08b9902..ed27bd627f5 100644 --- a/packages/@ourworldindata/grapher/src/index.ts +++ b/packages/@ourworldindata/grapher/src/index.ts @@ -26,6 +26,7 @@ export { grapherInterfaceWithHiddenTabsOnly, CONTINENTS_INDICATOR_ID, POPULATION_INDICATOR_ID_USED_IN_ADMIN, + DEFAULT_GRAPHER_CONFIG_SCHEMA, } from "./core/GrapherConstants" export { getVariableDataRoute, diff --git a/packages/@ourworldindata/types/src/dbTypes/Variables.ts b/packages/@ourworldindata/types/src/dbTypes/Variables.ts index ce241a997c8..f085efcbf00 100644 --- a/packages/@ourworldindata/types/src/dbTypes/Variables.ts +++ b/packages/@ourworldindata/types/src/dbTypes/Variables.ts @@ -20,8 +20,8 @@ export interface DbInsertVariable { descriptionShort?: string | null dimensions?: JsonString | null display: JsonString - grapherConfigAdmin?: JsonString | null - grapherConfigETL?: JsonString | null + grapherConfigIdAdmin?: Buffer | null + grapherConfigIdETL?: Buffer | null id?: number license?: JsonString | null licenses?: JsonString | null @@ -66,8 +66,6 @@ export type DbEnrichedVariable = Omit< | "dimensions" | "descriptionKey" | "originalMetadata" - | "grapherConfigAdmin" - | "grapherConfigETL" | "processingLog" | "sort" > & { @@ -77,8 +75,6 @@ export type DbEnrichedVariable = Omit< dimensions: VariableDisplayDimension | null descriptionKey: string[] | null originalMetadata: unknown | null - grapherConfigAdmin: GrapherInterface | null - grapherConfigETL: GrapherInterface | null processingLog: unknown | null sort: string[] | null } @@ -149,30 +145,6 @@ export function serializeVariableOriginalMetadata( return originalMetadata ? JSON.stringify(originalMetadata) : null } -export function parseVariableGrapherConfigAdmin( - grapherConfigAdmin: JsonString | null -): GrapherInterface { - return grapherConfigAdmin ? JSON.parse(grapherConfigAdmin) : null -} - -export function serializeVariableGrapherConfigAdmin( - grapherConfigAdmin: GrapherInterface | null -): JsonString | null { - return grapherConfigAdmin ? JSON.stringify(grapherConfigAdmin) : null -} - -export function parseVariableGrapherConfigETL( - grapherConfigETL: JsonString | null -): GrapherInterface { - return grapherConfigETL ? JSON.parse(grapherConfigETL) : null -} - -export function serializeVariableGrapherConfigETL( - grapherConfigETL: GrapherInterface | null -): JsonString | null { - return grapherConfigETL ? JSON.stringify(grapherConfigETL) : null -} - export function parseVariableProcessingLog( processingLog: JsonString | null ): any { @@ -204,10 +176,6 @@ export function parseVariablesRow(row: DbRawVariable): DbEnrichedVariable { dimensions: parseVariableDimensions(row.dimensions), descriptionKey: parseVariableDescriptionKey(row.descriptionKey), originalMetadata: parseVariableOriginalMetadata(row.originalMetadata), - grapherConfigAdmin: parseVariableGrapherConfigAdmin( - row.grapherConfigAdmin - ), - grapherConfigETL: parseVariableGrapherConfigETL(row.grapherConfigETL), processingLog: parseVariableProcessingLog(row.processingLog), sort: parseVariableSort(row.sort), } @@ -224,12 +192,6 @@ export function serializeVariablesRow(row: DbEnrichedVariable): DbRawVariable { originalMetadata: serializeVariableOriginalMetadata( row.originalMetadata ), - grapherConfigAdmin: serializeVariableGrapherConfigAdmin( - row.grapherConfigAdmin - ), - grapherConfigETL: serializeVariableGrapherConfigETL( - row.grapherConfigETL - ), processingLog: serializeVariableProcessingLog(row.processingLog), sort: serializeVariableSort(row.sort), } diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index f888ef464bf..fca5baf4db7 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -634,10 +634,6 @@ export { serializeVariableOriginalMetadata, parseVariableLicenses, serializeVariableLicenses, - parseVariableGrapherConfigAdmin, - serializeVariableGrapherConfigAdmin, - parseVariableGrapherConfigETL, - serializeVariableGrapherConfigETL, parseVariableProcessingLog, serializeVariableProcessingLog, type License,