diff --git a/baker/siteRenderers.tsx b/baker/siteRenderers.tsx index b488ed5f3f7..9e1b2a1610d 100644 --- a/baker/siteRenderers.tsx +++ b/baker/siteRenderers.tsx @@ -62,7 +62,6 @@ import { CountryProfileSpec } from "../site/countryProfileProjects.js" import { formatPost } from "./formatWordpressPost.js" import { getBlogIndex, - getEntriesByCategory, getLatestPostRevision, getPostBySlug, isPostCitable, @@ -92,6 +91,7 @@ import { postsTable } from "../db/model/Post.js" import { GdocPost } from "../db/model/Gdoc/GdocPost.js" import { logErrorAndMaybeSendToBugsnag } from "../serverUtils/errorLog.js" import { GdocFactory } from "../db/model/Gdoc/GdocFactory.js" +import { SiteNavigationStatic } from "../site/SiteNavigation.js" export const renderToHtmlPage = (element: any) => `${ReactDOMServer.renderToStaticMarkup(element)}` @@ -204,8 +204,7 @@ export const renderPreview = async (postId: number): Promise => { } export const renderMenuJson = async () => { - const categories = await getEntriesByCategory() - return JSON.stringify({ categories: categories }) + return JSON.stringify(SiteNavigationStatic) } export const renderPost = async ( diff --git a/db/wpdb.ts b/db/wpdb.ts index c5f99787aa7..64e7682e421 100644 --- a/db/wpdb.ts +++ b/db/wpdb.ts @@ -19,13 +19,11 @@ import { registerExitHandler } from "./cleanup.js" import { RelatedChart, CategoryWithEntries, - EntryNode, FullPost, WP_PostType, DocumentNode, PostReference, JsonError, - CategoryNode, FilterFnPostRestApi, PostRestApi, TopicId, @@ -49,6 +47,7 @@ import { import { TOPICS_CONTENT_GRAPH } from "../settings/clientSettings.js" import { GdocPost } from "./model/Gdoc/GdocPost.js" import { Link } from "./model/Link.js" +import { SiteNavigationStatic } from "../site/SiteNavigation.js" let _knexInstance: Knex @@ -307,133 +306,19 @@ export const getDocumentsInfo = async ( } } -const getEntryNode = ({ - slug, - title, - excerpt, - kpi, -}: EntryNode): { - slug: string - title: string - excerpt: string - kpi: string -} => ({ - slug, - title: decodeHTML(title), - excerpt: excerpt === null ? "" : decodeHTML(excerpt), - kpi, -}) - -const isEntryInSubcategories = (entry: EntryNode, subcategories: any): any => { - return subcategories.some((subcategory: any) => { - return subcategory.pages.nodes.some( - (node: EntryNode) => entry.slug === node.slug - ) - }) -} - -// Retrieve a list of categories and their associated entries -let cachedEntries: CategoryWithEntries[] = [] -export const getEntriesByCategory = async (): Promise< - CategoryWithEntries[] -> => { - if (!isWordpressAPIEnabled) return [] - if (cachedEntries.length) return cachedEntries - - const first = 100 - // The filtering of cached entries below makes the $first argument - // less accurate, as it does not represent the exact number of entries - // returned per subcategories but rather their maximum number of entries. - const orderby = "TERM_ORDER" - - // hack: using the description field ("01", "02", etc.) to order the top - // categories as TERM_ORDER doesn't seem to work as expected anymore. - const query = ` - query getEntriesByCategory($first: Int, $orderby: TermObjectsConnectionOrderbyEnum!) { - categories(first: $first, where: {termTaxonomId: ${ENTRIES_CATEGORY_ID}, orderby: $orderby}) { - nodes { - name - children(first: $first, where: {orderby: DESCRIPTION}) { - nodes { - ...categoryWithEntries - children(first: $first, where: {orderby: $orderby}) { - nodes { - ...categoryWithEntries - } - } - } - } - } - } - } - - fragment categoryWithEntries on Category { - name - slug - pages(first: $first, where: {orderby: {field: MENU_ORDER, order: ASC}}) { - nodes { - slug - title - excerpt - kpi - } - } - } - ` - const categories = await graphqlQuery(query, { first, orderby }) - - cachedEntries = categories.data.categories.nodes[0].children.nodes.map( - ({ name, slug, pages, children }: CategoryNode) => ({ - name: decodeHTML(name), - slug, - entries: pages.nodes - .filter( - (node: EntryNode) => - /* As entries are sometimes listed at all levels of the category hierarchy - (e.g. "Entries" > "Demographic Change" > "Life and Death" for "Child and - Infant Mortality"), it is necessary to filter out duplicates, by giving precedent to - the deepest level. In other words, if an entry is present in category 1 and category - 1.1, it will only show in category 1.1. - - N.B. Pre wp-graphql 0.6.0, entries would be returned at all levels of the category - hierarchy, no matter what categories were effectively selected. 0.6.0 fixes that - (cf. https://github.com/wp-graphql/wp-graphql/issues/1100). Even though this behaviour - has been fixed, we still have potential duplicates, from the multiple hierarchical - selection as noted above. The only difference is the nature of the duplicate, which can - now be considered more intentional as it is coming from the data / CMS. - Ultimately, this discrepency in the data should be addressed to make the system - less permissive. */ - !isEntryInSubcategories(node, children.nodes) - ) - .map((node: EntryNode) => getEntryNode(node)), - subcategories: children.nodes - .filter( - (subcategory: CategoryNode) => - subcategory.pages.nodes.length !== 0 - ) - .map(({ name, slug, pages }: CategoryNode) => ({ - name: decodeHTML(name), - slug, - entries: pages.nodes.map((node: EntryNode) => - getEntryNode(node) - ), - })), - }) - ) - - return cachedEntries -} - export const isPostCitable = async (post: FullPost): Promise => { - const entries = await getEntriesByCategory() + const entries = SiteNavigationStatic.categories return entries.some((category) => { return ( category.entries.some((entry) => entry.slug === post.slug) || - category.subcategories.some((subcategory: CategoryWithEntries) => { - return subcategory.entries.some( - (subCategoryEntry) => subCategoryEntry.slug === post.slug - ) - }) + (category.subcategories ?? []).some( + (subcategory: CategoryWithEntries) => { + return subcategory.entries.some( + (subCategoryEntry) => + subCategoryEntry.slug === post.slug + ) + } + ) ) }) } @@ -1005,7 +890,6 @@ export const getTables = async (): Promise> => { export const flushCache = (): void => { cachedAuthorship = undefined - cachedEntries = [] cachedFeaturedImages = undefined getBlogIndex.cache.clear?.() cachedTables = undefined diff --git a/packages/@ourworldindata/types/src/domainTypes/ContentGraph.ts b/packages/@ourworldindata/types/src/domainTypes/ContentGraph.ts index 4f1763c371f..b0ed950b1b0 100644 --- a/packages/@ourworldindata/types/src/domainTypes/ContentGraph.ts +++ b/packages/@ourworldindata/types/src/domainTypes/ContentGraph.ts @@ -3,16 +3,15 @@ import { TopicId } from "./Various.js" export interface EntryMeta { slug: string title: string - excerpt: string - kpi: string } export interface CategoryWithEntries { name: string slug: string entries: EntryMeta[] - subcategories: CategoryWithEntries[] + subcategories?: CategoryWithEntries[] } + export enum GraphDocumentType { Topic = "topic", Article = "article", diff --git a/site/SiteNavigation.tsx b/site/SiteNavigation.tsx index 0f799f90694..c6f60e8f3cf 100644 --- a/site/SiteNavigation.tsx +++ b/site/SiteNavigation.tsx @@ -247,3 +247,848 @@ export const runSiteNavigation = ( document.querySelector(".site-navigation-root") ) } + +export const SiteNavigationStatic: { categories: CategoryWithEntries[] } = { + categories: [ + { + name: "Population and Demographic Change", + slug: "population", + entries: [], + subcategories: [ + { + name: "Population Change", + slug: "population-change", + entries: [ + { + slug: "population-growth", + title: "Population Growth", + }, + { + slug: "age-structure", + title: "Age Structure", + }, + { + slug: "gender-ratio", + title: "Gender Ratio", + }, + ], + }, + { + name: "Life and Death", + slug: "life-and-death", + entries: [ + { + slug: "life-expectancy", + title: "Life Expectancy", + }, + { + slug: "child-mortality", + title: "Child and Infant Mortality", + }, + { + slug: "fertility-rate", + title: "Fertility Rate", + }, + ], + }, + { + name: "Distribution of the World Population", + slug: "distribution-of-the-world-population", + entries: [ + { + slug: "urbanization", + title: "Urbanization", + }, + { + slug: "migration", + title: "Migration", + }, + ], + }, + ], + }, + { + name: "Health", + slug: "health", + entries: [ + { + slug: "tuberculosis", + title: "Tuberculosis", + }, + { + slug: "pandemics", + title: "Pandemics", + }, + { + slug: "cardiovascular-diseases", + title: "Cardiovascular Diseases", + }, + ], + subcategories: [ + { + name: "Health Risks", + slug: "health-risks", + entries: [ + { + slug: "lead-pollution", + title: "Lead Pollution", + }, + { + slug: "air-pollution", + title: "Air Pollution", + }, + { + slug: "outdoor-air-pollution", + title: "Outdoor Air Pollution", + }, + { + slug: "indoor-air-pollution", + title: "Indoor Air Pollution", + }, + { + slug: "obesity", + title: "Obesity", + }, + { + slug: "smoking", + title: "Smoking", + }, + { + slug: "alcohol-consumption", + title: "Alcohol Consumption", + }, + { + slug: "illicit-drug-use", + title: "Opioids, Cocaine, Cannabis, and Other Illicit Drugs", + }, + ], + }, + { + name: "Infectious Diseases", + slug: "infectious-diseases", + entries: [ + { + slug: "influenza", + title: "Influenza", + }, + { + slug: "monkeypox", + title: "Mpox (monkeypox)", + }, + { + slug: "coronavirus", + title: "Coronavirus Pandemic (COVID-19)", + }, + { + slug: "hiv-aids", + title: "HIV / AIDS", + }, + { + slug: "malaria", + title: "Malaria", + }, + { + slug: "eradication-of-diseases", + title: "Eradication of Diseases", + }, + { + slug: "diarrheal-diseases", + title: "Diarrheal Diseases", + }, + { + slug: "smallpox", + title: "Smallpox", + }, + { + slug: "polio", + title: "Polio", + }, + { + slug: "pneumonia", + title: "Pneumonia", + }, + { + slug: "tetanus", + title: "Tetanus", + }, + ], + }, + { + name: "Health Institutions and Interventions", + slug: "health-institutions-and-interventions", + entries: [ + { + slug: "financing-healthcare", + title: "Healthcare Spending", + }, + { + slug: "vaccination", + title: "Vaccination", + }, + ], + }, + { + name: "Life and Death", + slug: "life-death-health", + entries: [ + { + slug: "life-expectancy", + title: "Life Expectancy", + }, + { + slug: "child-mortality", + title: "Child and Infant Mortality", + }, + { + slug: "maternal-mortality", + title: "Maternal Mortality", + }, + { + slug: "health-meta", + title: "Global Health", + }, + { + slug: "causes-of-death", + title: "Causes of Death", + }, + { + slug: "burden-of-disease", + title: "Burden of Disease", + }, + { + slug: "cancer", + title: "Cancer", + }, + { + slug: "mental-health", + title: "Mental Health", + }, + { + slug: "suicide", + title: "Suicides", + }, + ], + }, + ], + }, + { + name: "Energy and Environment", + slug: "environment", + entries: [], + subcategories: [ + { + name: "Energy", + slug: "energy", + entries: [ + { + slug: "nuclear-energy", + title: "Nuclear Energy", + }, + { + slug: "energy-access", + title: "Access to Energy", + }, + { + slug: "energy", + title: "Energy", + }, + { + slug: "renewable-energy", + title: "Renewable Energy", + }, + { + slug: "fossil-fuels", + title: "Fossil Fuels", + }, + ], + }, + { + name: "Waste and Pollution", + slug: "waste", + entries: [ + { + slug: "lead-pollution", + title: "Lead Pollution", + }, + { + slug: "plastic-pollution", + title: "Plastic Pollution", + }, + { + slug: "oil-spills", + title: "Oil Spills", + }, + ], + }, + { + name: "Air and Climate", + slug: "air-and-climate", + entries: [ + { + slug: "co2-and-greenhouse-gas-emissions", + title: "CO₂ and Greenhouse Gas Emissions", + }, + { + slug: "climate-change", + title: "Climate Change", + }, + { + slug: "air-pollution", + title: "Air Pollution", + }, + { + slug: "outdoor-air-pollution", + title: "Outdoor Air Pollution", + }, + { + slug: "indoor-air-pollution", + title: "Indoor Air Pollution", + }, + { + slug: "ozone-layer", + title: "Ozone Layer", + }, + ], + }, + { + name: "Water", + slug: "water", + entries: [ + { + slug: "clean-water-sanitation", + title: "Clean Water and Sanitation", + }, + { + slug: "water-access", + title: "Clean Water", + }, + { + slug: "sanitation", + title: "Sanitation", + }, + { + slug: "water-use-stress", + title: "Water Use and Stress", + }, + ], + }, + { + name: "Land and Ecosystems", + slug: "land-and-ecosystems", + entries: [ + { + slug: "biodiversity", + title: "Biodiversity", + }, + { + slug: "environmental-impacts-of-food", + title: "Environmental Impacts of Food Production", + }, + { + slug: "forests-and-deforestation", + title: "Forests and Deforestation", + }, + { + slug: "land-use", + title: "Land Use", + }, + { + slug: "natural-disasters", + title: "Natural Disasters", + }, + ], + }, + ], + }, + { + name: "Food and Agriculture", + slug: "food", + entries: [ + { + slug: "animal-welfare", + title: "Animal Welfare", + }, + ], + subcategories: [ + { + name: "Nutrition", + slug: "nutrition", + entries: [ + { + slug: "hunger-and-undernourishment", + title: "Hunger and Undernourishment", + }, + { + slug: "famines", + title: "Famines", + }, + { + slug: "food-supply", + title: "Food Supply", + }, + { + slug: "human-height", + title: "Human Height", + }, + { + slug: "micronutrient-deficiency", + title: "Micronutrient Deficiency", + }, + { + slug: "diet-compositions", + title: "Diet Compositions", + }, + { + slug: "food-prices", + title: "Food Prices", + }, + { + slug: "obesity", + title: "Obesity", + }, + { + slug: "alcohol-consumption", + title: "Alcohol Consumption", + }, + ], + }, + { + name: "Food Production", + slug: "food-production", + entries: [ + { + slug: "farm-size", + title: "Farm Size and Productivity", + }, + { + slug: "agricultural-production", + title: "Agricultural Production", + }, + { + slug: "environmental-impacts-of-food", + title: "Environmental Impacts of Food Production", + }, + { + slug: "crop-yields", + title: "Crop Yields", + }, + { + slug: "meat-production", + title: "Meat and Dairy Production", + }, + ], + }, + { + name: "Agricultural Inputs", + slug: "agricultural-inputs", + entries: [ + { + slug: "employment-in-agriculture", + title: "Employment in Agriculture", + }, + { + slug: "land-use", + title: "Land Use", + }, + { + slug: "fertilizers", + title: "Fertilizers", + }, + { + slug: "pesticides", + title: "Pesticides", + }, + ], + }, + ], + }, + { + name: "Poverty and Economic Development", + slug: "growth-inequality", + entries: [], + subcategories: [ + { + name: "Public Sector", + slug: "public-sector", + entries: [ + { + slug: "government-spending", + title: "Government Spending", + }, + { + slug: "taxation", + title: "Taxation", + }, + { + slug: "military-personnel-spending", + title: "Military Personnel and Spending", + }, + { + slug: "financing-healthcare", + title: "Healthcare Spending", + }, + { + slug: "financing-education", + title: "Education Spending", + }, + ], + }, + { + name: "Poverty and Prosperity", + slug: "poverty-and-prosperity", + entries: [ + { + slug: "economic-inequality", + title: "Economic Inequality", + }, + { + slug: "poverty", + title: "Poverty", + }, + { + slug: "economic-growth", + title: "Economic Growth", + }, + ], + }, + { + name: "Economic Inequality", + slug: "economic-inequality", + entries: [ + { + slug: "economic-inequality-by-gender", + title: "Economic Inequality by Gender", + }, + ], + }, + { + name: "Labor", + slug: "labor", + entries: [ + { + slug: "child-labor", + title: "Child Labor", + }, + { + slug: "working-hours", + title: "Working Hours", + }, + { + slug: "female-labor-supply", + title: "Women’s Employment", + }, + ], + }, + { + name: "Corruption", + slug: "corruption", + entries: [ + { + slug: "corruption", + title: "Corruption", + }, + ], + }, + { + name: "Trade and Migration", + slug: "trade-migration", + entries: [ + { + slug: "migration", + title: "Migration", + }, + { + slug: "trade-and-globalization", + title: "Trade and Globalization", + }, + { + slug: "tourism", + title: "Tourism", + }, + ], + }, + ], + }, + { + name: "Education and Knowledge", + slug: "education", + entries: [], + subcategories: [ + { + name: "Educational Access and Outcomes", + slug: "educational-outcomes", + entries: [ + { + slug: "global-education", + title: "Global Education", + }, + { + slug: "literacy", + title: "Literacy", + }, + { + slug: "research-and-development", + title: "Research and Development", + }, + ], + }, + { + name: "Inputs to Education", + slug: "inputs-to-education", + entries: [ + { + slug: "financing-education", + title: "Education Spending", + }, + ], + }, + { + name: "Media", + slug: "media-education", + entries: [ + { + slug: "books", + title: "Books", + }, + { + slug: "internet", + title: "Internet", + }, + ], + }, + ], + }, + { + name: "Innovation and Technological Change", + slug: "technology", + entries: [], + subcategories: [ + { + name: "Technological Change", + slug: "technological-change", + entries: [ + { + slug: "artificial-intelligence", + title: "Artificial Intelligence", + }, + { + slug: "space-exploration-satellites", + title: "Space Exploration and Satellites", + }, + { + slug: "transport", + title: "Transport", + }, + { + slug: "internet", + title: "Internet", + }, + { + slug: "research-and-development", + title: "Research and Development", + }, + { + slug: "technological-change", + title: "Technological Change", + }, + ], + }, + ], + }, + { + name: "Living Conditions, Community and Wellbeing", + slug: "work-life", + entries: [], + subcategories: [ + { + name: "Culture", + slug: "culture", + entries: [ + { + slug: "trust", + title: "Trust", + }, + ], + }, + { + name: "Housing", + slug: "housing", + entries: [ + { + slug: "clean-water-sanitation", + title: "Clean Water and Sanitation", + }, + { + slug: "energy-access", + title: "Access to Energy", + }, + { + slug: "water-access", + title: "Clean Water", + }, + { + slug: "homelessness", + title: "Homelessness", + }, + { + slug: "indoor-air-pollution", + title: "Indoor Air Pollution", + }, + { + slug: "light-at-night", + title: "Light at Night", + }, + { + slug: "sanitation", + title: "Sanitation", + }, + ], + }, + { + name: "Time Use", + slug: "time-use", + entries: [ + { + slug: "time-use", + title: "Time Use", + }, + { + slug: "working-hours", + title: "Working Hours", + }, + { + slug: "tourism", + title: "Tourism", + }, + ], + }, + { + name: "Relationships", + slug: "relationships", + entries: [ + { + slug: "marriages-and-divorces", + title: "Marriages and Divorces", + }, + { + slug: "social-connections-and-loneliness", + title: "Loneliness and Social Connections", + }, + ], + }, + { + name: "Happiness and Wellbeing", + slug: "happiness-wellbeing", + entries: [ + { + slug: "happiness-and-life-satisfaction", + title: "Happiness and Life Satisfaction", + }, + { + slug: "human-development-index", + title: "Human Development Index (HDI)", + }, + ], + }, + ], + }, + { + name: "Human Rights and Democracy", + slug: "politics", + entries: [], + subcategories: [ + { + name: "Human Rights", + slug: "human-rights", + entries: [ + { + slug: "state-capacity", + title: "State Capacity", + }, + { + slug: "lgbt-rights", + title: "LGBT+ Rights", + }, + { + slug: "women-rights", + title: "Women’s Rights", + }, + { + slug: "child-labor", + title: "Child Labor", + }, + { + slug: "human-rights", + title: "Human Rights", + }, + ], + }, + { + name: "Democracy and Corruption", + slug: "democracy", + entries: [ + { + slug: "democracy", + title: "Democracy", + }, + { + slug: "corruption", + title: "Corruption", + }, + ], + }, + ], + }, + { + name: "Violence and War", + slug: "violence-rights", + entries: [], + subcategories: [ + { + name: "War and Peace", + slug: "war-peace", + entries: [ + { + slug: "biological-and-chemical-weapons", + title: "Biological and Chemical Weapons", + }, + { + slug: "war-and-peace", + title: "War and Peace", + }, + { + slug: "military-personnel-spending", + title: "Military Personnel and Spending", + }, + { + slug: "terrorism", + title: "Terrorism", + }, + { + slug: "nuclear-weapons", + title: "Nuclear Weapons", + }, + ], + }, + { + name: "Violence", + slug: "violence", + entries: [ + { + slug: "state-capacity", + title: "State Capacity", + }, + { + slug: "violence-against-rights-for-children", + title: "Violence Against Children and Children’s Rights", + }, + { + slug: "homicides", + title: "Homicides", + }, + ], + }, + ], + }, + ], +} diff --git a/site/SiteNavigationTopics.tsx b/site/SiteNavigationTopics.tsx index f147a6eb46a..70f397c49bd 100644 --- a/site/SiteNavigationTopics.tsx +++ b/site/SiteNavigationTopics.tsx @@ -96,6 +96,8 @@ export const allTopicsInCategory = ( ): EntryMeta[] => { return [ ...category.entries, - ...category.subcategories.flatMap((subcategory) => subcategory.entries), + ...(category.subcategories ?? []).flatMap( + (subcategory) => subcategory.entries + ), ] }