diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 5c83dcab831..f41d008667c 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -50,12 +50,19 @@ import { getVariableOfDatapageIfApplicable, } from "../Variable.js" import { createLinkFromUrl } from "../Link.js" -import { OwidGdoc, OwidGdocContent, OwidGdocType } from "@ourworldindata/types" +import { + OwidGdoc, + OwidGdocContent, + OwidGdocType, + DbRawAuthor, + DbEnrichedAuthor, +} from "@ourworldindata/types" import { KnexReadonlyTransaction } from "../../db" export class GdocBase implements OwidGdocBaseInterface { id!: string slug: string = "" + authors: DbEnrichedAuthor[] = [] content!: OwidGdocContent published: boolean = false createdAt: Date = new Date() @@ -178,6 +185,10 @@ export class GdocBase implements OwidGdocBaseInterface { return [...details] } + async loadAuthors(knex: db.KnexReadonlyTransaction): Promise { + this.authors = await getMinimalAuthorByNames(knex, this.content.authors) + } + get links(): DbInsertPostGdocLink[] { const links: DbInsertPostGdocLink[] = [] @@ -643,8 +654,17 @@ export class GdocBase implements OwidGdocBaseInterface { } async loadLinkedDocuments(knex: db.KnexReadonlyTransaction): Promise { + if (!this.authors.length) await this.loadAuthors(knex) + + const authorIds = excludeNullish( + this.authors.map((author) => author.id) + ) + const linkedDocuments: OwidGdocMinimalPostInterface[] = - await getMinimalGdocPostsByIds(knex, this.linkedDocumentIds) + await getMinimalGdocPostsByIds(knex, [ + ...this.linkedDocumentIds, + ...authorIds, + ]) this.linkedDocuments = keyBy(linkedDocuments, "id") } @@ -792,6 +812,8 @@ export class GdocBase implements OwidGdocBaseInterface { } async loadState(knex: db.KnexReadonlyTransaction): Promise { + // TODO explain why we're loading authors first + await this.loadAuthors(knex) await this.loadLinkedDocuments(knex) await this.loadImageMetadataFromDB(knex) await this.loadLinkedCharts(knex) @@ -868,3 +890,31 @@ export async function getMinimalGdocPostsByIds( } satisfies OwidGdocMinimalPostInterface }) } + +export async function getMinimalAuthorByNames( + knex: KnexReadonlyTransaction, + names: string[] +): Promise { + if (names.length === 0) return [] + const rows = await db.knexRaw( + knex, + `-- sql + SELECT + id, + content ->> '$.title' as title + FROM posts_gdocs + WHERE type = 'author' + AND content->>"$.title" in (:names) + AND published = 1`, + { names } + ) + + const rowsByName = keyBy(rows, "title") + + return names.map((name) => { + return { + id: rowsByName[name]?.id || null, + title: name, + } + }) +} diff --git a/db/model/Gdoc/GdocFactory.ts b/db/model/Gdoc/GdocFactory.ts index 34e93715670..1fa239efc20 100644 --- a/db/model/Gdoc/GdocFactory.ts +++ b/db/model/Gdoc/GdocFactory.ts @@ -160,6 +160,7 @@ export async function getGdocBaseObjectById( const enrichedRow = parsePostsGdocsRow(row) const gdoc: OwidGdocBaseInterface = { ...enrichedRow, + authors: [], tags: null, } satisfies OwidGdocBaseInterface if (fetchLinkedTags) { @@ -244,6 +245,7 @@ export async function getGdocBaseObjectBySlug( const enrichedRow = parsePostsGdocsRow(row) const gdoc: OwidGdocBaseInterface = { ...enrichedRow, + authors: [], tags: null, } satisfies OwidGdocBaseInterface if (fetchLinkedTags) { @@ -387,6 +389,7 @@ export async function getAndLoadPublishedDataInsights( const enrichedRows = rows.map((row) => { return { ...parsePostsGdocsRow(row), + authors: [], tags: groupedTags[row.id] ? groupedTags[row.id] : null, } satisfies OwidGdocBaseInterface }) @@ -507,6 +510,7 @@ export function getDbEnrichedGdocFromOwidGdoc( gdoc: OwidGdoc | GdocBase ): DbEnrichedPostGdoc { const enrichedGdoc = { + authors: gdoc.authors, breadcrumbs: gdoc.breadcrumbs, content: gdoc.content, createdAt: gdoc.createdAt, diff --git a/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts b/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts index 1c7bc650b22..127b90ccb18 100644 --- a/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts +++ b/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts @@ -1,3 +1,4 @@ +import { DbEnrichedAuthor } from "../domainTypes/Author.js" import { BreadcrumbItem } from "../domainTypes/Site.js" import { JsonString } from "../domainTypes/Various.js" import { @@ -25,6 +26,7 @@ export type DbEnrichedPostGdoc = Omit< DbRawPostGdoc, "content" | "breadcrumbs" | "published" > & { + authors: DbEnrichedAuthor[] content: OwidGdocContent breadcrumbs: BreadcrumbItem[] | null published: boolean @@ -61,6 +63,7 @@ export function serializePostsGdocsBreadcrumbs( export function parsePostsGdocsRow(row: DbRawPostGdoc): DbEnrichedPostGdoc { return { ...row, + authors: [], content: parsePostGdocContent(row.content), breadcrumbs: parsePostsGdocsBreadcrumbs(row.breadcrumbs), published: !!row.published, @@ -72,15 +75,20 @@ export function parsePostsGdocsWithTagsRow( ): DBEnrichedPostGdocWithTags { return { ...parsePostsGdocsRow(row), + authors: [], tags: JSON.parse(row.tags), } } export function serializePostsGdocsRow(row: DbEnrichedPostGdoc): DbRawPostGdoc { + const { authors: _authors, ...rowWithoutAuthors } = row + return { - ...row, - content: serializePostGdocContent(row.content), - breadcrumbs: serializePostsGdocsBreadcrumbs(row.breadcrumbs), - published: row.published ? 1 : 0, + ...rowWithoutAuthors, + content: serializePostGdocContent(rowWithoutAuthors.content), + breadcrumbs: serializePostsGdocsBreadcrumbs( + rowWithoutAuthors.breadcrumbs + ), + published: rowWithoutAuthors.published ? 1 : 0, } } diff --git a/packages/@ourworldindata/types/src/domainTypes/Author.ts b/packages/@ourworldindata/types/src/domainTypes/Author.ts index 75e4850f93f..c16bf549286 100644 --- a/packages/@ourworldindata/types/src/domainTypes/Author.ts +++ b/packages/@ourworldindata/types/src/domainTypes/Author.ts @@ -1,3 +1,13 @@ +export interface DbRawAuthor { + id: string + title: string +} + +export interface DbEnrichedAuthor { + id: string | null + title: string +} + export interface DbRawLatestWork { id: string slug: string diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index cfeb590f0ba..48f4432ff46 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -13,7 +13,10 @@ import { } from "./ArchieMlComponents.js" import { DbChartTagJoin } from "../dbTypes/ChartTags.js" import { MinimalTag } from "../dbTypes/Tags.js" -import { DbEnrichedLatestWork } from "../domainTypes/Author.js" +import { + DbEnrichedAuthor, + DbEnrichedLatestWork, +} from "../domainTypes/Author.js" export enum OwidGdocPublicationContext { unlisted = "unlisted", @@ -59,6 +62,7 @@ export interface OwidGdocBaseInterface { id: string slug: string // TODO: should we type this as a union of the possible content types instead? + authors: DbEnrichedAuthor[] content: OwidGdocContent published: boolean createdAt: Date diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index 2c49fcab61b..11123910861 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -657,6 +657,8 @@ export { RedirectCode, type DbPlainRedirect } from "./dbTypes/Redirects.js" export type { Nominal } from "./NominalType.js" export { + type DbRawAuthor, + type DbEnrichedAuthor, type DbRawLatestWork, type DbEnrichedLatestWork, parseLatestWork, diff --git a/site/gdocs/components/Byline.tsx b/site/gdocs/components/Byline.tsx new file mode 100644 index 00000000000..e3b1a9d2b00 --- /dev/null +++ b/site/gdocs/components/Byline.tsx @@ -0,0 +1,24 @@ +import { DbEnrichedAuthor } from "@ourworldindata/types" +import React from "react" +import { useLinkedDocument } from "../utils.js" + +export const Byline = ({ authors }: { authors: DbEnrichedAuthor[] }) => { + return ( + <> + By:{" "} + {authors.map((author) => ( + + ))} + + ) +} + +const LinkedAuthor = ({ author }: { author: DbEnrichedAuthor }) => { + const gdocUrl = `https://docs.google.com/document/d/${author.id}` + const { linkedDocument } = useLinkedDocument(gdocUrl) + + if (linkedDocument && linkedDocument.published && linkedDocument.slug) { + return {author.title} + } + return <>{author.title} +} diff --git a/site/gdocs/components/OwidGdocHeader.tsx b/site/gdocs/components/OwidGdocHeader.tsx index de626356683..771f0cb66bd 100644 --- a/site/gdocs/components/OwidGdocHeader.tsx +++ b/site/gdocs/components/OwidGdocHeader.tsx @@ -2,17 +2,18 @@ import React from "react" import cx from "classnames" import { BreadcrumbItem, + DbEnrichedAuthor, OwidGdocPostContent, OwidGdocType, formatDate, } from "@ourworldindata/utils" -import { formatAuthors } from "../../clientFormatting.js" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js" import { faBook } from "@fortawesome/free-solid-svg-icons" import { faCreativeCommons } from "@fortawesome/free-brands-svg-icons" import Image from "./Image.js" import { Breadcrumbs } from "../../Breadcrumb/Breadcrumb.js" import { breadcrumbColorForCoverColor } from "../utils.js" +import { Byline } from "./Byline.js" function OwidArticleHeader({ content, @@ -21,7 +22,7 @@ function OwidArticleHeader({ breadcrumbs, }: { content: OwidGdocPostContent - authors: string[] + authors: DbEnrichedAuthor[] publishedAt: Date | null breadcrumbs?: BreadcrumbItem[] }) { @@ -79,12 +80,7 @@ function OwidArticleHeader({
{content.dateline || @@ -121,7 +117,7 @@ function OwidTopicPageHeader({ authors, }: { content: OwidGdocPostContent - authors: string[] + authors: DbEnrichedAuthor[] }) { return (
@@ -132,12 +128,7 @@ function OwidTopicPageHeader({ {content.subtitle}

- {"By "} - - {formatAuthors({ - authors, - })} - +

) @@ -148,7 +139,7 @@ function OwidLinearTopicPageHeader({ authors, }: { content: OwidGdocPostContent - authors: string[] + authors: DbEnrichedAuthor[] }) { return (
@@ -159,12 +150,7 @@ function OwidLinearTopicPageHeader({ {content.subtitle}

- {"By "} - - {formatAuthors({ - authors, - })} - +

{content.dateline} @@ -175,7 +161,7 @@ function OwidLinearTopicPageHeader({ export function OwidGdocHeader(props: { content: OwidGdocPostContent - authors: string[] + authors: DbEnrichedAuthor[] publishedAt: Date | null breadcrumbs?: BreadcrumbItem[] }) { diff --git a/site/gdocs/pages/GdocPost.tsx b/site/gdocs/pages/GdocPost.tsx index 40d302d7dc8..629f21c1bed 100644 --- a/site/gdocs/pages/GdocPost.tsx +++ b/site/gdocs/pages/GdocPost.tsx @@ -40,6 +40,7 @@ const citationDescriptionsByArticleType: Record< } export function GdocPost({ + authors, content, publishedAt, slug, @@ -83,7 +84,7 @@ export function GdocPost({ > diff --git a/site/gdocs/utils.tsx b/site/gdocs/utils.tsx index be7e0e57d2e..682045e4a73 100644 --- a/site/gdocs/utils.tsx +++ b/site/gdocs/utils.tsx @@ -69,6 +69,8 @@ export const useLinkedDocument = ( } else if (!linkedDocument.published) { errorMessage = `Article with slug "${linkedDocument.slug}" isn't published.` } + + //todo replace with getCanonicalUrl const subdirectory = linkedDocument.type === OwidGdocType.DataInsight ? "data-insights/" : "" return {