From f45a4e9e50e87b1326e27da689e378617473e8af Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Sun, 22 Dec 2024 20:27:34 +0100 Subject: [PATCH 01/29] chore: use node 22 --- .nvmrc | 2 +- docs/local-typescript-setup.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.nvmrc b/.nvmrc index 6d80269a4f0..1d9b7831ba9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.16.0 +22.12.0 diff --git a/docs/local-typescript-setup.md b/docs/local-typescript-setup.md index 950fee63df7..9e9d73c76fd 100644 --- a/docs/local-typescript-setup.md +++ b/docs/local-typescript-setup.md @@ -6,7 +6,7 @@ This local environment requires some manual setup. For a faster way to get start You need the following to be able to compile the grapher project and run the tests or use our Storybook: -- [Node 18](https://nodejs.org/en/) +- [Node 22](https://nodejs.org/en/) - [Yarn](https://yarnpkg.com/) All further dependencies will be automatically installed by the yarn package manager. From fa3bde8c219ee035e810789d07ccd97dea3b1a1e Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Sun, 22 Dec 2024 20:42:47 +0100 Subject: [PATCH 02/29] chore: update node version in dockerfiles & tests --- .devcontainer/Dockerfile | 2 +- adminSiteServer/app.test.ts | 2 +- docker-compose.devcontainer.yml | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4d7ce99e3c0..b2a1a5e2011 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG VARIANT=18 +ARG VARIANT=22 FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:dev-${VARIANT} COPY .tmux.conf /home/node/.tmux.conf diff --git a/adminSiteServer/app.test.ts b/adminSiteServer/app.test.ts index e35184c905c..7f3a6d8d031 100644 --- a/adminSiteServer/app.test.ts +++ b/adminSiteServer/app.test.ts @@ -213,7 +213,7 @@ describe("OwidAdminApp", () => { ) expect(nodeVersion.status).toBe(200) const text = await nodeVersion.text() - expect(text).toBe("v18.16.0") + expect(text).toBe("v22.12.0") }) it("should be able to edit a chart via the api", async () => { diff --git a/docker-compose.devcontainer.yml b/docker-compose.devcontainer.yml index eb072173cb7..72ea1d7dff0 100644 --- a/docker-compose.devcontainer.yml +++ b/docker-compose.devcontainer.yml @@ -20,7 +20,7 @@ services: dockerfile: Dockerfile args: # [Choice] Node.js version: 16, 14, 12 - VARIANT: 18 + VARIANT: 22 # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. #USER_UID: 1000 #USER_GID: 1000 diff --git a/package.json b/package.json index 56208e9e7e3..c9b2077f9c5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "version": "3.0.0", "engines": { - "node": ">=18.16" + "node": ">=22.0" }, "packageManager": "yarn@4.1.1", "scripts": { From e9545170d84898faca048449eecf6f19abed525f Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 8 Jan 2025 11:25:04 +0100 Subject: [PATCH 03/29] chore: update node version to 22.13 --- .nvmrc | 2 +- adminSiteServer/app.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.nvmrc b/.nvmrc index 1d9b7831ba9..6fa8dec4cd6 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.0 diff --git a/adminSiteServer/app.test.ts b/adminSiteServer/app.test.ts index 7f3a6d8d031..69b96f537fc 100644 --- a/adminSiteServer/app.test.ts +++ b/adminSiteServer/app.test.ts @@ -213,7 +213,7 @@ describe("OwidAdminApp", () => { ) expect(nodeVersion.status).toBe(200) const text = await nodeVersion.text() - expect(text).toBe("v22.12.0") + expect(text).toBe("v22.13.0") }) it("should be able to edit a chart via the api", async () => { From afcddde3c3e020465d5ff1669c812150bd1cb88b Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 11 Dec 2024 17:22:47 +0100 Subject: [PATCH 04/29] feat: pre-fetch chart views metadata in gdocs --- baker/SiteBaker.tsx | 10 ++++++- baker/siteRenderers.tsx | 1 + db/model/ChartView.ts | 29 +++++++++++++++++++ .../types/src/gdocTypes/Gdoc.ts | 10 +++++++ site/gdocs/AttachmentsContext.tsx | 3 ++ site/gdocs/OwidGdoc.tsx | 1 + 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 db/model/ChartView.ts diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index c422adcaa4e..e6a7495b3fa 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -109,6 +109,7 @@ import { getTombstones } from "../db/model/GdocTombstone.js" import { bakeAllMultiDimDataPages } from "./MultiDimBaker.js" import { getAllLinkedPublishedMultiDimDataPages } from "../db/model/MultiDimDataPage.js" import { getPublicDonorNames } from "../db/model/Donor.js" +import { getAllChartViewsMetadata } from "../db/model/ChartView.js" type PrefetchedAttachments = { donors: string[] @@ -176,7 +177,7 @@ function getProgressBarTotal(bakeSteps: BakeStepConfig): number { bakeSteps.has("dataInsights") || bakeSteps.has("authors") ) { - total += 8 + total += 9 } return total } @@ -459,6 +460,12 @@ export class SiteBaker { name: `✅ Prefetched ${publishedAuthors.length} authors`, }) + const chartViewMetadata = await getAllChartViewsMetadata(knex) + const chartViewMetadataByName = keyBy(chartViewMetadata, "name") + this.progressBar.tick({ + name: `✅ Prefetched ${chartViewMetadata.length} chart views`, + }) + const prefetchedAttachments = { donors, linkedAuthors: publishedAuthors, @@ -469,6 +476,7 @@ export class SiteBaker { graphers: publishedChartsBySlug, }, linkedIndicators: datapageIndicatorsById, + chartViewMetadata: chartViewMetadataByName, } this.progressBar.tick({ name: "✅ Prefetched attachments" }) this._prefetchedAttachmentsCache = prefetchedAttachments diff --git a/baker/siteRenderers.tsx b/baker/siteRenderers.tsx index e53a9e3e521..ae9d5a06337 100644 --- a/baker/siteRenderers.tsx +++ b/baker/siteRenderers.tsx @@ -441,6 +441,7 @@ ${dataInsights latestDataInsights: get(post, "latestDataInsights", []), homepageMetadata: get(post, "homepageMetadata", {}), latestWorkLinks: get(post, "latestWorkLinks", []), + chartViewMetadata: get(post, "chartViewMetadata", {}), }} > diff --git a/db/model/ChartView.ts b/db/model/ChartView.ts new file mode 100644 index 00000000000..6cdce8e4698 --- /dev/null +++ b/db/model/ChartView.ts @@ -0,0 +1,29 @@ +import { ChartViewMetadata, JsonString } from "@ourworldindata/types" +import * as db from "../db.js" + +export const getAllChartViewsMetadata = async ( + knex: db.KnexReadonlyTransaction +): Promise => { + type RawRow = Omit & { + queryParamsForParentChart: JsonString + } + const rows: RawRow[] = await db.knexRaw( + knex, + `-- sql +SELECT cv.name, + cc.full ->> "$.title" as title, + chartConfigId, + pcc.slug as parentChartSlug, + cv.queryParamsForParentChart +FROM chart_views cv +JOIN chart_configs cc on cc.id = cv.chartConfigId +JOIN charts pc on cv.parentChartId = pc.id +JOIN chart_configs pcc on pc.configId = pcc.id + ` + ) + + return rows.map((row) => ({ + ...row, + queryParamsForParentChart: JSON.parse(row.queryParamsForParentChart), + })) +} diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index a6f35022ea3..9731db1b709 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -13,6 +13,7 @@ import { } from "./ArchieMlComponents.js" import { MinimalTag } from "../dbTypes/Tags.js" import { DbEnrichedLatestWork } from "../domainTypes/Author.js" +import { QueryParams } from "../domainTypes/Various.js" export enum OwidGdocPublicationContext { unlisted = "unlisted", @@ -53,6 +54,15 @@ export interface LinkedChart { indicatorId?: number // in case of a datapage } +// An object containing metadata needed for embedded narrative charts +export interface ChartViewMetadata { + name: string + title: string + chartConfigId: string + parentChartSlug: string + queryParamsForParentChart: QueryParams +} + /** * A linked indicator is derived from a linked grapher's config (see: getVariableOfDatapageIfApplicable) * e.g. https://ourworldindata.org/grapher/tomato-production -> config for grapher with { slug: "tomato-production" } -> indicator metadata diff --git a/site/gdocs/AttachmentsContext.tsx b/site/gdocs/AttachmentsContext.tsx index e5b5747889c..49fc9b30ab9 100644 --- a/site/gdocs/AttachmentsContext.tsx +++ b/site/gdocs/AttachmentsContext.tsx @@ -9,6 +9,7 @@ import { LatestDataInsight, OwidGdocHomepageMetadata, DbEnrichedLatestWork, + ChartViewMetadata, } from "@ourworldindata/types" export type Attachments = { @@ -22,6 +23,7 @@ export type Attachments = { latestDataInsights?: LatestDataInsight[] homepageMetadata?: OwidGdocHomepageMetadata latestWorkLinks?: DbEnrichedLatestWork[] + chartViewMetadata?: Record } export const AttachmentsContext = createContext({ @@ -34,4 +36,5 @@ export const AttachmentsContext = createContext({ latestDataInsights: [], homepageMetadata: {}, latestWorkLinks: [], + chartViewMetadata: {}, }) diff --git a/site/gdocs/OwidGdoc.tsx b/site/gdocs/OwidGdoc.tsx index da82c3a30f7..d1502a766d6 100644 --- a/site/gdocs/OwidGdoc.tsx +++ b/site/gdocs/OwidGdoc.tsx @@ -93,6 +93,7 @@ export function OwidGdoc({ latestDataInsights: get(props, "latestDataInsights", []), homepageMetadata: get(props, "homepageMetadata", {}), latestWorkLinks: get(props, "latestWorkLinks", []), + chartViewMetadata: get(props, "chartViewMetadata", {}), }} > From c1067dfa19d01638db36ae2f6738eced20cee3cf Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 11 Dec 2024 17:23:22 +0100 Subject: [PATCH 05/29] feat: NarrativeChart component --- db/model/Gdoc/GdocBase.ts | 4 ++ db/model/Gdoc/enrichedToMarkdown.ts | 11 +++ db/model/Gdoc/enrichedToRaw.ts | 15 ++++ db/model/Gdoc/exampleEnrichedBlocks.ts | 10 +++ db/model/Gdoc/extractGdocComponentInfo.ts | 4 +- db/model/Gdoc/gdocUtils.ts | 1 + db/model/Gdoc/rawToArchie.ts | 20 ++++++ db/model/Gdoc/rawToEnriched.ts | 64 +++++++++++++++++ .../types/src/gdocTypes/ArchieMlComponents.ts | 27 +++++++ packages/@ourworldindata/types/src/index.ts | 3 + packages/@ourworldindata/utils/src/Util.ts | 1 + site/gdocs/components/ArticleBlock.tsx | 10 +++ site/gdocs/components/NarrativeChart.tsx | 70 +++++++++++++++++++ 13 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 site/gdocs/components/NarrativeChart.tsx diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 754471c247a..f8ba74610a5 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -577,6 +577,10 @@ export class GdocBase implements OwidGdocBaseInterface { "key-indicator-collection", "list", "missing-data", + + // Open question: there's not a direct link to a chart here, but there is a chart and also a parent chart + "narrative-chart", + "numbered-list", "people", "people-rows", diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index 2556f74ef2b..794700f30a3 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -127,6 +127,17 @@ ${items} exportComponents ) ) + .with({ type: "narrative-chart" }, (b): string | undefined => + markdownComponent( + "NarrativeChart", + { + name: b.name, + caption: b.caption ? spansToMarkdown(b.caption) : undefined, + // Note: truncated + }, + exportComponents + ) + ) .with({ type: "code" }, (b): string | undefined => { return ( "```\n" + diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index bc27e3356ed..08e8e1fa288 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -48,6 +48,7 @@ import { RawBlockPeople, RawBlockPeopleRows, RawBlockPerson, + RawBlockNarrativeChart, RawBlockCode, } from "@ourworldindata/types" import { spanToHtmlString } from "./gdocUtils.js" @@ -123,6 +124,20 @@ export function enrichedBlockToRawBlock( }, }) ) + .with( + { type: "narrative-chart" }, + (b): RawBlockNarrativeChart => ({ + type: b.type, + value: { + name: b.name, + height: b.height, + row: b.row, + column: b.column, + position: b.position, + caption: b.caption ? spansToHtmlText(b.caption) : undefined, + }, + }) + ) .with( { type: "code" }, (b): RawBlockCode => ({ diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index 7e37a16bc27..74b2a0f8842 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -121,6 +121,16 @@ export const enrichedBlockExamples: Record< caption: boldLinkExampleText, parseErrors: [], }, + "narrative-chart": { + type: "narrative-chart", + name: "world-has-become-less-democratic", + height: "400", + row: "1", + column: "1", + position: "featured", + caption: boldLinkExampleText, + parseErrors: [], + }, code: { type: "code", text: [ diff --git a/db/model/Gdoc/extractGdocComponentInfo.ts b/db/model/Gdoc/extractGdocComponentInfo.ts index 66b4c3c608a..dadac4f23e7 100644 --- a/db/model/Gdoc/extractGdocComponentInfo.ts +++ b/db/model/Gdoc/extractGdocComponentInfo.ts @@ -353,8 +353,8 @@ export function enumerateGdocComponentsWithoutChildren( "additional-charts", "simple-text", "donors", - "socials" - // "narrative-chart" should go here once it's done + "socials", + "narrative-chart" ), }, (c) => handleComponent(c, [], parentPath, path) diff --git a/db/model/Gdoc/gdocUtils.ts b/db/model/Gdoc/gdocUtils.ts index f08dfd85786..d53f3f41a5d 100644 --- a/db/model/Gdoc/gdocUtils.ts +++ b/db/model/Gdoc/gdocUtils.ts @@ -237,6 +237,7 @@ export function extractFilenamesFromBlock( "latest-data-insights", "list", "missing-data", + "narrative-chart", "numbered-list", "people", "people-rows", diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index c2c9b50803d..b1b344a4491 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -47,6 +47,7 @@ import { RawBlockPeople, RawBlockPeopleRows, RawBlockPerson, + RawBlockNarrativeChart, RawBlockCode, } from "@ourworldindata/types" import { isArray } from "@ourworldindata/utils" @@ -128,6 +129,21 @@ function* rawBlockChartToArchieMLString( yield "{}" } +function* rawBlockNarrativeChartToArchieMLString( + block: RawBlockNarrativeChart +): Generator { + yield "{.narrative-chart}" + if (typeof block.value !== "string") { + yield* propertyToArchieMLString("name", block.value) + yield* propertyToArchieMLString("height", block.value) + yield* propertyToArchieMLString("row", block.value) + yield* propertyToArchieMLString("column", block.value) + yield* propertyToArchieMLString("position", block.value) + yield* propertyToArchieMLString("caption", block.value) + } + yield "{}" +} + function* rawBlockCodeToArchieMLString( block: RawBlockCode ): Generator { @@ -840,6 +856,10 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator( .with({ type: "all-charts" }, rawBlockAllChartsToArchieMLString) .with({ type: "aside" }, rawBlockAsideToArchieMLString) .with({ type: "chart" }, rawBlockChartToArchieMLString) + .with( + { type: "narrative-chart" }, + rawBlockNarrativeChartToArchieMLString + ) .with({ type: "code" }, rawBlockCodeToArchieMLString) .with({ type: "donors" }, rawBlockDonorListToArchieMLString) .with({ type: "scroller" }, rawBlockScrollerToArchieMLString) diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index a60bd1f4e79..209fcc59847 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -129,6 +129,8 @@ import { EnrichedBlockPerson, RawBlockPeopleRows, EnrichedBlockPeopleRows, + RawBlockNarrativeChart, + EnrichedBlockNarrativeChart, RawBlockCode, EnrichedBlockCode, } from "@ourworldindata/types" @@ -172,6 +174,7 @@ export function parseRawBlocksToEnrichedBlocks( .with({ type: "blockquote" }, parseBlockquote) .with({ type: "callout" }, parseCallout) .with({ type: "chart" }, parseChart) + .with({ type: "narrative-chart" }, parseNarrativeChart) .with({ type: "code" }, parseCode) .with({ type: "donors" }, parseDonorList) .with({ type: "scroller" }, parseScroller) @@ -496,6 +499,67 @@ const parseChart = (raw: RawBlockChart): EnrichedBlockChart => { } } +const parseNarrativeChart = ( + raw: RawBlockNarrativeChart +): EnrichedBlockNarrativeChart => { + const createError = ( + error: ParseError, + name: string, + caption: Span[] = [] + ): EnrichedBlockNarrativeChart => ({ + type: "narrative-chart", + name, + caption, + parseErrors: [error], + }) + + const val = raw.value + + if (typeof val === "string") { + return { + type: "narrative-chart", + name: val, + parseErrors: [], + } + } else { + if (!val.name) + return createError( + { + message: "name property is missing", + }, + "" + ) + + const warnings: ParseError[] = [] + + const height = val.height + const row = val.row + const column = val.column + // This property is currently unused, a holdover from @mathisonian's gdocs demo. + // We will decide soon™️ if we want to use it for something + let position: ChartPositionChoice | undefined = undefined + if (val.position) + if (val.position === "featured") position = val.position + else { + warnings.push({ + message: "position must be 'featured' or unset", + }) + } + const caption = val.caption ? htmlToSpans(val.caption) : [] + + return omitUndefinedValues({ + type: "narrative-chart", + name: val.name, + height, + row, + column, + position, + caption: caption.length > 0 ? caption : undefined, + parseErrors: [], + }) as EnrichedBlockNarrativeChart + } +} + const parseCode = (raw: RawBlockCode): EnrichedBlockCode => { return { type: "code", diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index cbb50d10c51..06c200041d1 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -86,6 +86,31 @@ export type EnrichedBlockChart = { tabs?: ChartTabKeyword[] } & EnrichedBlockWithParseErrors +export type RawBlockNarrativeChartValue = { + name?: string + height?: string + row?: string + column?: string + // TODO: position is used as a classname apparently? Should be renamed or split + position?: string + caption?: string +} + +export type RawBlockNarrativeChart = { + type: "narrative-chart" + value: RawBlockNarrativeChartValue | string +} + +export type EnrichedBlockNarrativeChart = { + type: "narrative-chart" + name: string + height?: string + row?: string + column?: string + position?: ChartPositionChoice + caption?: Span[] +} & EnrichedBlockWithParseErrors + export type RawBlockCode = { type: "code" value: RawBlockText[] @@ -950,6 +975,7 @@ export type OwidRawGdocBlock = | RawBlockAside | RawBlockCallout | RawBlockChart + | RawBlockNarrativeChart | RawBlockCode | RawBlockDonorList | RawBlockScroller @@ -1001,6 +1027,7 @@ export type OwidEnrichedGdocBlock = | EnrichedBlockAside | EnrichedBlockCallout | EnrichedBlockChart + | EnrichedBlockNarrativeChart | EnrichedBlockCode | EnrichedBlockDonorList | EnrichedBlockScroller diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index f5fd8971661..3be6749e83e 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -287,6 +287,8 @@ export { SocialLinkType, type RawSocialLink, type EnrichedSocialLink, + type RawBlockNarrativeChart, + type EnrichedBlockNarrativeChart, } from "./gdocTypes/ArchieMlComponents.js" export { ChartConfigType, @@ -330,6 +332,7 @@ export { type OwidGdocContent, type OwidGdocIndexItem, extractGdocIndexItem, + type ChartViewMetadata, } from "./gdocTypes/Gdoc.js" export { diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index cd2978a218b..547e9404216 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1711,6 +1711,7 @@ export function traverseEnrichedBlock( type: P.union( "chart-story", "chart", + "narrative-chart", "code", "donors", "horizontal-rule", diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index 07539cd649a..eebaaaad04e 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -44,6 +44,7 @@ import { HomepageSearch } from "./HomepageSearch.js" import LatestDataInsightsBlock from "./LatestDataInsightsBlock.js" import { Socials } from "./Socials.js" import Person from "./Person.js" +import NarrativeChart from "./NarrativeChart.js" import { Container, getLayout } from "./layout.js" export default function ArticleBlock({ @@ -106,6 +107,15 @@ export default function ArticleBlock({ /> ) }) + .with({ type: "narrative-chart" }, (block) => { + return ( + + ) + }) .with({ type: "code" }, (block) => ( (null) + useEmbedChart(0, refChartContainer) + + const attachments = useContext(AttachmentsContext) + + const viewMetadata = attachments.chartViewMetadata?.[d.name] + + if (!viewMetadata) + return ( + + ) + + const metadataStringified = JSON.stringify(viewMetadata) + + return ( +
+
+ {/* + + + */} +
+ {d.caption ? ( +
+ +
+ ) : null} +
+ ) +} From 8f3e381ee3f6892ab7efe6945431afdcfb2c807c Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Thu, 12 Dec 2024 10:33:40 +0100 Subject: [PATCH 06/29] refactor: properly attach gdocs attachments --- baker/SiteBaker.tsx | 5 +++++ db/model/Gdoc/GdocBase.ts | 12 ++++++++++++ site/gdocs/components/NarrativeChart.tsx | 7 +++---- site/gdocs/utils.ts | 5 +++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index e6a7495b3fa..a4ce0036a98 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -56,6 +56,7 @@ import { grabMetadataForGdocLinkedIndicator, TombstonePageData, gdocUrlRegex, + ChartViewMetadata, } from "@ourworldindata/utils" import { execWrapper } from "../db/execWrapper.js" import { countryProfileSpecs } from "../site/countryProfileProjects.js" @@ -121,6 +122,7 @@ type PrefetchedAttachments = { explorers: Record } linkedIndicators: Record + chartViewMetadata: Record } // These aren't all "wordpress" steps @@ -536,6 +538,8 @@ export class SiteBaker { this._prefetchedAttachmentsCache.linkedAuthors.filter( (author) => authorNames.includes(author.name) ), + chartViewMetadata: + this._prefetchedAttachmentsCache.chartViewMetadata, // TODO: Filter } } return this._prefetchedAttachmentsCache @@ -637,6 +641,7 @@ export class SiteBaker { ...attachments.linkedCharts.explorers, } publishedGdoc.linkedIndicators = attachments.linkedIndicators + publishedGdoc.chartViewMetadata = attachments.chartViewMetadata // this is a no-op if the gdoc doesn't have an all-chart block if ("loadRelatedCharts" in publishedGdoc) { diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index f8ba74610a5..baff9b05355 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -56,6 +56,7 @@ import { import { ARCHVED_THUMBNAIL_FILENAME, ChartConfigType, + ChartViewMetadata, DEFAULT_THUMBNAIL_FILENAME, GrapherInterface, LatestDataInsight, @@ -66,6 +67,7 @@ import { OwidGdocLinkType, OwidGdocType, } from "@ourworldindata/types" +import { getAllChartViewsMetadata } from "../ChartView.js" export class GdocBase implements OwidGdocBaseInterface { id!: string @@ -89,6 +91,7 @@ export class GdocBase implements OwidGdocBaseInterface { linkedIndicators: Record = {} linkedDocuments: Record = {} latestDataInsights: LatestDataInsight[] = [] + chartViewMetadata?: Record = {} _omittableFields: string[] = [] constructor(id?: string) { @@ -714,6 +717,14 @@ export class GdocBase implements OwidGdocBaseInterface { } } + async loadChartViewMetadata( + knex: db.KnexReadonlyTransaction + ): Promise { + // TODO: Filter down to only those that are used in the Gdoc + const result = await getAllChartViewsMetadata(knex) + this.chartViewMetadata = keyBy(result, "name") + } + async fetchAndEnrichGdoc(): Promise { const docsClient = google.docs({ version: "v1", @@ -859,6 +870,7 @@ export class GdocBase implements OwidGdocBaseInterface { await this.loadImageMetadataFromDB(knex) await this.loadLinkedCharts(knex) await this.loadLinkedIndicators() // depends on linked charts + await this.loadChartViewMetadata(knex) await this._loadSubclassAttachments(knex) await this.validate(knex) } diff --git a/site/gdocs/components/NarrativeChart.tsx b/site/gdocs/components/NarrativeChart.tsx index f1451d164f4..4ca43b37c88 100644 --- a/site/gdocs/components/NarrativeChart.tsx +++ b/site/gdocs/components/NarrativeChart.tsx @@ -1,6 +1,7 @@ -import React, { useContext, useRef } from "react" +import React, { useRef } from "react" import { useEmbedChart } from "../../hooks.js" import { EnrichedBlockNarrativeChart } from "@ourworldindata/types" +import { useChartViewMetadata } from "../utils.js" import cx from "classnames" import { GRAPHER_PREVIEW_CLASS } from "../../SiteConstants.js" import { AttachmentsContext } from "../../gdocs/AttachmentsContext.js" @@ -19,9 +20,7 @@ export default function NarrativeChart({ const refChartContainer = useRef(null) useEmbedChart(0, refChartContainer) - const attachments = useContext(AttachmentsContext) - - const viewMetadata = attachments.chartViewMetadata?.[d.name] + const viewMetadata = useChartViewMetadata(d.name) if (!viewMetadata) return ( diff --git a/site/gdocs/utils.ts b/site/gdocs/utils.ts index e5501b02994..ffd255f73c1 100644 --- a/site/gdocs/utils.ts +++ b/site/gdocs/utils.ts @@ -148,6 +148,11 @@ export function useDonors(): string[] | undefined { return donors } +export const useChartViewMetadata = (name: string) => { + const { chartViewMetadata } = useContext(AttachmentsContext) + return chartViewMetadata?.[name] +} + export function getShortPageCitation( authors: string[], title: string, From 7b90855850de4e6a66745eace479fc42b6f06dda Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Thu, 12 Dec 2024 10:38:53 +0100 Subject: [PATCH 07/29] enhance: ability to filter `chartViewMetadata` --- baker/SiteBaker.tsx | 4 ++-- db/model/ChartView.ts | 17 +++++++++++------ db/model/Gdoc/GdocBase.ts | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index a4ce0036a98..a62a4b1f40b 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -110,7 +110,7 @@ import { getTombstones } from "../db/model/GdocTombstone.js" import { bakeAllMultiDimDataPages } from "./MultiDimBaker.js" import { getAllLinkedPublishedMultiDimDataPages } from "../db/model/MultiDimDataPage.js" import { getPublicDonorNames } from "../db/model/Donor.js" -import { getAllChartViewsMetadata } from "../db/model/ChartView.js" +import { getChartViewsMetadata } from "../db/model/ChartView.js" type PrefetchedAttachments = { donors: string[] @@ -462,7 +462,7 @@ export class SiteBaker { name: `✅ Prefetched ${publishedAuthors.length} authors`, }) - const chartViewMetadata = await getAllChartViewsMetadata(knex) + const chartViewMetadata = await getChartViewsMetadata(knex) const chartViewMetadataByName = keyBy(chartViewMetadata, "name") this.progressBar.tick({ name: `✅ Prefetched ${chartViewMetadata.length} chart views`, diff --git a/db/model/ChartView.ts b/db/model/ChartView.ts index 6cdce8e4698..2113aab0d84 100644 --- a/db/model/ChartView.ts +++ b/db/model/ChartView.ts @@ -1,15 +1,16 @@ import { ChartViewMetadata, JsonString } from "@ourworldindata/types" import * as db from "../db.js" -export const getAllChartViewsMetadata = async ( - knex: db.KnexReadonlyTransaction +export const getChartViewsMetadata = async ( + knex: db.KnexReadonlyTransaction, + names?: string[] ): Promise => { type RawRow = Omit & { queryParamsForParentChart: JsonString } - const rows: RawRow[] = await db.knexRaw( - knex, - `-- sql + let rows: RawRow[] + + const query = `-- sql SELECT cv.name, cc.full ->> "$.title" as title, chartConfigId, @@ -20,7 +21,11 @@ JOIN chart_configs cc on cc.id = cv.chartConfigId JOIN charts pc on cv.parentChartId = pc.id JOIN chart_configs pcc on pc.configId = pcc.id ` - ) + + if (names) { + if (names.length === 0) return [] + rows = await db.knexRaw(knex, `${query} WHERE cv.name IN (?)`, [names]) + } else rows = await db.knexRaw(knex, query) return rows.map((row) => ({ ...row, diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index baff9b05355..90d02c2257f 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -67,7 +67,7 @@ import { OwidGdocLinkType, OwidGdocType, } from "@ourworldindata/types" -import { getAllChartViewsMetadata } from "../ChartView.js" +import { getChartViewsMetadata } from "../ChartView.js" export class GdocBase implements OwidGdocBaseInterface { id!: string @@ -721,7 +721,7 @@ export class GdocBase implements OwidGdocBaseInterface { knex: db.KnexReadonlyTransaction ): Promise { // TODO: Filter down to only those that are used in the Gdoc - const result = await getAllChartViewsMetadata(knex) + const result = await getChartViewsMetadata(knex) this.chartViewMetadata = keyBy(result, "name") } From 8e45ff47b1ea2c9c19fb1856cc6307fd24f49d11 Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Mon, 16 Dec 2024 23:07:24 +0100 Subject: [PATCH 08/29] refactor: add narrative-chart to `extractGdocComponentInfo` --- db/model/Gdoc/extractGdocComponentInfo.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/db/model/Gdoc/extractGdocComponentInfo.ts b/db/model/Gdoc/extractGdocComponentInfo.ts index dadac4f23e7..308f06953c6 100644 --- a/db/model/Gdoc/extractGdocComponentInfo.ts +++ b/db/model/Gdoc/extractGdocComponentInfo.ts @@ -327,6 +327,7 @@ export function enumerateGdocComponentsWithoutChildren( type: P.union( "chart-story", "chart", + "narrative-chart", "horizontal-rule", "html", "image", From 7b61123f11ce850ef42223354529b74f2cbeb89e Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Tue, 17 Dec 2024 15:33:53 +0100 Subject: [PATCH 09/29] refactor: chartViewMetadata -> narrativeViewInfo --- baker/SiteBaker.tsx | 20 +++++++++---------- baker/siteRenderers.tsx | 2 +- db/model/ChartView.ts | 8 ++++---- db/model/Gdoc/GdocBase.ts | 14 ++++++------- .../types/src/gdocTypes/Gdoc.ts | 2 +- packages/@ourworldindata/types/src/index.ts | 2 +- site/gdocs/AttachmentsContext.tsx | 6 +++--- site/gdocs/OwidGdoc.tsx | 2 +- site/gdocs/components/NarrativeChart.tsx | 4 ++-- site/gdocs/utils.ts | 6 +++--- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index a62a4b1f40b..24ca2315cc1 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -56,7 +56,7 @@ import { grabMetadataForGdocLinkedIndicator, TombstonePageData, gdocUrlRegex, - ChartViewMetadata, + NarrativeViewInfo, } from "@ourworldindata/utils" import { execWrapper } from "../db/execWrapper.js" import { countryProfileSpecs } from "../site/countryProfileProjects.js" @@ -110,7 +110,7 @@ import { getTombstones } from "../db/model/GdocTombstone.js" import { bakeAllMultiDimDataPages } from "./MultiDimBaker.js" import { getAllLinkedPublishedMultiDimDataPages } from "../db/model/MultiDimDataPage.js" import { getPublicDonorNames } from "../db/model/Donor.js" -import { getChartViewsMetadata } from "../db/model/ChartView.js" +import { getNarrativeViewsInfo } from "../db/model/ChartView.js" type PrefetchedAttachments = { donors: string[] @@ -122,7 +122,7 @@ type PrefetchedAttachments = { explorers: Record } linkedIndicators: Record - chartViewMetadata: Record + narrativeViewsInfo: Record } // These aren't all "wordpress" steps @@ -462,10 +462,10 @@ export class SiteBaker { name: `✅ Prefetched ${publishedAuthors.length} authors`, }) - const chartViewMetadata = await getChartViewsMetadata(knex) - const chartViewMetadataByName = keyBy(chartViewMetadata, "name") + const narrativeViewsInfo = await getNarrativeViewsInfo(knex) + const narrativeViewsInfoByName = keyBy(narrativeViewsInfo, "name") this.progressBar.tick({ - name: `✅ Prefetched ${chartViewMetadata.length} chart views`, + name: `✅ Prefetched ${narrativeViewsInfo.length} chart views`, }) const prefetchedAttachments = { @@ -478,7 +478,7 @@ export class SiteBaker { graphers: publishedChartsBySlug, }, linkedIndicators: datapageIndicatorsById, - chartViewMetadata: chartViewMetadataByName, + narrativeViewsInfo: narrativeViewsInfoByName, } this.progressBar.tick({ name: "✅ Prefetched attachments" }) this._prefetchedAttachmentsCache = prefetchedAttachments @@ -538,8 +538,8 @@ export class SiteBaker { this._prefetchedAttachmentsCache.linkedAuthors.filter( (author) => authorNames.includes(author.name) ), - chartViewMetadata: - this._prefetchedAttachmentsCache.chartViewMetadata, // TODO: Filter + narrativeViewsInfo: + this._prefetchedAttachmentsCache.narrativeViewsInfo, // TODO: Filter } } return this._prefetchedAttachmentsCache @@ -641,7 +641,7 @@ export class SiteBaker { ...attachments.linkedCharts.explorers, } publishedGdoc.linkedIndicators = attachments.linkedIndicators - publishedGdoc.chartViewMetadata = attachments.chartViewMetadata + publishedGdoc.narrativeViewsInfo = attachments.narrativeViewsInfo // this is a no-op if the gdoc doesn't have an all-chart block if ("loadRelatedCharts" in publishedGdoc) { diff --git a/baker/siteRenderers.tsx b/baker/siteRenderers.tsx index ae9d5a06337..ba9d4aecbf4 100644 --- a/baker/siteRenderers.tsx +++ b/baker/siteRenderers.tsx @@ -441,7 +441,7 @@ ${dataInsights latestDataInsights: get(post, "latestDataInsights", []), homepageMetadata: get(post, "homepageMetadata", {}), latestWorkLinks: get(post, "latestWorkLinks", []), - chartViewMetadata: get(post, "chartViewMetadata", {}), + narrativeViewsInfo: get(post, "narrativeViewsInfo", {}), }} > diff --git a/db/model/ChartView.ts b/db/model/ChartView.ts index 2113aab0d84..5a7e609694f 100644 --- a/db/model/ChartView.ts +++ b/db/model/ChartView.ts @@ -1,11 +1,11 @@ -import { ChartViewMetadata, JsonString } from "@ourworldindata/types" +import { NarrativeViewInfo, JsonString } from "@ourworldindata/types" import * as db from "../db.js" -export const getChartViewsMetadata = async ( +export const getNarrativeViewsInfo = async ( knex: db.KnexReadonlyTransaction, names?: string[] -): Promise => { - type RawRow = Omit & { +): Promise => { + type RawRow = Omit & { queryParamsForParentChart: JsonString } let rows: RawRow[] diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 90d02c2257f..1b0784d563b 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -56,7 +56,7 @@ import { import { ARCHVED_THUMBNAIL_FILENAME, ChartConfigType, - ChartViewMetadata, + NarrativeViewInfo, DEFAULT_THUMBNAIL_FILENAME, GrapherInterface, LatestDataInsight, @@ -67,7 +67,7 @@ import { OwidGdocLinkType, OwidGdocType, } from "@ourworldindata/types" -import { getChartViewsMetadata } from "../ChartView.js" +import { getNarrativeViewsInfo } from "../ChartView.js" export class GdocBase implements OwidGdocBaseInterface { id!: string @@ -91,7 +91,7 @@ export class GdocBase implements OwidGdocBaseInterface { linkedIndicators: Record = {} linkedDocuments: Record = {} latestDataInsights: LatestDataInsight[] = [] - chartViewMetadata?: Record = {} + narrativeViewsInfo?: Record = {} _omittableFields: string[] = [] constructor(id?: string) { @@ -717,12 +717,12 @@ export class GdocBase implements OwidGdocBaseInterface { } } - async loadChartViewMetadata( + async loadNarrativeViewsInfo( knex: db.KnexReadonlyTransaction ): Promise { // TODO: Filter down to only those that are used in the Gdoc - const result = await getChartViewsMetadata(knex) - this.chartViewMetadata = keyBy(result, "name") + const result = await getNarrativeViewsInfo(knex) + this.narrativeViewsInfo = keyBy(result, "name") } async fetchAndEnrichGdoc(): Promise { @@ -870,7 +870,7 @@ export class GdocBase implements OwidGdocBaseInterface { await this.loadImageMetadataFromDB(knex) await this.loadLinkedCharts(knex) await this.loadLinkedIndicators() // depends on linked charts - await this.loadChartViewMetadata(knex) + await this.loadNarrativeViewsInfo(knex) await this._loadSubclassAttachments(knex) await this.validate(knex) } diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 9731db1b709..35187a30f3c 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -55,7 +55,7 @@ export interface LinkedChart { } // An object containing metadata needed for embedded narrative charts -export interface ChartViewMetadata { +export interface NarrativeViewInfo { name: string title: string chartConfigId: string diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index 3be6749e83e..9e94a05ee01 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -332,7 +332,7 @@ export { type OwidGdocContent, type OwidGdocIndexItem, extractGdocIndexItem, - type ChartViewMetadata, + type NarrativeViewInfo, } from "./gdocTypes/Gdoc.js" export { diff --git a/site/gdocs/AttachmentsContext.tsx b/site/gdocs/AttachmentsContext.tsx index 49fc9b30ab9..a570a6f7b71 100644 --- a/site/gdocs/AttachmentsContext.tsx +++ b/site/gdocs/AttachmentsContext.tsx @@ -9,7 +9,7 @@ import { LatestDataInsight, OwidGdocHomepageMetadata, DbEnrichedLatestWork, - ChartViewMetadata, + NarrativeViewInfo, } from "@ourworldindata/types" export type Attachments = { @@ -23,7 +23,7 @@ export type Attachments = { latestDataInsights?: LatestDataInsight[] homepageMetadata?: OwidGdocHomepageMetadata latestWorkLinks?: DbEnrichedLatestWork[] - chartViewMetadata?: Record + narrativeViewsInfo?: Record } export const AttachmentsContext = createContext({ @@ -36,5 +36,5 @@ export const AttachmentsContext = createContext({ latestDataInsights: [], homepageMetadata: {}, latestWorkLinks: [], - chartViewMetadata: {}, + narrativeViewsInfo: {}, }) diff --git a/site/gdocs/OwidGdoc.tsx b/site/gdocs/OwidGdoc.tsx index d1502a766d6..f0f6137f51d 100644 --- a/site/gdocs/OwidGdoc.tsx +++ b/site/gdocs/OwidGdoc.tsx @@ -93,7 +93,7 @@ export function OwidGdoc({ latestDataInsights: get(props, "latestDataInsights", []), homepageMetadata: get(props, "homepageMetadata", {}), latestWorkLinks: get(props, "latestWorkLinks", []), - chartViewMetadata: get(props, "chartViewMetadata", {}), + narrativeViewsInfo: get(props, "narrativeViewsInfo", {}), }} > diff --git a/site/gdocs/components/NarrativeChart.tsx b/site/gdocs/components/NarrativeChart.tsx index 4ca43b37c88..de5604ffdc6 100644 --- a/site/gdocs/components/NarrativeChart.tsx +++ b/site/gdocs/components/NarrativeChart.tsx @@ -1,7 +1,7 @@ import React, { useRef } from "react" import { useEmbedChart } from "../../hooks.js" import { EnrichedBlockNarrativeChart } from "@ourworldindata/types" -import { useChartViewMetadata } from "../utils.js" +import { useNarrativeViewsInfo } from "../utils.js" import cx from "classnames" import { GRAPHER_PREVIEW_CLASS } from "../../SiteConstants.js" import { AttachmentsContext } from "../../gdocs/AttachmentsContext.js" @@ -20,7 +20,7 @@ export default function NarrativeChart({ const refChartContainer = useRef(null) useEmbedChart(0, refChartContainer) - const viewMetadata = useChartViewMetadata(d.name) + const viewMetadata = useNarrativeViewsInfo(d.name) if (!viewMetadata) return ( diff --git a/site/gdocs/utils.ts b/site/gdocs/utils.ts index ffd255f73c1..78b10b195cb 100644 --- a/site/gdocs/utils.ts +++ b/site/gdocs/utils.ts @@ -148,9 +148,9 @@ export function useDonors(): string[] | undefined { return donors } -export const useChartViewMetadata = (name: string) => { - const { chartViewMetadata } = useContext(AttachmentsContext) - return chartViewMetadata?.[name] +export const useNarrativeViewsInfo = (name: string) => { + const { narrativeViewsInfo } = useContext(AttachmentsContext) + return narrativeViewsInfo?.[name] } export function getShortPageCitation( From eab3dac6cacbbd7ebdbcbdc75763118b4a3751f6 Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Tue, 17 Dec 2024 16:28:12 +0100 Subject: [PATCH 10/29] enhance: narrative views are reflected as links in gdocs --- db/model/Gdoc/GdocBase.ts | 13 +++++++----- db/model/Link.ts | 20 +++++++++++++++++++ .../types/src/gdocTypes/Gdoc.ts | 1 + 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 1b0784d563b..5df9c6164b5 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -48,7 +48,7 @@ import { getVariableMetadata, getVariableOfDatapageIfApplicable, } from "../Variable.js" -import { createLinkFromUrl } from "../Link.js" +import { createLinkForNarrativeChart, createLinkFromUrl } from "../Link.js" import { getMultiDimDataPageBySlug, isMultiDimDataPagePublished, @@ -352,6 +352,13 @@ export class GdocBase implements OwidGdocBaseInterface { componentType: block.type, }), ]) + .with({ type: "narrative-chart" }, (block) => [ + createLinkForNarrativeChart({ + name: block.name, + source: this, + componentType: block.type, + }), + ]) .with({ type: "all-charts" }, (block) => block.top.map((item) => createLinkFromUrl({ @@ -580,10 +587,6 @@ export class GdocBase implements OwidGdocBaseInterface { "key-indicator-collection", "list", "missing-data", - - // Open question: there's not a direct link to a chart here, but there is a chart and also a parent chart - "narrative-chart", - "numbered-list", "people", "people-rows", diff --git a/db/model/Link.ts b/db/model/Link.ts index 4468e6832dd..c2a62a16cc0 100644 --- a/db/model/Link.ts +++ b/db/model/Link.ts @@ -62,3 +62,23 @@ export function createLinkFromUrl({ sourceId: source.id, } satisfies DbInsertPostGdocLink } + +export function createLinkForNarrativeChart({ + name, + source, + componentType, +}: { + name: string + source: GdocBase + componentType: string +}): DbInsertPostGdocLink { + return { + target: name, + linkType: OwidGdocLinkType.NarrativeChart, + queryString: "", + hash: "", + text: "", + componentType, + sourceId: source.id, + } satisfies DbInsertPostGdocLink +} diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 35187a30f3c..9f75af5e70d 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -281,6 +281,7 @@ export enum OwidGdocLinkType { Url = "url", Grapher = "grapher", Explorer = "explorer", + NarrativeChart = "narrative-chart", } export interface OwidGdocLinkJSON { From 8b1fa6e16861bd272ab834fac7c9ad63a6226636 Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Tue, 17 Dec 2024 16:38:01 +0100 Subject: [PATCH 11/29] refactor: filter down `narrativeViewsInfo` --- baker/SiteBaker.tsx | 12 +++++++++--- db/model/Gdoc/GdocBase.ts | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 24ca2315cc1..29dc2c2c934 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -348,7 +348,7 @@ export class SiteBaker { _prefetchedAttachmentsCache: PrefetchedAttachments | undefined = undefined private async getPrefetchedGdocAttachments( knex: db.KnexReadonlyTransaction, - picks?: [string[], string[], string[], string[], string[]] + picks?: [string[], string[], string[], string[], string[], string[]] ): Promise { if (!this._prefetchedAttachmentsCache) { console.log("Prefetching attachments...") @@ -490,6 +490,7 @@ export class SiteBaker { imageFilenames, linkedGrapherSlugs, linkedExplorerSlugs, + linkedNarrativeChartNames, ] = picks const linkedDocuments = pick( this._prefetchedAttachmentsCache.linkedDocuments, @@ -538,8 +539,10 @@ export class SiteBaker { this._prefetchedAttachmentsCache.linkedAuthors.filter( (author) => authorNames.includes(author.name) ), - narrativeViewsInfo: - this._prefetchedAttachmentsCache.narrativeViewsInfo, // TODO: Filter + narrativeViewsInfo: pick( + this._prefetchedAttachmentsCache.narrativeViewsInfo, + linkedNarrativeChartNames + ), } } return this._prefetchedAttachmentsCache @@ -631,6 +634,7 @@ export class SiteBaker { publishedGdoc.linkedImageFilenames, publishedGdoc.linkedChartSlugs.grapher, publishedGdoc.linkedChartSlugs.explorer, + publishedGdoc.linkedNarrativeChartNames, ]) publishedGdoc.donors = attachments.donors publishedGdoc.linkedAuthors = attachments.linkedAuthors @@ -889,6 +893,7 @@ export class SiteBaker { dataInsight.linkedImageFilenames, dataInsight.linkedChartSlugs.grapher, dataInsight.linkedChartSlugs.explorer, + dataInsight.linkedNarrativeChartNames, ]) dataInsight.linkedDocuments = attachments.linkedDocuments dataInsight.imageMetadata = { @@ -962,6 +967,7 @@ export class SiteBaker { publishedAuthor.linkedImageFilenames, publishedAuthor.linkedChartSlugs.grapher, publishedAuthor.linkedChartSlugs.explorer, + publishedAuthor.linkedNarrativeChartNames, ]) // We don't need these to be attached to the gdoc in the current diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 5df9c6164b5..a93e49946d6 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -295,6 +295,14 @@ export class GdocBase implements OwidGdocBaseInterface { return { grapher: [...grapher], explorer: [...explorer] } } + get linkedNarrativeChartNames(): string[] { + const filteredLinks = this.links + .filter((link) => link.linkType === "narrative-chart") + .map((link) => link.target) + + return filteredLinks + } + get hasAllChartsBlock(): boolean { let hasAllChartsBlock = false for (const enrichedBlockSource of this.enrichedBlockSources) { @@ -723,8 +731,10 @@ export class GdocBase implements OwidGdocBaseInterface { async loadNarrativeViewsInfo( knex: db.KnexReadonlyTransaction ): Promise { - // TODO: Filter down to only those that are used in the Gdoc - const result = await getNarrativeViewsInfo(knex) + const result = await getNarrativeViewsInfo( + knex, + this.linkedNarrativeChartNames + ) this.narrativeViewsInfo = keyBy(result, "name") } From 3a7ceca3a3f575789ae740adf496623a867e3a7b Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Tue, 17 Dec 2024 18:06:54 +0100 Subject: [PATCH 12/29] refactor: change `linkType` enum --- ...4799588-PostsGdocsLinksAddNarrativeCharts.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 db/migration/1734454799588-PostsGdocsLinksAddNarrativeCharts.ts diff --git a/db/migration/1734454799588-PostsGdocsLinksAddNarrativeCharts.ts b/db/migration/1734454799588-PostsGdocsLinksAddNarrativeCharts.ts new file mode 100644 index 00000000000..cb1d710cf9c --- /dev/null +++ b/db/migration/1734454799588-PostsGdocsLinksAddNarrativeCharts.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class PostsGdocsLinksAddNarrativeCharts1734454799588 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE posts_gdocs_links + MODIFY linkType ENUM ('gdoc', 'url', 'grapher', 'explorer', 'narrative-chart') NULL`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE posts_gdocs_links + MODIFY linkType ENUM ('gdoc', 'url', 'grapher', 'explorer') NULL`) + } +} From 02717eca2a329f6523ccd8c307c494eeab6261ff Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 18 Dec 2024 20:28:11 +0100 Subject: [PATCH 13/29] fix: fix error when publishing NarrativeChart with error --- site/gdocs/components/NarrativeChart.tsx | 30 ++++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/site/gdocs/components/NarrativeChart.tsx b/site/gdocs/components/NarrativeChart.tsx index de5604ffdc6..c5b3fa6f3e8 100644 --- a/site/gdocs/components/NarrativeChart.tsx +++ b/site/gdocs/components/NarrativeChart.tsx @@ -1,12 +1,12 @@ -import React, { useRef } from "react" +import { useContext, useRef } from "react" import { useEmbedChart } from "../../hooks.js" import { EnrichedBlockNarrativeChart } from "@ourworldindata/types" import { useNarrativeViewsInfo } from "../utils.js" import cx from "classnames" import { GRAPHER_PREVIEW_CLASS } from "../../SiteConstants.js" -import { AttachmentsContext } from "../../gdocs/AttachmentsContext.js" import { BlockErrorFallback } from "./BlockErrorBoundary.js" import SpanElements from "./SpanElements.js" +import { DocumentContext } from "../DocumentContext.js" export default function NarrativeChart({ d, @@ -22,16 +22,21 @@ export default function NarrativeChart({ const viewMetadata = useNarrativeViewsInfo(d.name) - if (!viewMetadata) - return ( - - ) + const { isPreviewing } = useContext(DocumentContext) + + if (!viewMetadata) { + if (isPreviewing) { + return ( + + ) + } else return null // If not previewing, just don't render anything + } const metadataStringified = JSON.stringify(viewMetadata) @@ -47,7 +52,6 @@ export default function NarrativeChart({ key={metadataStringified} className={cx(GRAPHER_PREVIEW_CLASS, "chart")} data-grapher-view-config={metadataStringified} - // data-grapher-src={isExplorer ? undefined : resolvedUrl} style={{ width: "100%", border: "0px none", From 95b23b83e88198044ff23266b9f22f09d87b3166 Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 18 Dec 2024 22:09:56 +0100 Subject: [PATCH 14/29] enhance: add narrative chart support to MultiEmbedder --- .../grapher/src/core/GrapherConstants.ts | 3 + packages/@ourworldindata/grapher/src/index.ts | 1 + site/multiembedder/MultiEmbedder.tsx | 283 +++++++++++------- 3 files changed, 180 insertions(+), 107 deletions(-) diff --git a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts index 1601f36f65e..a4f8a896827 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts @@ -5,6 +5,9 @@ import type { GrapherProgrammaticInterface } from "./Grapher" export const GRAPHER_EMBEDDED_FIGURE_ATTR = "data-grapher-src" export const GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR = "data-grapher-config" +export const GRAPHER_VIEW_EMBEDDED_FIGURE_CONFIG_ATTR = + "data-grapher-view-config" + export const GRAPHER_PAGE_BODY_CLASS = "StandaloneGrapherOrExplorerPage" export const GRAPHER_IS_IN_IFRAME_CLASS = "IsInIframe" export const GRAPHER_TIMELINE_CLASS = "timeline-component" diff --git a/packages/@ourworldindata/grapher/src/index.ts b/packages/@ourworldindata/grapher/src/index.ts index 8d52675e86c..ac3a8142dc2 100644 --- a/packages/@ourworldindata/grapher/src/index.ts +++ b/packages/@ourworldindata/grapher/src/index.ts @@ -10,6 +10,7 @@ export { ChartDimension } from "./chart/ChartDimension" export { GRAPHER_EMBEDDED_FIGURE_ATTR, GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR, + GRAPHER_VIEW_EMBEDDED_FIGURE_CONFIG_ATTR, GRAPHER_PAGE_BODY_CLASS, GRAPHER_IS_IN_IFRAME_CLASS, DEFAULT_GRAPHER_WIDTH, diff --git a/site/multiembedder/MultiEmbedder.tsx b/site/multiembedder/MultiEmbedder.tsx index 45a41a03948..e06e78a75b8 100644 --- a/site/multiembedder/MultiEmbedder.tsx +++ b/site/multiembedder/MultiEmbedder.tsx @@ -10,6 +10,7 @@ import { migrateSelectedEntityNamesParam, SelectionArray, migrateGrapherConfigToLatestVersion, + GRAPHER_VIEW_EMBEDDED_FIGURE_CONFIG_ATTR, } from "@ourworldindata/grapher" import { fetchText, @@ -21,6 +22,7 @@ import { MultiDimDataPageConfig, extractMultiDimChoicesFromQueryStr, fetchWithRetry, + NarrativeViewInfo, } from "@ourworldindata/utils" import { action } from "mobx" import ReactDOM from "react-dom" @@ -41,6 +43,9 @@ import { } from "../../settings/clientSettings.js" import Bugsnag from "@bugsnag/js" import { embedDynamicCollectionGrapher } from "../collections/DynamicCollection.js" +import { match } from "ts-pattern" + +type EmbedType = "grapher" | "explorer" | "multiDim" | "grapherView" const figuresFromDOM = ( container: HTMLElement | Document = document, @@ -109,10 +114,16 @@ class MultiEmbedder { * Use this when you programmatically create/replace charts. */ observeFigures(container: HTMLElement | Document = document) { - const figures = figuresFromDOM( - container, - GRAPHER_EMBEDDED_FIGURE_ATTR - ).concat(figuresFromDOM(container, EXPLORER_EMBEDDED_FIGURE_SELECTOR)) + const figures = figuresFromDOM(container, GRAPHER_EMBEDDED_FIGURE_ATTR) + .concat( + figuresFromDOM(container, EXPLORER_EMBEDDED_FIGURE_SELECTOR) + ) + .concat( + figuresFromDOM( + container, + GRAPHER_VIEW_EMBEDDED_FIGURE_CONFIG_ATTR + ) + ) figures.forEach((figure) => { this.figuresObserver?.observe(figure) @@ -127,33 +138,41 @@ class MultiEmbedder { }) } - @action.bound - async renderInteractiveFigure(figure: Element) { - const isExplorer = figure.hasAttribute( + async renderExplorerIntoFigure(figure: Element) { + const explorerUrl = figure.getAttribute( EXPLORER_EMBEDDED_FIGURE_SELECTOR ) - const isMultiDim = figure.hasAttribute("data-is-multi-dim") - const dataSrc = figure.getAttribute( - isExplorer - ? EXPLORER_EMBEDDED_FIGURE_SELECTOR - : GRAPHER_EMBEDDED_FIGURE_ATTR - ) + if (!explorerUrl) return - if (!dataSrc) return + const { fullUrl, queryStr } = Url.fromURL(explorerUrl) - const hasPreview = isExplorer ? false : !!figure.querySelector("img") - if (!shouldProgressiveEmbed() && hasPreview) return - - // Stop observing visibility as soon as possible, that is not before - // shouldProgressiveEmbed gets a chance to reevaluate a possible change - // in screen size on mobile (i.e. after a rotation). Stopping before - // shouldProgressiveEmbed would prevent rendering interactive charts - // when going from portrait to landscape mode (without page reload). - this.figuresObserver?.unobserve(figure) + const html = await fetchText(fullUrl) + const props: ExplorerProps = await buildExplorerProps( + html, + queryStr, + this.selection + ) + if (props.selection) + this.graphersAndExplorersToUpdate.add(props.selection) + ReactDOM.render(, figure) + } - const { fullUrl, queryStr, queryParams } = Url.fromURL(dataSrc) + private async _renderGrapherComponentIntoFigure( + figure: Element, + { + configUrl, + embedUrl, + additionalConfig, + }: { + configUrl: string + embedUrl?: Url + additionalConfig?: Partial + } + ) { + const { queryStr, queryParams } = embedUrl ?? {} + figure.classList.remove(GRAPHER_PREVIEW_CLASS) const common: GrapherProgrammaticInterface = { isEmbeddedInAnOwidPage: true, queryStr, @@ -162,95 +181,145 @@ class MultiEmbedder { dataApiUrl: DATA_API_URL, } - if (isExplorer) { - const html = await fetchText(fullUrl) - const props: ExplorerProps = await buildExplorerProps( - html, - queryStr, - this.selection - ) - if (props.selection) - this.graphersAndExplorersToUpdate.add(props.selection) - ReactDOM.render(, figure) - } else { - figure.classList.remove(GRAPHER_PREVIEW_CLASS) - const url = new URL(fullUrl) - const slug = url.pathname.split("/").pop() - let configUrl - if (isMultiDim) { - const mdimConfigUrl = `${MULTI_DIM_DYNAMIC_CONFIG_URL}/${slug}.json` - const mdimJsonConfig = await fetchWithRetry(mdimConfigUrl).then( - (res) => res.json() - ) - const mdimConfig = - MultiDimDataPageConfig.fromObject(mdimJsonConfig) - const dimensions = extractMultiDimChoicesFromQueryStr( - url.search, - mdimConfig - ) - const view = mdimConfig.findViewByDimensions(dimensions) - if (!view) { - throw new Error( - `No view found for dimensions ${JSON.stringify( - dimensions - )}` - ) - } - configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/by-uuid/${view.fullConfigId}.config.json` - } else { - configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/${slug}.config.json` - } - const fetchedGrapherPageConfig = await fetchWithRetry( - configUrl - ).then((res) => res.json()) - const grapherPageConfig = migrateGrapherConfigToLatestVersion( - fetchedGrapherPageConfig - ) + const fetchedGrapherPageConfig = await fetchWithRetry(configUrl).then( + (res) => res.json() + ) + const grapherPageConfig = migrateGrapherConfigToLatestVersion( + fetchedGrapherPageConfig + ) - const figureConfigAttr = figure.getAttribute( - GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR - ) - const localConfig = figureConfigAttr - ? JSON.parse(figureConfigAttr) - : {} - - // make sure the tab of the active pane is visible - if (figureConfigAttr && !isEmpty(localConfig)) { - const activeTab = queryParams.tab || grapherPageConfig.tab - if (activeTab === GRAPHER_TAB_OPTIONS.chart) - localConfig.hideChartTabs = false - if (activeTab === GRAPHER_TAB_OPTIONS.map) - localConfig.hasMapTab = true - if (activeTab === GRAPHER_TAB_OPTIONS.table) - localConfig.hasTableTab = true + const figureConfigAttr = figure.getAttribute( + GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR + ) + const localConfig = figureConfigAttr ? JSON.parse(figureConfigAttr) : {} + + // make sure the tab of the active pane is visible + if (figureConfigAttr && !isEmpty(localConfig)) { + const activeTab = queryParams?.tab || grapherPageConfig.tab + if (activeTab === GRAPHER_TAB_OPTIONS.chart) + localConfig.hideChartTabs = false + if (activeTab === GRAPHER_TAB_OPTIONS.map) + localConfig.hasMapTab = true + if (activeTab === GRAPHER_TAB_OPTIONS.table) + localConfig.hasTableTab = true + } + + const config = merge( + {}, // merge mutates the first argument + grapherPageConfig, + common, + additionalConfig, + localConfig, + { + manager: { + selection: new SelectionArray( + this.selection.selectedEntityNames + ), + }, } + ) + if (config.manager?.selection) + this.graphersAndExplorersToUpdate.add(config.manager.selection) - const config = merge( - {}, // merge mutates the first argument - grapherPageConfig, - common, - localConfig, - { - manager: { - selection: new SelectionArray( - this.selection.selectedEntityNames - ), - }, - } - ) - if (config.manager?.selection) - this.graphersAndExplorersToUpdate.add(config.manager.selection) + const grapherRef = Grapher.renderGrapherIntoContainer(config, figure) - const grapherRef = Grapher.renderGrapherIntoContainer( - config, - figure - ) + // Special handling for shared collections + if (window.location.pathname.startsWith("/collection/custom")) { + embedDynamicCollectionGrapher(grapherRef, figure) + } + } + async renderGrapherIntoFigure(figure: Element) { + const embedUrlRaw = figure.getAttribute(GRAPHER_EMBEDDED_FIGURE_ATTR) + if (!embedUrlRaw) return + const embedUrl = Url.fromURL(embedUrlRaw) - // Special handling for shared collections - if (window.location.pathname.startsWith("/collection/custom")) { - embedDynamicCollectionGrapher(grapherRef, figure) - } + const configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/${embedUrl.slug}.config.json` + + await this._renderGrapherComponentIntoFigure(figure, { + configUrl, + embedUrl, + }) + } + async renderMultiDimIntoFigure(figure: Element) { + const embedUrlRaw = figure.getAttribute(GRAPHER_EMBEDDED_FIGURE_ATTR) + if (!embedUrlRaw) return + const embedUrl = Url.fromURL(embedUrlRaw) + + const { queryStr, slug } = embedUrl + + const mdimConfigUrl = `${MULTI_DIM_DYNAMIC_CONFIG_URL}/${slug}.json` + const mdimJsonConfig = await fetchWithRetry(mdimConfigUrl).then((res) => + res.json() + ) + const mdimConfig = MultiDimDataPageConfig.fromObject(mdimJsonConfig) + const dimensions = extractMultiDimChoicesFromQueryStr( + queryStr, + mdimConfig + ) + const view = mdimConfig.findViewByDimensions(dimensions) + if (!view) { + throw new Error( + `No view found for dimensions ${JSON.stringify(dimensions)}` + ) } + + const configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/by-uuid/${view.fullConfigId}.config.json` + + await this._renderGrapherComponentIntoFigure(figure, { + configUrl, + embedUrl, + }) + } + async renderGrapherViewIntoFigure(figure: Element) { + const viewConfigRaw = figure.getAttribute( + GRAPHER_VIEW_EMBEDDED_FIGURE_CONFIG_ATTR + ) + if (!viewConfigRaw) return + const viewConfig: NarrativeViewInfo = JSON.parse(viewConfigRaw) + if (!viewConfig) return + + const configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/by-uuid/${viewConfig.chartConfigId}.config.json` + + await this._renderGrapherComponentIntoFigure(figure, { + configUrl, + additionalConfig: {}, + }) + } + + @action.bound + async renderInteractiveFigure(figure: Element) { + const isExplorer = figure.hasAttribute( + EXPLORER_EMBEDDED_FIGURE_SELECTOR + ) + const isMultiDim = figure.hasAttribute("data-is-multi-dim") + const isGrapherView = figure.hasAttribute( + GRAPHER_VIEW_EMBEDDED_FIGURE_CONFIG_ATTR + ) + + const embedType: EmbedType = isExplorer + ? "explorer" + : isMultiDim + ? "multiDim" + : isGrapherView + ? "grapherView" + : "grapher" + + const hasPreview = isExplorer ? false : !!figure.querySelector("img") + if (!shouldProgressiveEmbed() && hasPreview) return + + // Stop observing visibility as soon as possible, that is not before + // shouldProgressiveEmbed gets a chance to reevaluate a possible change + // in screen size on mobile (i.e. after a rotation). Stopping before + // shouldProgressiveEmbed would prevent rendering interactive charts + // when going from portrait to landscape mode (without page reload). + this.figuresObserver?.unobserve(figure) + + await match(embedType) + .with("explorer", () => this.renderExplorerIntoFigure(figure)) + .with("multiDim", () => this.renderMultiDimIntoFigure(figure)) + .with("grapherView", () => this.renderGrapherViewIntoFigure(figure)) + .with("grapher", () => this.renderGrapherIntoFigure(figure)) + .exhaustive() } setUpGlobalEntitySelectorForEmbeds() { From cab124febf4273473fd4284242f7ba71d1f52864 Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 18 Dec 2024 22:35:40 +0100 Subject: [PATCH 15/29] enhance: basic config to hide some grapher elements --- site/multiembedder/MultiEmbedder.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/site/multiembedder/MultiEmbedder.tsx b/site/multiembedder/MultiEmbedder.tsx index e06e78a75b8..b9bb9cd93e9 100644 --- a/site/multiembedder/MultiEmbedder.tsx +++ b/site/multiembedder/MultiEmbedder.tsx @@ -23,6 +23,7 @@ import { extractMultiDimChoicesFromQueryStr, fetchWithRetry, NarrativeViewInfo, + queryParamsToStr, } from "@ourworldindata/utils" import { action } from "mobx" import ReactDOM from "react-dom" @@ -280,9 +281,18 @@ class MultiEmbedder { const configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/by-uuid/${viewConfig.chartConfigId}.config.json` + const queryStr = queryParamsToStr(viewConfig.queryParamsForParentChart) + await this._renderGrapherComponentIntoFigure(figure, { configUrl, - additionalConfig: {}, + additionalConfig: { + hideRelatedQuestion: true, + hideShareButton: true, // always hidden since the original chart would be shared, not the customized one + hideExploreTheDataButton: false, + manager: { + canonicalUrl: `${BAKED_GRAPHER_URL}/${viewConfig.parentChartSlug}${queryStr}`, + }, + }, }) } From cf94feddedf2212c3f8d3014618de084158b4d5e Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Wed, 18 Dec 2024 22:45:01 +0100 Subject: [PATCH 16/29] fix: correctly generate narrative view query params --- adminSiteServer/apiRouter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 5cf0e042bc3..96278efa0fc 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -3639,7 +3639,7 @@ const createPatchConfigAndQueryParamsForChartView = async ( ...pick(fullConfigIncludingDefaults, CHART_VIEW_PROPS_TO_PERSIST), } - const queryParams = grapherConfigToQueryParams(config) + const queryParams = grapherConfigToQueryParams(patchConfigToSave) const fullConfig = mergeGrapherConfigs(parentChartConfig, patchConfigToSave) return { patchConfig: patchConfigToSave, fullConfig, queryParams } From cc778475c47d3329de8832835ac53f860f63a8d6 Mon Sep 17 00:00:00 2001 From: Marcel Gerber Date: Thu, 19 Dec 2024 18:18:10 +0100 Subject: [PATCH 17/29] refactor: use consistent names -- chart views & narrative charts --- adminSiteClient/AdminSidebar.tsx | 2 +- adminSiteClient/ChartEditor.ts | 4 +-- adminSiteClient/ChartViewIndexPage.tsx | 2 +- adminSiteClient/EditorReferencesTab.tsx | 2 +- adminSiteClient/SaveButtons.tsx | 8 ++--- baker/SiteBaker.tsx | 30 +++++++++---------- baker/siteRenderers.tsx | 2 +- ...454799588-PostsGdocsLinksAddChartViews.ts} | 4 +-- db/model/ChartView.ts | 8 ++--- db/model/Gdoc/GdocBase.ts | 27 +++++++---------- db/model/Link.ts | 4 +-- .../grapher/src/core/GrapherConstants.ts | 4 +-- packages/@ourworldindata/grapher/src/index.ts | 2 +- .../types/src/gdocTypes/Gdoc.ts | 4 +-- packages/@ourworldindata/types/src/index.ts | 2 +- site/gdocs/AttachmentsContext.tsx | 6 ++-- site/gdocs/OwidGdoc.tsx | 2 +- site/gdocs/components/NarrativeChart.tsx | 14 +++++---- site/gdocs/utils.ts | 6 ++-- site/multiembedder/MultiEmbedder.tsx | 24 +++++++-------- 20 files changed, 78 insertions(+), 79 deletions(-) rename db/migration/{1734454799588-PostsGdocsLinksAddNarrativeCharts.ts => 1734454799588-PostsGdocsLinksAddChartViews.ts} (85%) diff --git a/adminSiteClient/AdminSidebar.tsx b/adminSiteClient/AdminSidebar.tsx index 3b7ad4c2d0c..ec76340cbe7 100644 --- a/adminSiteClient/AdminSidebar.tsx +++ b/adminSiteClient/AdminSidebar.tsx @@ -37,7 +37,7 @@ export const AdminSidebar = (): React.ReactElement => ( {chartViewsFeatureEnabled && (
  • - Narrative views + Narrative charts
  • )} diff --git a/adminSiteClient/ChartEditor.ts b/adminSiteClient/ChartEditor.ts index 1a4747fbb39..25b10df424d 100644 --- a/adminSiteClient/ChartEditor.ts +++ b/adminSiteClient/ChartEditor.ts @@ -200,7 +200,7 @@ export class ChartEditor extends AbstractChartEditor { ) } - async saveAsNarrativeView(): Promise { + async saveAsChartView(): Promise { const { patchConfig, grapher } = this const chartJson = omit(patchConfig, CHART_VIEW_PROPS_TO_OMIT) @@ -208,7 +208,7 @@ export class ChartEditor extends AbstractChartEditor { const suggestedName = grapher.title ? slugify(grapher.title) : undefined const name = prompt( - "Please enter a programmatic name for the narrative view. Note that this name cannot be changed later.", + "Please enter a programmatic name for the narrative chart. Note that this name cannot be changed later.", suggestedName ) diff --git a/adminSiteClient/ChartViewIndexPage.tsx b/adminSiteClient/ChartViewIndexPage.tsx index 4211ba1d15b..3bc945314fe 100644 --- a/adminSiteClient/ChartViewIndexPage.tsx +++ b/adminSiteClient/ChartViewIndexPage.tsx @@ -135,7 +135,7 @@ export function ChartViewIndexPage() { }, [admin]) return ( - +
    -

    Narrative views based on this chart

    +

    Narrative charts based on this chart