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

New data insight feed and detail page #3801

Merged
merged 11 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
4 changes: 1 addition & 3 deletions adminSiteServer/mockSiteRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,7 @@ getPlainRouteNonIdempotentWithRWTransaction(
"/data-insights/:pageNumberOrSlug?",
async (req, res, trx) => {
const totalPageCount = calculateDataInsightIndexPageCount(
await db
.getPublishedDataInsights(trx)
.then((insights) => insights.length)
await db.getPublishedDataInsightCount(trx)
)
async function renderIndexPage(pageNumber: number) {
const dataInsights = await GdocDataInsight.getPublishedDataInsights(
Expand Down
10 changes: 8 additions & 2 deletions baker/SiteBaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import {
import {
gdocFromJSON,
getAllMinimalGdocBaseObjects,
getAndLoadPublishedDataInsights,
} from "../db/model/Gdoc/GdocFactory.js"
import { getBakePath } from "@ourworldindata/components"
import { GdocAuthor, getMinimalAuthors } from "../db/model/Gdoc/GdocAuthor.js"
Expand Down Expand Up @@ -788,7 +789,9 @@ export class SiteBaker {
// TODO: this transaction is only RW because somewhere inside it we fetch images
private async bakeDataInsights(knex: db.KnexReadWriteTransaction) {
if (!this.bakeSteps.has("dataInsights")) return
const latestDataInsights = await db.getPublishedDataInsights(knex, 5)
const latestDataInsights = await getAndLoadPublishedDataInsights(knex, {
limit: 7,
})
const publishedDataInsights =
await GdocDataInsight.getPublishedDataInsights(knex)

Expand All @@ -803,7 +806,10 @@ export class SiteBaker {
// Not used just yet
// dataInsight.linkedAuthors = attachments.linkedAuthors
dataInsight.linkedDocuments = attachments.linkedDocuments
dataInsight.imageMetadata = attachments.imageMetadata
dataInsight.imageMetadata = Object.assign(
attachments.imageMetadata,
...latestDataInsights.map((insight) => insight.imageMetadata)
)
dataInsight.linkedCharts = {
...attachments.linkedCharts.graphers,
...attachments.linkedCharts.explorers,
Expand Down
4 changes: 2 additions & 2 deletions baker/siteRenderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import { logErrorAndMaybeSendToBugsnag } from "../serverUtils/errorLog.js"
import {
getAndLoadGdocBySlug,
getAndLoadGdocById,
getAndLoadPublishedDataInsights,
getAndLoadPublishedDataInsightsPage,
} from "../db/model/Gdoc/GdocFactory.js"
import { transformExplorerProgramToResolveCatalogPaths } from "./ExplorerBaker.js"
import { AttachmentsContext } from "../site/gdocs/OwidGdoc.js"
Expand Down Expand Up @@ -366,7 +366,7 @@ export async function makeAtomFeed(knex: KnexReadWriteTransaction) {
}

export async function makeDataInsightsAtomFeed(knex: KnexReadWriteTransaction) {
const dataInsights = await getAndLoadPublishedDataInsights(knex, 0)
const dataInsights = await getAndLoadPublishedDataInsightsPage(knex, 0)
return makeAtomFeedFromDataInsights({
dataInsights,
htmlUrl: `${BAKED_BASE_URL}/data-insights`,
Expand Down
4 changes: 2 additions & 2 deletions db/model/Gdoc/GdocBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
OwidGdocBaseInterface,
OwidGdocPublicationContext,
BreadcrumbItem,
MinimalDataInsightInterface,
OwidGdocMinimalPostInterface,
urlToSlug,
grabMetadataForGdocLinkedIndicator,
Expand Down Expand Up @@ -58,6 +57,7 @@ import {
DbEnrichedAuthor,
} from "@ourworldindata/types"
import { KnexReadonlyTransaction } from "../../db"
import { GdocDataInsight } from "./GdocDataInsight.js"

export class GdocBase implements OwidGdocBaseInterface {
id!: string
Expand All @@ -79,7 +79,7 @@ export class GdocBase implements OwidGdocBaseInterface {
linkedCharts: Record<string, LinkedChart> = {}
linkedIndicators: Record<number, LinkedIndicator> = {}
linkedDocuments: Record<string, OwidGdocMinimalPostInterface> = {}
latestDataInsights: MinimalDataInsightInterface[] = []
latestDataInsights: GdocDataInsight[] = []
_omittableFields: string[] = []

constructor(id?: string) {
Expand Down
41 changes: 32 additions & 9 deletions db/model/Gdoc/GdocDataInsight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,43 @@ import {
OwidGdocErrorMessageType,
OwidGdocDataInsightContent,
OwidGdocDataInsightInterface,
MinimalDataInsightInterface,
OwidGdocMinimalPostInterface,
OwidGdocBaseInterface,
excludeNullish,
} from "@ourworldindata/utils"
import { GdocBase } from "./GdocBase.js"
import * as db from "../../../db/db.js"
import { getAndLoadPublishedDataInsights } from "./GdocFactory.js"
import {
getAndLoadPublishedDataInsights,
getAndLoadPublishedDataInsightsPage,
} from "./GdocFactory.js"

export class GdocDataInsight
extends GdocBase
implements OwidGdocDataInsightInterface
{
content!: OwidGdocDataInsightContent
private shouldLoadLatestDataInsights: boolean

constructor(id?: string) {
constructor(id?: string, shouldLoadLatestDataInsights: boolean = false) {
super(id)
this.shouldLoadLatestDataInsights = shouldLoadLatestDataInsights
}

static create(obj: OwidGdocBaseInterface): GdocDataInsight {
const gdoc = new GdocDataInsight()
static create(
obj: OwidGdocBaseInterface,
shouldLoadLatestDataInsights: boolean = false
): GdocDataInsight {
const gdoc = new GdocDataInsight(
undefined,
shouldLoadLatestDataInsights
)
Object.assign(gdoc, obj)
return gdoc
}

linkedDocuments: Record<string, OwidGdocMinimalPostInterface> = {}
latestDataInsights: MinimalDataInsightInterface[] = []
latestDataInsights: GdocDataInsight[] = []
// TODO: support query parameters in grapher urls so we can track country selections

protected typeSpecificUrls(): string[] {
Expand All @@ -49,17 +59,30 @@ export class GdocDataInsight
}

_loadSubclassAttachments = async (
knex: db.KnexReadonlyTransaction
knex: db.KnexReadWriteTransaction
): Promise<void> => {
// TODO: refactor these classes to properly use knex - not going to start it now
this.latestDataInsights = await db.getPublishedDataInsights(knex, 5)
if (this.shouldLoadLatestDataInsights) {
this.latestDataInsights = await getAndLoadPublishedDataInsights(
knex,
{
limit: 7,
}
)
this.imageMetadata = Object.assign(
this.imageMetadata,
...this.latestDataInsights.map(
(insight) => insight.imageMetadata
)
)
}
}

// TODO: this transaction is only RW because somewhere inside it we fetch images
static async getPublishedDataInsights(
knex: db.KnexReadWriteTransaction,
page?: number
): Promise<GdocDataInsight[]> {
return getAndLoadPublishedDataInsights(knex, page)
return getAndLoadPublishedDataInsightsPage(knex, page)
}
}
51 changes: 29 additions & 22 deletions db/model/Gdoc/GdocFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export async function getAndLoadGdocBySlug(
`No published Google Doc with slug "${slug}" found in the database`
)
}
return loadGdocFromGdocBase(knex, base)
return loadGdocFromGdocBase(knex, base, undefined, true)
}

// TODO: this transaction is only RW because somewhere inside it we fetch images
Expand Down Expand Up @@ -309,7 +309,8 @@ export async function createOrLoadGdocById(
export async function loadGdocFromGdocBase(
knex: KnexReadWriteTransaction,
base: OwidGdocBaseInterface,
contentSource?: GdocsContentSource
contentSource?: GdocsContentSource,
shouldLoadLatestDataInsights?: boolean
): Promise<GdocPost | GdocDataInsight | GdocHomepage | GdocAuthor> {
const type = get(base, "content.type") as unknown
if (!type)
Expand All @@ -333,7 +334,9 @@ export async function loadGdocFromGdocBase(
),
() => GdocPost.create(base)
)
.with(OwidGdocType.DataInsight, () => GdocDataInsight.create(base))
.with(OwidGdocType.DataInsight, () =>
GdocDataInsight.create(base, shouldLoadLatestDataInsights)
)
.with(OwidGdocType.Homepage, () => GdocHomepage.create(base))
.with(OwidGdocType.Author, () => GdocAuthor.create(base))
.exhaustive()
Expand All @@ -354,26 +357,16 @@ export async function loadGdocFromGdocBase(
// TODO: this transaction is only RW because somewhere inside it we fetch images
export async function getAndLoadPublishedDataInsights(
knex: KnexReadWriteTransaction,
page?: number
options?: { limit: number; offset?: number }
): Promise<GdocDataInsight[]> {
const limitOffsetClause =
page !== undefined
? `LIMIT ${DATA_INSIGHTS_INDEX_PAGE_SIZE} OFFSET ${
page * DATA_INSIGHTS_INDEX_PAGE_SIZE
}`
: ""
const rows = await knexRaw<DbRawPostGdoc>(
knex,
`-- sql
SELECT *
FROM posts_gdocs
WHERE published = 1
AND type = ?
AND publishedAt <= NOW()
ORDER BY publishedAt DESC
${limitOffsetClause}`,
[OwidGdocType.DataInsight]
)
let query = knex<DbRawPostGdoc>(PostsGdocsTableName)
.where("type", OwidGdocType.DataInsight)
.where("published", 1)
.where("publishedAt", "<=", knex.fn.now())
.orderBy("publishedAt", "desc")
if (options?.limit) query = query.limit(options.limit)
if (options?.offset) query = query.offset(options.offset)
const rows = await query.select("*")
const ids = rows.map((row) => row.id)
const tags = await knexRaw<DbPlainTag>(
knex,
Expand All @@ -397,6 +390,20 @@ export async function getAndLoadPublishedDataInsights(
return gdocs as GdocDataInsight[]
}

export async function getAndLoadPublishedDataInsightsPage(
knex: KnexReadWriteTransaction,
page?: number
): Promise<GdocDataInsight[]> {
const options =
page === undefined
? undefined
: {
limit: DATA_INSIGHTS_INDEX_PAGE_SIZE,
offset: page * DATA_INSIGHTS_INDEX_PAGE_SIZE,
}
return await getAndLoadPublishedDataInsights(knex, options)
}

// TODO: this transaction is only RW because somewhere inside it we fetch images
export async function getAndLoadPublishedGdocPosts(
knex: KnexReadWriteTransaction
Expand Down
12 changes: 10 additions & 2 deletions db/model/Gdoc/GdocHomepage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
OwidGdocHomepageMetadata,
} from "@ourworldindata/types"
import { getUniqueTopicCount } from "../../../site/SiteNavigation.js"
import { getAndLoadPublishedDataInsights } from "./GdocFactory.js"

export class GdocHomepage
extends GdocBase
implements OwidGdocHomepageInterface
Expand Down Expand Up @@ -58,7 +60,7 @@ export class GdocHomepage
}

_loadSubclassAttachments = async (
knex: db.KnexReadonlyTransaction
knex: db.KnexReadWriteTransaction
): Promise<void> => {
const [grapherCount, nonGrapherExplorerViewCount] = await Promise.all([
db.getTotalNumberOfCharts(knex),
Expand All @@ -70,6 +72,12 @@ export class GdocHomepage
topicCount: getUniqueTopicCount(),
}

this.latestDataInsights = await db.getPublishedDataInsights(knex, 4)
this.latestDataInsights = await getAndLoadPublishedDataInsights(knex, {
limit: 7,
})
this.imageMetadata = Object.assign(
this.imageMetadata,
...this.latestDataInsights.map((insight) => insight.imageMetadata)
)
}
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
"dayjs": "^1.11.11",
"decko": "^1.2.0",
"dotenv": "^16.0.1",
"embla-carousel-class-names": "^8.1.7",
"embla-carousel-react": "^8.1.7",
"entities": "^4.4.0",
"enzyme": "^3.11.0",
"expr-eval": "^2.0.2",
Expand Down Expand Up @@ -136,6 +138,7 @@
"react-move": "^6.5.0",
"react-recaptcha": "^2.3.10",
"react-remark": "^2.1.0",
"react-responsive": "^10.0.0",
"react-router-dom": "^5.3.1",
"react-select": "^5.8.0",
"react-tag-autocomplete": "^7.1.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/@ourworldindata/components/src/styles/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,16 @@

@mixin topic-chip {
a {
display: flex;
align-items: center;
background-color: $blue-10;
border-radius: 50px;
padding: 8.5px 16px;
padding: 8px 16px;
white-space: nowrap;
@include body-3-medium;
color: $blue-90;
transition: background-color 150ms;
height: 32px;

&:hover {
background-color: $blue-20;
Expand Down
23 changes: 23 additions & 0 deletions packages/@ourworldindata/components/src/styles/typography.scss
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,17 @@ body {
@include subtitle-2;
}

@mixin subtitle-2-bold {
font-family: $sans-serif-font-stack;
font-size: 1.25rem;
line-height: 1.2;
font-weight: 700;
}

.subtitle-2-bold {
@include subtitle-2-bold;
}

@mixin overline-black-caps {
font-family: $sans-serif-font-stack;
font-size: 0.75rem;
Expand Down Expand Up @@ -344,6 +355,18 @@ body {
@include body-2-semibold;
}

@mixin body-3-regular {
font-family: $sans-serif-font-stack;
font-size: 0.875rem;
line-height: 1.5;
font-weight: 400;
letter-spacing: 0.01rem;
}

.body-3-regular {
@include body-3-regular;
}

@mixin body-3-medium {
font-family: $sans-serif-font-stack;
font-size: 0.875rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ $grapher-thumbnail-height: 600px;
* Breakpoints
*/

// Calculations in Image.tsx and image.ts rely on these variables. Please change them there if you change them here.
// Calculations in LatestDataInsights.tsx, Image.tsx and image.ts rely on these
// variables. Please change them there if you change them here.
$sm: 768px;
$md: 960px;
$lg: 1280px;
Expand Down
2 changes: 1 addition & 1 deletion packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const DATA_INSIGHTS_INDEX_PAGE_SIZE = 20

export interface OwidGdocDataInsightInterface extends OwidGdocBaseInterface {
content: OwidGdocDataInsightContent
latestDataInsights?: MinimalDataInsightInterface[]
latestDataInsights?: OwidGdocDataInsightInterface[]
}

export type MinimalDataInsightInterface = Pick<
Expand Down
Loading