Skip to content

Commit

Permalink
Merge pull request #3120 from owid/homepage-gdoc
Browse files Browse the repository at this point in the history
🎉 Homepage Gdoc type
  • Loading branch information
ikesau authored Feb 16, 2024
2 parents 0151ad6 + 99ec19b commit 65473c6
Show file tree
Hide file tree
Showing 26 changed files with 716 additions and 9 deletions.
3 changes: 3 additions & 0 deletions adminSiteClient/GdocsIndexPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Modal, SearchField } from "./Forms.js"
import { EditableTags } from "./EditableTags.js"
import {
faCirclePlus,
faHouse,
faLightbulb,
faNewspaper,
faPuzzlePiece,
Expand Down Expand Up @@ -37,6 +38,7 @@ const iconGdocTypeMap = {
[OwidGdocType.TopicPage]: <FontAwesomeIcon icon={faLightbulb} />,
[OwidGdocType.LinearTopicPage]: <FontAwesomeIcon icon={faLightbulb} />,
[OwidGdocType.DataInsight]: <FontAwesomeIcon icon={faThList} />,
[OwidGdocType.Homepage]: <FontAwesomeIcon icon={faHouse} />,
}

@observer
Expand Down Expand Up @@ -113,6 +115,7 @@ export class GdocsIndexPage extends React.Component<GdocsMatchProps> {
[OwidGdocType.TopicPage]: false,
[OwidGdocType.LinearTopicPage]: false,
[OwidGdocType.DataInsight]: false,
[OwidGdocType.Homepage]: false,
}

@observable search = { value: "" }
Expand Down
19 changes: 18 additions & 1 deletion adminSiteClient/GdocsPreviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import React, {
} from "react"
import { AdminLayout } from "./AdminLayout.js"
import { GdocsMatchProps } from "./GdocsIndexPage.js"
import { GdocPostSettings, GdocInsightSettings } from "./GdocsSettingsForms.js"
import {
GdocPostSettings,
GdocInsightSettings,
GdocHomepageSettings,
} from "./GdocsSettingsForms.js"
import { AdminAppContext } from "./AdminAppContext.js"
import {
checkIsPlainObjectWithGuard,
Expand Down Expand Up @@ -326,6 +330,19 @@ export const GdocsPreviewPage = ({ match, history }: GdocsMatchProps) => {
/>
)
)
.with(
{
content: {
type: OwidGdocType.Homepage,
},
},
(gdoc) => (
<GdocHomepageSettings
gdoc={gdoc}
errors={errors}
/>
)
)
.with(P.any, () => (
<div>
Unknown gdoc type. Add a <strong>type</strong>{" "}
Expand Down
6 changes: 5 additions & 1 deletion adminSiteClient/GdocsSettingsContentField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
OwidGdocDataInsightInterface,
OwidGdocErrorMessageProperty,
get,
OwidGdocHomepageInterface,
} from "@ourworldindata/utils"
import { GdocsEditLink } from "./GdocsEditLink.js"
import { GdocsErrorHelp } from "./GdocsErrorHelp.js"
Expand All @@ -21,7 +22,10 @@ export const GdocsSettingsContentField = ({
errors,
description,
}: {
gdoc: OwidGdocPostInterface | OwidGdocDataInsightInterface
gdoc:
| OwidGdocPostInterface
| OwidGdocDataInsightInterface
| OwidGdocHomepageInterface
property: OwidGdocErrorMessageProperty
render?: (props: {
name: string
Expand Down
21 changes: 21 additions & 0 deletions adminSiteClient/GdocsSettingsForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
OwidGdocErrorMessage,
OwidGdocDataInsightInterface,
OwidGdoc,
OwidGdocHomepageInterface,
} from "@ourworldindata/utils"
import { EXCERPT_MAX_LENGTH } from "./gdocsValidation.js"
import { GdocsSlug } from "./GdocsSlug.js"
Expand Down Expand Up @@ -202,3 +203,23 @@ export const GdocInsightSettings = ({
</form>
)
}

export const GdocHomepageSettings = ({
gdoc,
errors,
}: {
gdoc: OwidGdocHomepageInterface
errors?: OwidGdocErrorMessage[]
}) => {
if (!gdoc || !errors) return null
return (
<div className="GdocsSettingsForm">
<GdocCommonErrors errors={errors} errorsToFilter={[]} />
<div className="form-group">
<h3 className="form-section-heading">Homepage settings</h3>
<p>The homepage has no custom authors, slug, title, etc.</p>
<p>Just hit publish when you'd like to update the page!</p>
</div>
</div>
)
}
11 changes: 11 additions & 0 deletions adminSiteClient/gdocsDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
OwidGdocPostContent,
OwidGdocDataInsightContent,
OwidGdocType,
OwidGdocHomepageContent,
} from "@ourworldindata/types"
import { GDOC_DIFF_OMITTABLE_PROPERTIES } from "./GdocsDiff.js"
import { GDOCS_DETAILS_ON_DEMAND_ID } from "../settings/clientSettings.js"
Expand Down Expand Up @@ -105,13 +106,23 @@ export const checkIsLightningUpdate = (
body: false, // requires rebaking the feed
type: false, // shouldn't be changed, but would require rebaking the feed if it was
}
const homepageLightningPropContentConfigMap: Record<
keyof OwidGdocHomepageContent,
boolean
> = {
body: true,
title: false, // shouldn't be changed, but won't be used in the baked page anyway
authors: false, // shouldn't be set, but defaults to "Our World in Data" because it's assumed to exist in the DB
type: false, // should never be changed
}

const contentPropsMap: Record<OwidGdocType, Record<string, boolean>> = {
[OwidGdocType.Article]: postlightningPropContentConfigMap,
[OwidGdocType.Fragment]: postlightningPropContentConfigMap,
[OwidGdocType.LinearTopicPage]: postlightningPropContentConfigMap,
[OwidGdocType.TopicPage]: postlightningPropContentConfigMap,
[OwidGdocType.DataInsight]: dataInsightLightningPropContentConfigMap,
[OwidGdocType.Homepage]: homepageLightningPropContentConfigMap,
}

const getLightningPropKeys = (configMap: Record<string, boolean>) =>
Expand Down
17 changes: 17 additions & 0 deletions baker/siteRenderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
KEY_INSIGHTS_CLASS_NAME,
} from "../site/blocks/KeyInsights.js"
import { formatUrls, KEY_INSIGHTS_H2_CLASSNAME } from "../site/formatting.js"
import * as db from "../db/db.js"

import { GrapherProgrammaticInterface } from "@ourworldindata/grapher"
import { ExplorerProgram } from "../explorer/ExplorerProgram.js"
Expand Down Expand Up @@ -242,7 +243,23 @@ export const renderPost = async (
)
}

// This function is temporarily forked until we have fully transitioned to a gdocs homepage,
// whereupon we'll strip out the old front page code.
export const renderFrontPage = async () => {
// Annoying, MySQL+TypeORM doesn't support JSONB, so I'm using raw SQL to confirm if there's a published homepage
const gdocHomepageResult = await db.knexRawFirst<{ id: string }>(
`SELECT id FROM posts_gdocs WHERE content->>"$.type" = "${OwidGdocType.Homepage}" AND published = TRUE`,
db.knexInstance()
)

if (gdocHomepageResult) {
const gdocHomepage = await GdocFactory.load(gdocHomepageResult.id)
if (gdocHomepage) {
await gdocHomepage.loadState()
return renderGdoc(gdocHomepage)
}
}

const posts = await getBlogIndex()

const NUM_FEATURED_POSTS = 6
Expand Down
10 changes: 10 additions & 0 deletions db/model/Gdoc/GdocBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,16 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface {
}),
]
})
.with({ type: "pill-row" }, (pillRow) => {
return pillRow.pills.map((pill) =>
Link.createFromUrl({
url: pill.url,
source: this,
componentType: pillRow.type,
text: pill.text,
})
)
})
.with(
{
// no urls directly on any of these blocks
Expand Down
13 changes: 11 additions & 2 deletions db/model/Gdoc/GdocFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { GdocBase, Tag } from "./GdocBase.js"
import { GdocPost } from "./GdocPost.js"
import { GdocDataInsight } from "./GdocDataInsight.js"
import { GdocHomepage } from "./GdocHomepage.js"

// Handles the registration and loading of Gdocs
// Couldn't put this in GdocBase because of circular dependency issues
Expand Down Expand Up @@ -51,6 +52,11 @@ export class GdocFactory {
// TODO: better validation here?
() => GdocDataInsight.create({ ...(json as any) })
)
.with(
OwidGdocType.Homepage,
// TODO: better validation here?
() => GdocHomepage.create({ ...(json as any) })
)
.exhaustive()
}

Expand All @@ -70,7 +76,9 @@ export class GdocFactory {
return gdoc
}

static async loadBySlug(slug: string): Promise<GdocPost | GdocDataInsight> {
static async loadBySlug(
slug: string
): Promise<GdocPost | GdocDataInsight | GdocHomepage> {
const base = await GdocBase.findOne({
where: { slug, published: true },
})
Expand All @@ -87,7 +95,7 @@ export class GdocFactory {
static async load(
id: string,
contentSource?: GdocsContentSource
): Promise<GdocPost | GdocDataInsight> {
): Promise<GdocPost | GdocDataInsight | GdocHomepage> {
const base = await GdocBase.findOne({
where: {
id,
Expand Down Expand Up @@ -121,6 +129,7 @@ export class GdocFactory {
() => GdocPost.create(base)
)
.with(OwidGdocType.DataInsight, () => GdocDataInsight.create(base))
.with(OwidGdocType.Homepage, () => GdocHomepage.create(base))
.exhaustive()

if (contentSource === GdocsContentSource.Gdocs) {
Expand Down
55 changes: 55 additions & 0 deletions db/model/Gdoc/GdocHomepage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Entity, Column } from "typeorm"
import * as db from "../../db"
import {
OwidGdocErrorMessage,
OwidGdocErrorMessageType,
OwidGdocHomepageContent,
OwidGdocHomepageInterface,
OwidGdocMinimalPostInterface,
OwidGdocType,
} from "@ourworldindata/utils"
import { GdocBase } from "./GdocBase.js"

@Entity("posts_gdocs")
export class GdocHomepage
extends GdocBase
implements OwidGdocHomepageInterface
{
@Column({ default: "{}", type: "json" })
content!: OwidGdocHomepageContent

constructor(id?: string) {
super()
if (id) {
this.id = id
}
}

linkedDocuments: Record<string, OwidGdocMinimalPostInterface> = {}
_urlProperties: string[] = []

_validateSubclass = async (): Promise<OwidGdocErrorMessage[]> => {
const errors: OwidGdocErrorMessage[] = []
const otherPublishedHomepages = await db.knexRaw<{ id: string }>(
`
SELECT
id
FROM posts_gdocs
WHERE content->>"$.type" = "${OwidGdocType.Homepage}"
AND published = TRUE
AND id != ?`,
db.knexInstance(),
[this.id]
)
if (otherPublishedHomepages.length > 0) {
errors.push({
property: "published",
message: `There can only be one published homepage. There is a homepage with the ID ${otherPublishedHomepages[0].id} that is already published.`,
type: OwidGdocErrorMessageType.Error,
})
}
return errors
}

_loadSubclassAttachments = async (): Promise<void> => {}

Check warning on line 54 in db/model/Gdoc/GdocHomepage.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected empty async method '_loadSubclassAttachments'
}
7 changes: 7 additions & 0 deletions db/model/Gdoc/enrichedToMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,12 @@ ${links}`
.join("\n")
return `<KeyIndicatorCollection>\n${keyIndicators}\n</KeyIndicatorCollection>`
})
.with({ type: "pill-row" }, (b): string | undefined => {
const title = b.title ? `### ${b.title}` : ""
const pills = b.pills
.map((pill) => `* [${pill.text}](${pill.url})`)
.join("\n")
return [title, pills].join("\n")
})
.exhaustive()
}
10 changes: 10 additions & 0 deletions db/model/Gdoc/enrichedToRaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
RawBlockExplorerTiles,
RawBlockKeyIndicator,
RawBlockKeyIndicatorCollection,
RawBlockPillRow,
} from "@ourworldindata/types"
import { spanToHtmlString } from "./gdocUtils.js"
import { match, P } from "ts-pattern"
Expand Down Expand Up @@ -469,5 +470,14 @@ export function enrichedBlockToRawBlock(
}
}
)
.with({ type: "pill-row" }, (b): RawBlockPillRow => {
return {
type: "pill-row",
value: {
title: b.title,
pills: b.pills,
},
}
})
.exhaustive()
}
11 changes: 10 additions & 1 deletion db/model/Gdoc/exampleEnrichedBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
OwidEnrichedGdocBlock,
Span,
SpanSimpleText,
} from "@ourworldindata/utils"
} from "@ourworldindata/types"

const spanSimpleText: SpanSimpleText = {
spanType: "span-simple-text",
Expand Down Expand Up @@ -575,4 +575,13 @@ export const enrichedBlockExamples: Record<
],
parseErrors: [],
},
"pill-row": {
type: "pill-row",
title: "Recently updated",
pills: [
{ text: "Energy", url: "https://ourworldindata.org/energy" },
{ text: "Poverty", url: "https://ourworldindata.org/poverty" },
],
parseErrors: [],
},
}
18 changes: 18 additions & 0 deletions db/model/Gdoc/rawToArchie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
RawBlockKeyIndicator,
RawBlockKeyIndicatorCollection,
RawBlockExplorerTiles,
RawBlockPillRow,
} from "@ourworldindata/types"
import { isArray } from "@ourworldindata/utils"
import { match } from "ts-pattern"
Expand Down Expand Up @@ -655,6 +656,22 @@ function* rawBlockKeyIndicatorToArchieMLString(
yield "[]"
}
}
}

function* rawBlockPillRowToArchieMLString(
block: RawBlockPillRow
): Generator<string, void, undefined> {
yield "{.pill-row}"
yield* propertyToArchieMLString("title", block.value)
const pills = block?.value?.pills
if (pills) {
yield "[.pills]"
for (const pill of pills) {
yield* propertyToArchieMLString("text", pill)
yield* propertyToArchieMLString("url", pill)
}
yield "[]"
}
yield "{}"
}

Expand Down Expand Up @@ -739,6 +756,7 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator(
{ type: "key-indicator-collection" },
rawBlockKeyIndicatorCollectionToArchieMLString
)
.with({ type: "pill-row" }, rawBlockPillRowToArchieMLString)
.exhaustive()
yield* content
}
Expand Down
Loading

0 comments on commit 65473c6

Please sign in to comment.