diff --git a/adminSiteClient/GdocsBreadcrumbsInput.tsx b/adminSiteClient/GdocsManualBreadcrumbsInput.tsx similarity index 73% rename from adminSiteClient/GdocsBreadcrumbsInput.tsx rename to adminSiteClient/GdocsManualBreadcrumbsInput.tsx index 094e7188cb..e2ef87fa2c 100644 --- a/adminSiteClient/GdocsBreadcrumbsInput.tsx +++ b/adminSiteClient/GdocsManualBreadcrumbsInput.tsx @@ -70,7 +70,7 @@ export const BreadcrumbLine = ({ ) } -export const GdocsBreadcrumbsInput = ({ +export const GdocsManualBreadcrumbsInput = ({ gdoc, setCurrentGdoc, errors, @@ -85,56 +85,63 @@ export const GdocsBreadcrumbsInput = ({ breadcrumbs[breadcrumbs.length - 1].href = undefined } else breadcrumbs = undefined - setCurrentGdoc({ ...gdoc, breadcrumbs: breadcrumbs ?? null }) + setCurrentGdoc({ ...gdoc, manualBreadcrumbs: breadcrumbs ?? null }) } const setItemAtIndex = (item: BreadcrumbItem, i: number) => { - const breadcrumbs = [...(gdoc.breadcrumbs ?? [])] + const breadcrumbs = [...(gdoc.manualBreadcrumbs ?? [])] breadcrumbs[i] = item setBreadcrumbs(breadcrumbs) } const removeItemAtIndex = (i: number) => { - const breadcrumbs = [...(gdoc.breadcrumbs ?? [])] + const breadcrumbs = [...(gdoc.manualBreadcrumbs ?? [])] breadcrumbs.splice(i, 1) setBreadcrumbs(breadcrumbs) } return (
-
- Breadcrumbs - -
- {gdoc.breadcrumbs?.map((item, i) => ( +
Breadcrumbs
+ {!!gdoc.breadcrumbs?.length && !gdoc.manualBreadcrumbs?.length && ( +

+ The breadcrumbs for this article will be automatically + generated, based on this article's tags and the tag graph. + If you want to override these breadcrumbs, you can do so + here. +

+ )} + + {gdoc.manualBreadcrumbs?.map((item, i) => ( setItemAtIndex(item, i)} removeItem={() => removeItemAtIndex(i)} key={i} labelError={getPropertyMostCriticalError( - `breadcrumbs[${i}].label`, + `manualBreadcrumbs[${i}].label`, errors )} hrefError={getPropertyMostCriticalError( - `breadcrumbs[${i}].href`, + `manualBreadcrumbs[${i}].href`, errors )} - isLastBreadcrumbItem={i === gdoc.breadcrumbs!.length - 1} + isLastBreadcrumbItem={ + i === gdoc.manualBreadcrumbs!.length - 1 + } /> ))} - {!gdoc.breadcrumbs?.length && No breadcrumbs}
) } diff --git a/adminSiteClient/GdocsSettingsForms.tsx b/adminSiteClient/GdocsSettingsForms.tsx index a191106806..5d555a45df 100644 --- a/adminSiteClient/GdocsSettingsForms.tsx +++ b/adminSiteClient/GdocsSettingsForms.tsx @@ -15,7 +15,7 @@ import { import { GdocsPublishedAt } from "./GdocsDateline.js" import { GdocsPublicationContext } from "./GdocsPublicationContext.js" import { Alert } from "antd" -import { GdocsBreadcrumbsInput } from "./GdocsBreadcrumbsInput.js" +import { GdocsManualBreadcrumbsInput } from "./GdocsManualBreadcrumbsInput.js" const GdocCommonErrors = ({ errors, @@ -155,7 +155,7 @@ export const GdocPostSettings = ({ errors={errors} description="An optional property to override the excerpt of this post in our atom feed, which is used for the newsletter" /> - , boolean > = { - breadcrumbs: true, + breadcrumbs: true, // automatically generated, not actually possible to change via the admin preview + manualBreadcrumbs: true, errors: true, linkedAuthors: false, linkedCharts: true, diff --git a/adminSiteClient/gdocsValidation.ts b/adminSiteClient/gdocsValidation.ts index 0ad8da3f38..fe64982c67 100644 --- a/adminSiteClient/gdocsValidation.ts +++ b/adminSiteClient/gdocsValidation.ts @@ -140,12 +140,12 @@ function validateExcerpt( } } -function validateBreadcrumbs( +function validateManualBreadcrumbs( gdoc: OwidGdocPostInterface, errors: OwidGdocErrorMessage[] ) { - if (gdoc.breadcrumbs) { - for (const [i, breadcrumb] of gdoc.breadcrumbs.entries()) { + if (gdoc.manualBreadcrumbs) { + for (const [i, breadcrumb] of gdoc.manualBreadcrumbs.entries()) { if (!breadcrumb.label) { errors.push({ property: `breadcrumbs[${i}].label`, @@ -155,7 +155,7 @@ function validateBreadcrumbs( } // Last item can be missing a href - if (!breadcrumb.href && i !== gdoc.breadcrumbs.length - 1) { + if (!breadcrumb.href && i !== gdoc.manualBreadcrumbs.length - 1) { errors.push({ property: `breadcrumbs[${i}].href`, type: OwidGdocErrorMessageType.Error, @@ -292,7 +292,7 @@ export const getErrors = (gdoc: OwidGdoc): OwidGdocErrorMessage[] => { if (checkIsGdocPost(gdoc)) { validateRefs(gdoc, errors) validateExcerpt(gdoc, errors) - validateBreadcrumbs(gdoc, errors) + validateManualBreadcrumbs(gdoc, errors) validateAtomFields(gdoc, errors) } else if (checkIsDataInsight(gdoc)) { validateApprovedBy(gdoc, errors) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 8a565eb578..6f6decdb44 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -634,7 +634,7 @@ export class SiteBaker { publishedGdoc.linkedIndicators = attachments.linkedIndicators if ( - !publishedGdoc.breadcrumbs?.length && + !publishedGdoc.manualBreadcrumbs?.length && publishedGdoc.tags?.length ) { publishedGdoc.breadcrumbs = db.getBestBreadcrumbs( diff --git a/db/db.ts b/db/db.ts index ef992fc16c..d64f6d29ad 100644 --- a/db/db.ts +++ b/db/db.ts @@ -390,7 +390,7 @@ export const getPublishedGdocPosts = async ( knex, `-- sql SELECT - g.breadcrumbs, + g.manualBreadcrumbs, g.content, g.createdAt, g.id, @@ -426,7 +426,7 @@ export const getPublishedGdocPostsWithTags = async ( knex, `-- sql SELECT - g.breadcrumbs, + g.manualBreadcrumbs, g.content, g.createdAt, g.id, diff --git a/db/migrateWpPostsToArchieMl.ts b/db/migrateWpPostsToArchieMl.ts index a0938d6c54..e3a9647c39 100644 --- a/db/migrateWpPostsToArchieMl.ts +++ b/db/migrateWpPostsToArchieMl.ts @@ -256,7 +256,7 @@ const migrate = async (trx: db.KnexReadWriteTransaction): Promise => { updatedAt: post.updated_at_in_wordpress, publicationContext: OwidGdocPublicationContext.listed, // TODO: not all articles are listed, take this from the DB revisionId: null, - breadcrumbs: null, + manualBreadcrumbs: null, markdown: null, } const archieMlStatsContent = { diff --git a/db/migration/1736455365750-RenameBreadcrumbsColumn.ts b/db/migration/1736455365750-RenameBreadcrumbsColumn.ts new file mode 100644 index 0000000000..e23ced8420 --- /dev/null +++ b/db/migration/1736455365750-RenameBreadcrumbsColumn.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class RenameBreadcrumbsColumn1736455365750 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`-- sql + ALTER TABLE posts_gdocs RENAME COLUMN breadcrumbs TO manualBreadcrumbs`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`-- sql + ALTER TABLE posts_gdocs RENAME COLUMN manualBreadcrumbs TO breadcrumbs`) + } +} diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 754471c247..f16d18128f 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -80,6 +80,7 @@ export class GdocBase implements OwidGdocBaseInterface { publicationContext: OwidGdocPublicationContext = OwidGdocPublicationContext.unlisted breadcrumbs: BreadcrumbItem[] | null = null + manualBreadcrumbs: BreadcrumbItem[] | null = null tags: DbPlainTag[] | null = null errors: OwidGdocErrorMessage[] = [] donors: string[] = [] diff --git a/db/model/Gdoc/GdocFactory.ts b/db/model/Gdoc/GdocFactory.ts index 4e8f9f8140..de175255d2 100644 --- a/db/model/Gdoc/GdocFactory.ts +++ b/db/model/Gdoc/GdocFactory.ts @@ -207,7 +207,7 @@ export async function getGdocBaseObjectById( ) gdoc.tags = tags - if (!gdoc.breadcrumbs?.length && tags.length) { + if (tags.length) { const parentTagArraysByChildName = await getParentTagArraysByChildName(knex) gdoc.breadcrumbs = getBestBreadcrumbs( @@ -304,7 +304,7 @@ export async function getPublishedGdocBaseObjectBySlug( [gdoc.id] ) gdoc.tags = tags - if (!gdoc.breadcrumbs?.length && tags.length) { + if (tags.length) { const parentTagArraysByChildName = await getParentTagArraysByChildName(knex) gdoc.breadcrumbs = getBestBreadcrumbs( @@ -595,7 +595,7 @@ export function getDbEnrichedGdocFromOwidGdoc( gdoc: OwidGdoc | GdocBase ): DbEnrichedPostGdoc { const enrichedGdoc = { - breadcrumbs: gdoc.breadcrumbs, + manualBreadcrumbs: gdoc.manualBreadcrumbs, content: gdoc.content, createdAt: gdoc.createdAt, id: gdoc.id, diff --git a/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts b/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts index 1c7bc650b2..c5e0c63933 100644 --- a/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts +++ b/packages/@ourworldindata/types/src/dbTypes/PostsGdocs.ts @@ -8,7 +8,7 @@ import { MinimalTag } from "./Tags.js" export const PostsGdocsTableName = "posts_gdocs" export interface DbInsertPostGdoc { - breadcrumbs?: JsonString | null + manualBreadcrumbs?: JsonString | null content: JsonString createdAt: Date id: string @@ -23,10 +23,10 @@ export interface DbInsertPostGdoc { export type DbRawPostGdoc = Required export type DbEnrichedPostGdoc = Omit< DbRawPostGdoc, - "content" | "breadcrumbs" | "published" + "content" | "manualBreadcrumbs" | "published" > & { content: OwidGdocContent - breadcrumbs: BreadcrumbItem[] | null + manualBreadcrumbs: BreadcrumbItem[] | null published: boolean } @@ -62,7 +62,7 @@ export function parsePostsGdocsRow(row: DbRawPostGdoc): DbEnrichedPostGdoc { return { ...row, content: parsePostGdocContent(row.content), - breadcrumbs: parsePostsGdocsBreadcrumbs(row.breadcrumbs), + manualBreadcrumbs: parsePostsGdocsBreadcrumbs(row.manualBreadcrumbs), published: !!row.published, } } @@ -77,10 +77,17 @@ export function parsePostsGdocsWithTagsRow( } export function serializePostsGdocsRow(row: DbEnrichedPostGdoc): DbRawPostGdoc { + // Kind of awkward, but automatic breadcrumbs are part of OwidGdocBaseInterface, + // but not part of the DB schema. So we remove them here. + if ("breadcrumbs" in row) { + delete row.breadcrumbs + } return { ...row, content: serializePostGdocContent(row.content), - breadcrumbs: serializePostsGdocsBreadcrumbs(row.breadcrumbs), + manualBreadcrumbs: serializePostsGdocsBreadcrumbs( + row.manualBreadcrumbs + ), published: row.published ? 1 : 0, } } diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index a6f35022ea..ca6b902717 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -87,7 +87,7 @@ export interface OwidGdocBaseInterface { updatedAt: Date | null revisionId: string | null publicationContext: OwidGdocPublicationContext - breadcrumbs: BreadcrumbItem[] | null + manualBreadcrumbs: BreadcrumbItem[] | null linkedAuthors?: LinkedAuthor[] linkedDocuments?: Record linkedCharts?: Record @@ -96,6 +96,7 @@ export interface OwidGdocBaseInterface { relatedCharts?: RelatedChart[] tags?: MinimalTag[] | null errors?: OwidGdocErrorMessage[] + breadcrumbs?: BreadcrumbItem[] | null markdown: string | null } diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index a81abc69b8..a456eeea21 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1932,7 +1932,7 @@ export function isFiniteWithGuard(value: unknown): value is number { // Use with getParentTagArraysByChildName to collapse all paths to the child into a single array of unique parent tag names export function getUniqueNamesFromParentTagArrays( - parentTagArrays: DbPlainTag[][] + parentTagArrays: Pick[][] ): string[] { const tagNames = new Set( parentTagArrays.flatMap((parentTagArray) => diff --git a/site/gdocs/pages/GdocPost.tsx b/site/gdocs/pages/GdocPost.tsx index 64468b8090..0fc0f892c4 100644 --- a/site/gdocs/pages/GdocPost.tsx +++ b/site/gdocs/pages/GdocPost.tsx @@ -47,6 +47,7 @@ export function GdocPost({ publishedAt, slug, breadcrumbs, + manualBreadcrumbs, }: OwidGdocPostInterface & { isPreviewing?: boolean }) { @@ -90,7 +91,7 @@ export function GdocPost({ {isDeprecated && content["deprecation-notice"] && (