Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 Homepage redesign #3219

Merged
merged 46 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f7d13c0
🎉 create homepage gdoc type
ikesau Jan 18, 2024
e43481b
🎉 add pill row component
ikesau Jan 24, 2024
e9cdb18
✨ pill-row overflow-x on entire div, not just the ul
ikesau Feb 7, 2024
1f27abb
🎉 add settings form for homepage
ikesau Feb 15, 2024
99ec19b
🎉 add baking support for the gdocs homepage
ikesau Feb 15, 2024
65473c6
Merge pull request #3120 from owid/homepage-gdoc
ikesau Feb 16, 2024
593867c
🎉 add homepage-search component
ikesau Jan 26, 2024
48f8cea
🎉 add accurate data for homepage-search metadata and mobile styles
ikesau Jan 26, 2024
34d433f
✨ update homepage searchbar heading copy
ikesau Feb 1, 2024
9dbf807
✨ make search tagline links clickable
ikesau Feb 7, 2024
127fb5a
Merge pull request #3142 from owid/homepage-gdoc-search
ikesau Feb 16, 2024
2830138
🎉 add accurate data for homepage-search metadata and mobile styles
ikesau Jan 26, 2024
adf0c83
🎉 homepage-intro archie implementation
ikesau Feb 2, 2024
daa1fec
🎉 homepage intro desktop css
ikesau Feb 6, 2024
2ecb6fd
✨ fix author overrides not working for linked gdocs
ikesau Feb 6, 2024
b5f3552
✨ fix clickable area
ikesau Feb 6, 2024
0296eb6
✨ homepage-intro css touchups, add 'See all our latest work' button
ikesau Feb 7, 2024
e2d5d4c
✨ use knex in GdocHomepage
ikesau Feb 16, 2024
f6c1501
Merge pull request #3183 from owid/homepage-gdoc-featured-posts
ikesau Feb 16, 2024
1ed2832
🎉 add accurate data for homepage-search metadata and mobile styles
ikesau Jan 26, 2024
2211b41
🎉 Add newsletter form to homepage gdoc
ikesau Feb 8, 2024
a8a539f
🎉 add homepage all topics section
ikesau Feb 8, 2024
f3259c4
✨ tidy topics list code
ikesau Feb 8, 2024
08e6583
✨ homepage-intro updates
ikesau Feb 14, 2024
251d6bc
Merge pull request #3184 from owid/homepage-gdoc-static-content
ikesau Feb 16, 2024
e0871d2
✅ fix lint
ikesau Feb 16, 2024
3c90cf8
✨ featured-intro mobile styles
ikesau Feb 17, 2024
ad28f39
✨ improve homepage static content CSS
ikesau Feb 17, 2024
5b4abf1
🎉 body-3-medium on /charts links
ikesau Feb 19, 2024
ca36eee
🎉 add header to key-indicator-collection
ikesau Feb 19, 2024
a56660a
✅ fix homepage archie tests
ikesau Feb 19, 2024
43945d1
🐝 increase bundlewatch for homepage
ikesau Feb 19, 2024
8efc438
🐛 fix pill row title overrides
ikesau Feb 19, 2024
5dc0515
✨ restore some frontpage css
ikesau Feb 19, 2024
4865899
🎉 add latest-data-insights block
ikesau Feb 19, 2024
27f20ee
✨ latest data insights mobile styles
ikesau Feb 19, 2024
20739c2
🐛 fix homepage newsletter anchor link
ikesau Feb 19, 2024
622b391
✨ homepage design review fixes
ikesau Feb 21, 2024
8078282
✨ homepage code review fixes
ikesau Feb 21, 2024
1be6bbf
✨ new Autocomplete featured searches
ikesau Feb 21, 2024
cf8fc5f
✨ hide homepage topics list on mobile
ikesau Feb 21, 2024
cbb078e
Merge remote-tracking branch 'origin/master' into homepage-feature-br…
ikesau Feb 21, 2024
d069b34
✨ tighter margin on common.mjs bundlewatch
ikesau Feb 21, 2024
37b8a3a
✨ standardize homepage topic list styling for all browsers
ikesau Feb 22, 2024
a59782d
✨ revert mistaken grid size change
ikesau Feb 22, 2024
a0ffe43
✨ tighter bundlewatch common.mjs max
ikesau Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"files": [
{
"path": "./dist/assets/common.mjs",
"maxSize": "2MB"
"maxSize": "2.3MB"
ikesau marked this conversation as resolved.
Show resolved Hide resolved
},
{
"path": "./dist/assets/owid.mjs",
Expand Down
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
3 changes: 1 addition & 2 deletions baker/SiteBaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,7 @@ export class SiteBaker {

private async bakeDataInsights() {
if (!this.bakeSteps.has("dataInsights")) return
const latestDataInsights =
await GdocDataInsight.loadLatestDataInsights()
const latestDataInsights = await db.getLatestDataInsights()
const publishedDataInsights =
await GdocDataInsight.getPublishedDataInsights()

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)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modifications to the renderFrontPage function introduce a new approach to rendering the homepage by checking for a published homepage Google Doc before proceeding with the existing logic. This change is significant as it affects the initial content users will see. Consider the following points:

  1. SQL Injection Protection: The SQL query within db.knexRawFirst uses parameterized inputs, which is a good practice for preventing SQL injection vulnerabilities. However, since this is a critical security aspect, double-check that all dynamic inputs to SQL queries throughout the application follow this practice.
  2. Error Handling: Ensure that there is adequate error handling around the database query and subsequent logic. If the database query fails or if there are issues loading the Google Doc, the application should handle these gracefully, possibly falling back to the default rendering logic.
  3. Performance Considerations: Depending on the frequency of homepage visits, this database query could potentially impact performance. Consider caching the result of the homepage check or the rendered homepage content itself to reduce database load.
  4. Maintainability: The comment mentions that this function is temporarily forked and will undergo further changes. Ensure that there is a clear plan and timeline for the transition to the new homepage rendering logic to avoid having temporary fixes become permanent technical debt.

Overall, the approach seems sound, but careful attention should be paid to security, error handling, and performance implications.

Consider implementing caching for the homepage check or rendered content to improve performance. Additionally, ensure robust error handling around the database query and Google Doc loading process.


const posts = await getBlogIndex()

const NUM_FEATURED_POSTS = 6
Expand Down
66 changes: 65 additions & 1 deletion db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
} from "../settings/serverSettings.js"
import { registerExitHandler } from "./cleanup.js"
import { keyBy } from "@ourworldindata/utils"
import { DbChartTagJoin } from "@ourworldindata/types"
import {
DbChartTagJoin,
MinimalDataInsightInterface,
OwidGdocType,
} from "@ourworldindata/types"
let typeormDataSource: DataSource

export const getConnection = async (
Expand Down Expand Up @@ -204,3 +208,63 @@ export const getPublishedExplorersBySlug = async (
return keyBy(processed, "slug")
})
}

export const getLatestDataInsights = (
limit = 5
): Promise<MinimalDataInsightInterface[]> => {
return knexRaw(
`
SELECT
content->>'$.title' AS title,
publishedAt,
ROW_NUMBER() OVER (ORDER BY publishedAt DESC) - 1 AS \`index\`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems a bit heavy handed, given that we can get the index in the map() below (EDIT: I'm seeing now this has been merely moved so not really part of this PR)

FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()
ORDER BY publishedAt DESC
LIMIT ?
`,
knexInstance(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the recent refactor in #3194, you're now able to get the knex instance from the baker directly

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure I understand you - you mean all these db functions should take knex as an argument and we pass the same instance around everywhere? 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep 😉

[limit]
).then((results) =>
results.map((record: any) => ({
...record,
index: Number(record.index),
}))
) as Promise<MinimalDataInsightInterface[]>
}
Comment on lines +212 to +236
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getLatestDataInsights function retrieves the latest data insights with a limit parameter. It's well-structured and uses parameterized queries to prevent SQL injection. However, consider adding error handling to manage potential database query failures gracefully.

export const getLatestDataInsights = (
    limit = 5
): Promise<MinimalDataInsightInterface[]> => {
    return knexRaw(
        `
        SELECT
            content->>'$.title' AS title,
            publishedAt,
            ROW_NUMBER() OVER (ORDER BY publishedAt DESC) - 1 AS \`index\`
        FROM posts_gdocs
        WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
            AND published = TRUE
            AND publishedAt < NOW()
        ORDER BY publishedAt DESC
        LIMIT ?
        `,
        knexInstance(),
        [limit]
    ).then((results) =>
        results.map((record: any) => ({
            ...record,
            index: Number(record.index),
        }))
    )
+   .catch((error) => {
+       console.error("Failed to get latest data insights:", error);
+       throw error; // Rethrow or handle as appropriate for your application
+   });
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const getLatestDataInsights = (
limit = 5
): Promise<MinimalDataInsightInterface[]> => {
return knexRaw(
`
SELECT
content->>'$.title' AS title,
publishedAt,
ROW_NUMBER() OVER (ORDER BY publishedAt DESC) - 1 AS \`index\`
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()
ORDER BY publishedAt DESC
LIMIT ?
`,
knexInstance(),
[limit]
).then((results) =>
results.map((record: any) => ({
...record,
index: Number(record.index),
}))
) as Promise<MinimalDataInsightInterface[]>
}
export const getLatestDataInsights = (
limit = 5
): Promise<MinimalDataInsightInterface[]> => {
return knexRaw(
`
SELECT
content->>'$.title' AS title,
publishedAt,
ROW_NUMBER() OVER (ORDER BY publishedAt DESC) - 1 AS \`index\`
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()
ORDER BY publishedAt DESC
LIMIT ?
`,
knexInstance(),
[limit]
).then((results) =>
results.map((record: any) => ({
...record,
index: Number(record.index),
}))
)
.catch((error) => {
console.error("Failed to get latest data insights:", error);
throw error; // Rethrow or handle as appropriate for your application
});
}


export const getPublishedDataInsightCount = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`SELECT COUNT(*) AS count
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()`,
knexInstance()
).then((res) => res?.count ?? 0)
}
Comment on lines +238 to +247
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getPublishedDataInsightCount function correctly counts the number of published data insights. It uses parameterized queries, which is good for security. Similar to the previous function, consider adding error handling for robustness.

export const getPublishedDataInsightCount = (): Promise<number> => {
    return knexRawFirst<{ count: number }>(
        `SELECT COUNT(*) AS count
        FROM posts_gdocs
        WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
            AND published = TRUE
            AND publishedAt < NOW()`,
        knexInstance()
    ).then((res) => res?.count ?? 0)
+   .catch((error) => {
+       console.error("Failed to get published data insight count:", error);
+       throw error; // Rethrow or handle as appropriate for your application
+   });
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const getPublishedDataInsightCount = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`SELECT COUNT(*) AS count
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()`,
knexInstance()
).then((res) => res?.count ?? 0)
}
export const getPublishedDataInsightCount = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`SELECT COUNT(*) AS count
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()`,
knexInstance()
).then((res) => res?.count ?? 0)
.catch((error) => {
console.error("Failed to get published data insight count:", error);
throw error; // Rethrow or handle as appropriate for your application
});
}


export const getTotalNumberOfCharts = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`
SELECT COUNT(*) AS count
FROM charts
WHERE publishedAt IS NOT NULL`,
knexInstance()
).then((res) => res?.count ?? 0)
}
Comment on lines +249 to +257
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getTotalNumberOfCharts function is straightforward and correctly counts the total number of published charts. It also uses parameterized queries for security. Again, adding error handling would improve the robustness of this function.

export const getTotalNumberOfCharts = (): Promise<number> => {
    return knexRawFirst<{ count: number }>(
        `SELECT COUNT(*) AS count
        FROM charts
        WHERE publishedAt IS NOT NULL`,
        knexInstance()
    ).then((res) => res?.count ?? 0)
+   .catch((error) => {
+       console.error("Failed to get total number of charts:", error);
+       throw error; // Rethrow or handle as appropriate for your application
+   });
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const getTotalNumberOfCharts = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`
SELECT COUNT(*) AS count
FROM charts
WHERE publishedAt IS NOT NULL`,
knexInstance()
).then((res) => res?.count ?? 0)
}
export const getTotalNumberOfCharts = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`
SELECT COUNT(*) AS count
FROM charts
WHERE publishedAt IS NOT NULL`,
knexInstance()
).then((res) => res?.count ?? 0)
.catch((error) => {
console.error("Failed to get total number of charts:", error);
throw error; // Rethrow or handle as appropriate for your application
});
}


export const getTotalNumberOfTopics = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`
SELECT COUNT(DISTINCT(tagId)) AS count
FROM chart_tags
WHERE chartId IN (
SELECT id
FROM charts
WHERE publishedAt IS NOT NULL)`,
knexInstance()
).then((res) => res?.count ?? 0)
Comment on lines +259 to +269
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getTotalNumberOfTopics function correctly counts the number of unique topics by counting distinct tagId values from chart_tags where the chart is published. It follows best practices for query parameterization. Similar to other functions, adding error handling would enhance its reliability.

export const getTotalNumberOfTopics = (): Promise<number> => {
    return knexRawFirst<{ count: number }>(
        `SELECT COUNT(DISTINCT(tagId)) AS count
        FROM chart_tags
        WHERE chartId IN (
        SELECT id
        FROM charts
        WHERE publishedAt IS NOT NULL)`,
        knexInstance()
    ).then((res) => res?.count ?? 0)
+   .catch((error) => {
+       console.error("Failed to get total number of topics:", error);
+       throw error; // Rethrow or handle as appropriate for your application
+   });
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const getTotalNumberOfTopics = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`
SELECT COUNT(DISTINCT(tagId)) AS count
FROM chart_tags
WHERE chartId IN (
SELECT id
FROM charts
WHERE publishedAt IS NOT NULL)`,
knexInstance()
).then((res) => res?.count ?? 0)
export const getTotalNumberOfTopics = (): Promise<number> => {
return knexRawFirst<{ count: number }>(
`SELECT COUNT(DISTINCT(tagId)) AS count
FROM chart_tags
WHERE chartId IN (
SELECT id
FROM charts
WHERE publishedAt IS NOT NULL)`,
knexInstance()
).then((res) => res?.count ?? 0)
.catch((error) => {
console.error("Failed to get total number of topics:", error);
throw error; // Rethrow or handle as appropriate for your application
});
}

}
34 changes: 33 additions & 1 deletion db/model/Gdoc/GdocBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface {
}
})
}
if (item.type === "homepage-intro") {
item.featuredWork.forEach((featuredWork) => {
if (featuredWork.filename) {
filenames.add(featuredWork.filename)
}
})
}
}
return item
})
Expand Down Expand Up @@ -528,6 +535,29 @@ 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({ type: "homepage-intro" }, (homepageIntro) => {
return homepageIntro.featuredWork.map((featuredWork) =>
Link.createFromUrl({
url: featuredWork.url,
source: this,
componentType: homepageIntro.type,
text:
featuredWork.title ||
featuredWork.description ||
"",
})
)
})
.with(
{
// no urls directly on any of these blocks
Expand Down Expand Up @@ -557,7 +587,9 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface {
"sticky-left",
"sticky-right",
"table",
"text"
"text",
"homepage-search",
"latest-data-insights"
),
},
() => []
Expand Down
41 changes: 3 additions & 38 deletions db/model/Gdoc/GdocDataInsight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
OwidGdocMinimalPostInterface,
} from "@ourworldindata/utils"
import { GdocBase } from "./GdocBase.js"
import { getConnection } from "../../../db/db.js"
import * as db from "../../../db/db.js"

@Entity("posts_gdocs")
export class GdocDataInsight
Expand Down Expand Up @@ -45,30 +45,7 @@ export class GdocDataInsight
}

_loadSubclassAttachments = async (): Promise<void> => {
this.latestDataInsights = await GdocDataInsight.loadLatestDataInsights()
}

static async loadLatestDataInsights(): Promise<
MinimalDataInsightInterface[]
> {
const c = await getConnection()
return (
await c.query(`
SELECT
content->>'$.title' AS title,
publishedAt,
ROW_NUMBER() OVER (ORDER BY publishedAt DESC) - 1 AS \`index\`
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()
ORDER BY publishedAt DESC
LIMIT 5
`)
).map((record: any) => ({
...record,
index: Number(record.index),
})) as MinimalDataInsightInterface[]
this.latestDataInsights = await db.getLatestDataInsights()
}

static async getPublishedDataInsights(
Expand All @@ -93,24 +70,12 @@ export class GdocDataInsight
})
}

static async getPublishedDataInsightCount(): Promise<number> {
const c = await getConnection()
const query = `
SELECT COUNT(*) AS count
FROM posts_gdocs
WHERE content->>'$.type' = '${OwidGdocType.DataInsight}'
AND published = TRUE
AND publishedAt < NOW()
`
return (await c.query(query))[0].count
}

/**
* Returns the number of pages that will exist on the data insights index page
* based on the number of published data insights and DATA_INSIGHTS_INDEX_PAGE_SIZE
*/
static async getTotalPageCount(): Promise<number> {
const count = await GdocDataInsight.getPublishedDataInsightCount()
const count = await db.getPublishedDataInsightCount()
return Math.ceil(count / DATA_INSIGHTS_INDEX_PAGE_SIZE)
}
}
Loading
Loading