diff --git a/.vscode/settings.json b/.vscode/settings.json index 75a3f5f169..d81610df90 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,6 @@ }, "Prettier-SQL.keywordCase": "upper", "Prettier-SQL.SQLFlavourOverride": "mysql", - "Prettier-SQL.expressionWidth": 80 + "Prettier-SQL.expressionWidth": 80, + "prettier.semi": false } diff --git a/adminSiteClient/GrapherConfigGridEditor.tsx b/adminSiteClient/GrapherConfigGridEditor.tsx index b064688980..3652b23f7e 100644 --- a/adminSiteClient/GrapherConfigGridEditor.tsx +++ b/adminSiteClient/GrapherConfigGridEditor.tsx @@ -332,7 +332,8 @@ export class GrapherConfigGridEditor extends React.Component } diff --git a/baker/GrapherImageBaker.tsx b/baker/GrapherImageBaker.tsx index 51362d1caf..d646d240c3 100644 --- a/baker/GrapherImageBaker.tsx +++ b/baker/GrapherImageBaker.tsx @@ -34,7 +34,7 @@ export async function bakeGrapherToSvgAndPng( optimizeSvgs = false ) { const grapher = initGrapherForSvgExport(jsonConfig) - grapher.receiveOwidData(vardata) + // grapher.receiveOwidData(vardata) const outPath = path.join(outDir, grapher.slug as string) let svgCode = grapher.staticSVG @@ -125,7 +125,7 @@ export async function bakeGrapherToSvg( if (fs.existsSync(outPath) && !overwriteExisting) return const variableIds = grapher.dimensions.map((d) => d.variableId) const vardata = await getDataForMultipleVariables(variableIds) - grapher.receiveOwidData(vardata) + // grapher.receiveOwidData(vardata) let svgCode = grapher.staticSVG if (optimizeSvgs) svgCode = await optimizeSvg(svgCode) @@ -234,6 +234,6 @@ export async function grapherToSVG( const grapher = new Grapher({ ...jsonConfig, manuallyProvideData: true }) grapher.isExportingToSvgOrPng = true grapher.shouldIncludeDetailsInStaticExport = false - grapher.receiveOwidData(vardata) + // grapher.receiveOwidData(vardata) return grapher.staticSVG } diff --git a/baker/updateChartEntities.ts b/baker/updateChartEntities.ts index cbaf6a7410..1f5cfab9ec 100644 --- a/baker/updateChartEntities.ts +++ b/baker/updateChartEntities.ts @@ -93,7 +93,8 @@ const obtainAvailableEntitiesForGrapherConfig = async ( await getVariableDataUsingCache(variableId), ]) ) - grapher.receiveOwidData(variableData) + // TODO: make sure that data is loaded here + // grapher.receiveOwidData(variableData) // If the grapher has a chart tab, then the available entities there are the "most interesting" ones to us if (grapher.hasChartTab) { diff --git a/devTools/svgTester/utils.ts b/devTools/svgTester/utils.ts index 12d20a1c1d..537667bc29 100644 --- a/devTools/svgTester/utils.ts +++ b/devTools/svgTester/utils.ts @@ -419,7 +419,7 @@ export async function renderSvg( { shouldHashQueryStr: false, separator: "?" } ) - grapher.receiveOwidData(configAndData.variableData) + // grapher.receiveOwidData(configAndData.variableData) const durationReceiveData = Date.now() - timeStart const svg = grapher.staticSVG diff --git a/packages/@ourworldindata/explorer/src/Explorer.tsx b/packages/@ourworldindata/explorer/src/Explorer.tsx index 8a9b232f4b..17d13748a4 100644 --- a/packages/@ourworldindata/explorer/src/Explorer.tsx +++ b/packages/@ourworldindata/explorer/src/Explorer.tsx @@ -578,7 +578,7 @@ export class Explorer grapher.setAuthoredVersion(config) grapher.reset() grapher.updateFromObject(config) - grapher.downloadData() + // grapher.downloadData() } @action.bound async updateGrapherFromExplorerUsingVariableIds() { @@ -745,11 +745,11 @@ export class Explorer if (dimensions.length === 0) { // If dimensions are empty, explicitly set the table to an empty table // so we don't end up confusingly showing stale data from a previous chart - grapher.receiveOwidData(new Map()) + // grapher.receiveOwidData(new Map()) } else { - await grapher.downloadLegacyDataFromOwidVariableIds( - inputTableTransformer - ) + // await grapher.downloadLegacyDataFromOwidVariableIds( + // inputTableTransformer + // ) } } diff --git a/packages/@ourworldindata/grapher/src/core/FetchingGrapher.tsx b/packages/@ourworldindata/grapher/src/core/FetchingGrapher.tsx new file mode 100644 index 0000000000..32fbd47737 --- /dev/null +++ b/packages/@ourworldindata/grapher/src/core/FetchingGrapher.tsx @@ -0,0 +1,170 @@ +import { + GrapherInterface, + MultipleOwidVariableDataDimensionsMap, + OwidVariableDataMetadataDimensions, +} from "@ourworldindata/types" +import React from "react" +import { Grapher } from "./Grapher.js" +import { loadVariableDataAndMetadata } from "./loadVariable.js" +import { legacyToOwidTableAndDimensions } from "./LegacyToOwidTable.js" +import { OwidTable } from "@ourworldindata/core-table" + +export interface FetchingGrapherProps { + config?: GrapherInterface + configUrl?: string + queryString?: string + dataApiUrl: string + adminBaseUrl: string + bakedGrapherURL: string +} +export function FetchingGrapher( + props: FetchingGrapherProps +): JSX.Element | null { + // if config is not provided, fetch it from configUrl + console.log("FetchingGrapher") + + const [config, setConfig] = React.useState( + props.config + ) + + const [inputTable, setInputTable] = React.useState( + undefined + ) + + React.useEffect(() => { + async function fetchConfigAndLoadData(): Promise { + console.log("fetchConfigAndLoadData", props.configUrl) + if (!config && props.configUrl) { + const fetchedConfig = await fetch(props.configUrl).then((res) => + res.json() + ) + setConfig(fetchedConfig) + } + console.log("fetchConfigAndLoadData: config", config) + if (!config) return + const dimensions = config.dimensions || [] + if (dimensions.length === 0) return + const variables = dimensions.map((d) => d.variableId) + const variablesDataMap = await loadVariablesDataSite( + variables, + props.dataApiUrl + ) + const inputTable = legacyToOwidTableAndDimensions( + variablesDataMap, + dimensions + ) + console.log("setting input table") + setInputTable(inputTable) + } + void fetchConfigAndLoadData() + }, [props.configUrl, config, props.dataApiUrl]) + + if (!config) return null + if (!inputTable) return null + return ( + + ) +} + +// async function loadVariablesDataAdmin( +// variableFetchBaseUrl: string | undefined, +// variableIds: number[] +// ): Promise { +// const dataFetchPath = (variableId: number): string => +// variableFetchBaseUrl +// ? `${variableFetchBaseUrl}/v1/variableById/data/${variableId}` +// : `/api/data/variables/data/${variableId}.json` +// const metadataFetchPath = (variableId: number): string => +// variableFetchBaseUrl +// ? `${variableFetchBaseUrl}/v1/variableById/metadata/${variableId}` +// : `/api/data/variables/metadata/${variableId}.json` + +// const loadVariableDataPromises = variableIds.map(async (variableId) => { +// const dataPromise = window.admin.getJSON( +// dataFetchPath(variableId) +// ) as Promise +// const metadataPromise = window.admin.getJSON( +// metadataFetchPath(variableId) +// ) as Promise +// const [data, metadata] = await Promise.all([ +// dataPromise, +// metadataPromise, +// ]) +// return { data, metadata: { ...metadata, id: variableId } } +// }) +// const variablesData: OwidVariableDataMetadataDimensions[] = +// await Promise.all(loadVariableDataPromises) +// const variablesDataMap = new Map( +// variablesData.map((data) => [data.metadata.id, data]) +// ) +// return variablesDataMap +// } + +async function loadVariablesDataSite( + variableIds: number[], + dataApiUrl: string +): Promise { + const loadVariableDataPromises = variableIds.map((variableId) => + loadVariableDataAndMetadata(variableId, dataApiUrl) + ) + const variablesData: OwidVariableDataMetadataDimensions[] = + await Promise.all(loadVariableDataPromises) + const variablesDataMap = new Map( + variablesData.map((data) => [data.metadata.id, data]) + ) + return variablesDataMap +} + +// function downloadData(): void { +// if (this.manuallyProvideData) { +// } else if (this.owidDataset) { +// this._receiveOwidDataAndApplySelection(this.owidDataset) +// } else void this.downloadLegacyDataFromOwidVariableIds() +// } + +// async function downloadLegacyDataFromOwidVariableIds( +// inputTableTransformer?: ChartTableTransformer +// ): Promise { +// if (this.variableIds.length === 0) +// // No data to download +// return + +// try { +// let variablesDataMap: MultipleOwidVariableDataDimensionsMap + +// const startMark = performance.now() +// if (this.useAdminAPI) { +// // TODO grapher model: switch this to downloading multiple data and metadata files +// variablesDataMap = await loadVariablesDataAdmin( +// this.dataApiUrlForAdmin, +// this.variableIds +// ) +// } else { +// variablesDataMap = await loadVariablesDataSite( +// this.variableIds, +// this.dataApiUrl +// ) +// } +// this.createPerformanceMeasurement("downloadVariablesData", startMark) + +// this._receiveOwidDataAndApplySelection( +// variablesDataMap, +// inputTableTransformer +// ) +// } catch (err) { +// // eslint-disable-next-line no-console +// console.log(`Error fetching '${err}'`) +// console.error(err) +// Bugsnag?.notify(`Error fetching variables: ${err}`, (event) => { +// event.addMetadata("context", { +// variableIds: this.variableIds, +// }) +// }) +// } +// } diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.tsx b/packages/@ourworldindata/grapher/src/core/Grapher.tsx index af860e6c90..20d94dc78d 100644 --- a/packages/@ourworldindata/grapher/src/core/Grapher.tsx +++ b/packages/@ourworldindata/grapher/src/core/Grapher.tsx @@ -241,55 +241,6 @@ declare global { } } -async function loadVariablesDataAdmin( - variableFetchBaseUrl: string | undefined, - variableIds: number[] -): Promise { - const dataFetchPath = (variableId: number): string => - variableFetchBaseUrl - ? `${variableFetchBaseUrl}/v1/variableById/data/${variableId}` - : `/api/data/variables/data/${variableId}.json` - const metadataFetchPath = (variableId: number): string => - variableFetchBaseUrl - ? `${variableFetchBaseUrl}/v1/variableById/metadata/${variableId}` - : `/api/data/variables/metadata/${variableId}.json` - - const loadVariableDataPromises = variableIds.map(async (variableId) => { - const dataPromise = window.admin.getJSON( - dataFetchPath(variableId) - ) as Promise - const metadataPromise = window.admin.getJSON( - metadataFetchPath(variableId) - ) as Promise - const [data, metadata] = await Promise.all([ - dataPromise, - metadataPromise, - ]) - return { data, metadata: { ...metadata, id: variableId } } - }) - const variablesData: OwidVariableDataMetadataDimensions[] = - await Promise.all(loadVariableDataPromises) - const variablesDataMap = new Map( - variablesData.map((data) => [data.metadata.id, data]) - ) - return variablesDataMap -} - -async function loadVariablesDataSite( - variableIds: number[], - dataApiUrl: string -): Promise { - const loadVariableDataPromises = variableIds.map((variableId) => - loadVariableDataAndMetadata(variableId, dataApiUrl) - ) - const variablesData: OwidVariableDataMetadataDimensions[] = - await Promise.all(loadVariableDataPromises) - const variablesDataMap = new Map( - variablesData.map((data) => [data.metadata.id, data]) - ) - return variablesDataMap -} - const DEFAULT_MS_PER_TICK = 100 // Exactly the same as GrapherInterface, but contains options that developers want but authors won't be touching. @@ -572,8 +523,6 @@ export class Grapher this.updateFromObject(props) } - if (!props.table) this.downloadData() - this.populateFromQueryParams( legacyToCurrentGrapherQueryParams(props.queryStr ?? "") ) @@ -619,14 +568,6 @@ export class Grapher return obj } - @action.bound downloadData(): void { - if (this.manuallyProvideData) { - // ignore - } else if (this.owidDataset) { - this._receiveOwidDataAndApplySelection(this.owidDataset) - } else void this.downloadLegacyDataFromOwidVariableIds() - } - @action.bound updateFromObject(obj?: GrapherProgrammaticInterface): void { if (!obj) return @@ -1101,63 +1042,9 @@ export class Grapher // In old browsers, the above may throw an error - just ignore it } } - - @action.bound - async downloadLegacyDataFromOwidVariableIds( - inputTableTransformer?: ChartTableTransformer - ): Promise { - if (this.variableIds.length === 0) - // No data to download - return - - try { - let variablesDataMap: MultipleOwidVariableDataDimensionsMap - - const startMark = performance.now() - if (this.useAdminAPI) { - // TODO grapher model: switch this to downloading multiple data and metadata files - variablesDataMap = await loadVariablesDataAdmin( - this.dataApiUrlForAdmin, - this.variableIds - ) - } else { - variablesDataMap = await loadVariablesDataSite( - this.variableIds, - this.dataApiUrl - ) - } - this.createPerformanceMeasurement( - "downloadVariablesData", - startMark - ) - - this._receiveOwidDataAndApplySelection( - variablesDataMap, - inputTableTransformer - ) - } catch (err) { - // eslint-disable-next-line no-console - console.log(`Error fetching '${err}'`) - console.error(err) - Bugsnag?.notify(`Error fetching variables: ${err}`, (event) => { - event.addMetadata("context", { - variableIds: this.variableIds, - }) - }) - } - } - - @action.bound receiveOwidData( - json: MultipleOwidVariableDataDimensionsMap - ): void { - // TODO grapher model: switch this to downloading multiple data and metadata files - this._receiveOwidDataAndApplySelection(json) - } - @action.bound private _setInputTable( json: MultipleOwidVariableDataDimensionsMap, - legacyConfig: Partial, - inputTableTransformer?: ChartTableTransformer + legacyConfig: Partial ): void { // TODO grapher model: switch this to downloading multiple data and metadata files @@ -1180,9 +1067,7 @@ export class Grapher startMark ) - if (inputTableTransformer) - this.inputTable = inputTableTransformer(tableWithColors) - else this.inputTable = tableWithColors + this.inputTable = tableWithColors // We need to reset the dimensions because some of them may have changed slugs in the legacy // transformation (can happen when columns use targetTime) @@ -1197,30 +1082,17 @@ export class Grapher } else this.applyOriginalSelectionAsAuthored() } - @action rebuildInputOwidTable( - inputTableTransformer?: ChartTableTransformer - ): void { + @action rebuildInputOwidTable(): void { // TODO grapher model: switch this to downloading multiple data and metadata files if (!this.legacyVariableDataJson) return this._setInputTable( this.legacyVariableDataJson, - this.legacyConfigAsAuthored, - inputTableTransformer + this.legacyConfigAsAuthored ) } @observable private legacyVariableDataJson?: MultipleOwidVariableDataDimensionsMap - - @action.bound private _receiveOwidDataAndApplySelection( - json: MultipleOwidVariableDataDimensionsMap, - inputTableTransformer?: ChartTableTransformer - ): void { - this.legacyVariableDataJson = json - - this.rebuildInputOwidTable(inputTableTransformer) - } - @action.bound appendNewEntitySelectionOptions(): void { const { selection } = this const currentEntities = selection.availableEntityNameSet @@ -1449,12 +1321,6 @@ export class Grapher // todo: can we remove this? // I believe these states can only occur during editing. @action.bound private ensureValidConfigWhenEditing(): void { - this.disposers.push( - reaction( - () => this.variableIds, - () => this.downloadLegacyDataFromOwidVariableIds() - ) - ) const disposers = [ autorun(() => { if (!this.availableTabs.includes(this.activeTab)) diff --git a/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.ts b/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.ts index 79b4fa25e8..bc43da4112 100644 --- a/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.ts +++ b/packages/@ourworldindata/grapher/src/core/LegacyToOwidTable.ts @@ -86,7 +86,8 @@ export const legacyToOwidTableAndDimensions = ( const valueColumnColor = dimension.display?.color // Ensure the column slug is unique by copying it from the dimensions // (there can be two columns of the same variable with different targetTimes) - valueColumnDef.slug = dimension.slug + if (dimension.slug) valueColumnDef.slug = dimension.slug + else throw new Error("Dimension slug was undefined") // Because database columns can contain mixed types, we want to avoid // parsing for Grapher data until we fix that. valueColumnDef.skipParsing = true diff --git a/packages/@ourworldindata/grapher/src/index.ts b/packages/@ourworldindata/grapher/src/index.ts index 13ca26a69c..ef77a13b1e 100644 --- a/packages/@ourworldindata/grapher/src/index.ts +++ b/packages/@ourworldindata/grapher/src/index.ts @@ -7,6 +7,7 @@ export { type ColorScaleBin, } from "./color/ColorScaleBin" export { ChartDimension } from "./chart/ChartDimension" +export { FetchingGrapher } from "./core/FetchingGrapher" export { GRAPHER_EMBEDDED_FIGURE_ATTR, GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR, diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx index 673dbc7a39..c26a470b18 100644 --- a/site/DataPageV2Content.tsx +++ b/site/DataPageV2Content.tsx @@ -1,5 +1,8 @@ -import { useState, useEffect, useMemo } from "react" -import { Grapher, GrapherProgrammaticInterface } from "@ourworldindata/grapher" +import { useState, useMemo } from "react" +import { + FetchingGrapher, + GrapherProgrammaticInterface, +} from "@ourworldindata/grapher" import { REUSE_THIS_WORK_SECTION_ID, DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, @@ -18,7 +21,13 @@ import { import { DocumentContext } from "./gdocs/DocumentContext.js" import { AttachmentsContext } from "./gdocs/AttachmentsContext.js" import StickyNav from "./blocks/StickyNav.js" -import { BAKED_BASE_URL } from "../settings/clientSettings.js" +import { DebugProvider } from "./gdocs/DebugContext.js" +import { + ADMIN_BASE_URL, + BAKED_BASE_URL, + BAKED_GRAPHER_URL, + DATA_API_URL, +} from "../settings/clientSettings.js" import Image from "./gdocs/components/Image.js" import AboutThisData from "./AboutThisData.js" import MetadataSection from "./MetadataSection.js" @@ -72,8 +81,6 @@ export const DataPageV2Content = ({ grapherConfig: GrapherInterface imageMetadata: Record }) => { - const [grapher, setGrapher] = useState(undefined) - const titleFragments = joinTitleFragments( datapageData.attributionShort, datapageData.titleVariant @@ -89,10 +96,6 @@ export const DataPageV2Content = ({ [grapherConfig] ) - useEffect(() => { - setGrapher(new Grapher(mergedGrapherConfig)) - }, [mergedGrapherConfig]) - const stickyNavLinks = [ { text: "Explore the Data", @@ -147,9 +150,11 @@ export const DataPageV2Content = ({ > - @@ -181,14 +186,11 @@ export const DataPageV2Content = ({ - - - - + {grapher.slug && (