Skip to content

Commit

Permalink
feat: implement refs tab for narrative charts
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelgerber committed Jan 9, 2025
1 parent d69d129 commit 9a16e9c
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 21 deletions.
6 changes: 3 additions & 3 deletions adminSiteClient/ChartViewEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface Chart {
export interface ChartViewEditorManager extends AbstractChartEditorManager {
chartViewId: number
parentChartId: number
references: References | undefined
}

export class ChartViewEditor extends AbstractChartEditor<ChartViewEditorManager> {
Expand All @@ -48,9 +49,8 @@ export class ChartViewEditor extends AbstractChartEditor<ChartViewEditorManager>
return tabs
}

@computed get references(): References | undefined {
// Not yet implemented for chart views
return undefined
@computed get references() {
return this.manager.references
}

@computed override get patchConfig(): GrapherInterface {
Expand Down
19 changes: 17 additions & 2 deletions adminSiteClient/ChartViewEditorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from "react"
import { observer } from "mobx-react"
import { computed, action } from "mobx"
import { computed, action, runInAction, observable } from "mobx"
import { GrapherInterface } from "@ourworldindata/types"
import { Admin } from "./Admin.js"
import { AdminAppContext, AdminAppContextType } from "./AdminAppContext.js"
import { ChartEditorView, ChartEditorViewManager } from "./ChartEditorView.js"
import { ChartViewEditor, ChartViewEditorManager } from "./ChartViewEditor.js"
import { References } from "./AbstractChartEditor.js"

@observer
export class ChartViewEditorPage
Expand All @@ -26,9 +27,11 @@ export class ChartViewEditorPage

isInheritanceEnabled: boolean | undefined = true

@observable references: References | undefined = undefined

async fetchChartViewData(): Promise<void> {
const data = await this.context.admin.getJSON(
`/api/chartViews/${this.chartViewId}`
`/api/chartViews/${this.chartViewId}.config.json`
)

this.idAndName = { id: data.id, name: data.name }
Expand All @@ -50,8 +53,20 @@ export class ChartViewEditorPage
return new ChartViewEditor({ manager: this })
}

async fetchRefs(): Promise<void> {
const { admin } = this.context
const json =
this.chartViewId === undefined
? {}
: await admin.getJSON(
`/api/chartViews/${this.chartViewId}.references.json`
)
runInAction(() => (this.references = json.references))
}

@action.bound refresh(): void {
void this.fetchChartViewData()
void this.fetchRefs()
}

componentDidMount(): void {
Expand Down
24 changes: 24 additions & 0 deletions adminSiteClient/EditorReferencesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import {
isIndicatorChartEditorInstance,
} from "./IndicatorChartEditor.js"
import { Section } from "./Forms.js"
import {
ChartViewEditor,
isChartViewEditorInstance,
} from "./ChartViewEditor.js"

const BASE_URL = BAKED_GRAPHER_URL.replace(/^https?:\/\//, "")

Expand All @@ -37,6 +41,8 @@ export class EditorReferencesTab<
return <EditorReferencesTabForChart editor={editor} />
else if (isIndicatorChartEditorInstance(editor))
return <EditorReferencesTabForIndicator editor={editor} />
else if (isChartViewEditorInstance(editor))
return <EditorReferencesTabForChartView editor={editor} />
else return null
}
}
Expand Down Expand Up @@ -268,6 +274,24 @@ export class EditorReferencesTabForChart extends Component<{
}
}

export class EditorReferencesTabForChartView extends Component<{
editor: ChartViewEditor
}> {
@computed get references() {
return this.props.editor.references
}

render() {
return (
<div>
<section>
<ReferencesSection references={this.references} />
</section>
</div>
)
}
}

@observer
class AddRedirectForm<Editor extends AbstractChartEditor> extends Component<{
editor: Editor
Expand Down
2 changes: 1 addition & 1 deletion adminSiteClient/SaveButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class SaveButtonsForChartView extends Component<{
onClick={this.onSaveChart}
disabled={isSavingDisabled}
>
Save chart view
Save narrative chart
</button>{" "}
<a
className="btn btn-secondary"
Expand Down
67 changes: 58 additions & 9 deletions adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
getParentVariableIdFromChartConfig,
omit,
gdocUrlRegex,
uniqBy,
} from "@ourworldindata/utils"
import { applyPatch } from "../adminShared/patchHelper.js"
import {
Expand Down Expand Up @@ -117,6 +118,7 @@ import {
CHART_VIEW_PROPS_TO_OMIT,
DbEnrichedImage,
JsonString,
OwidGdocLinkType,
} from "@ourworldindata/types"
import { uuidv7 } from "uuidv7"
import {
Expand Down Expand Up @@ -1102,7 +1104,7 @@ deleteRouteWithRWTransaction(
if (chart.slug) {
const links = await getPublishedLinksTo(trx, [chart.slug])
if (links.length) {
const sources = links.map((link) => link.sourceSlug).join(", ")
const sources = links.map((link) => link.slug).join(", ")
throw new Error(
`Cannot delete chart in-use in the following published documents: ${sources}`
)
Expand Down Expand Up @@ -3693,7 +3695,7 @@ getRouteWithROTransaction(apiRouter, "/chartViews", async (req, res, trx) => {

getRouteWithROTransaction(
apiRouter,
"/chartViews/:id",
"/chartViews/:id.config.json",
async (req, res, trx) => {
const id = expectInt(req.params.id)

Expand Down Expand Up @@ -3858,16 +3860,35 @@ deleteRouteWithRWTransaction(
async (req, res, trx) => {
const id = expectInt(req.params.id)

const chartConfigId: string | undefined = await trx(ChartViewsTableName)
.select("chartConfigId")
.where({ id })
.first()
.then((row) => row?.chartConfigId)

if (!chartConfigId) {
const {
name,
chartConfigId,
}: { name: string | undefined; chartConfigId: string | undefined } =
await trx(ChartViewsTableName)
.select("name", "chartConfigId")
.where({ id })
.first()
.then((row) => row ?? {})

if (!chartConfigId || !name) {
throw new JsonError(`No chart view found for id ${id}`, 404)
}

const references = await getPublishedLinksTo(
trx,
[name],
OwidGdocLinkType.ChartView
)

if (references.length) {
throw new JsonError(
`Cannot delete chart view "${name}" because it is referenced by the following posts: ${references
.map((r) => r.slug)
.join(", ")}`,
400
)
}

await trx.table(ChartViewsTableName).where({ id }).delete()

await deleteGrapherConfigFromR2ByUUID(chartConfigId)
Expand All @@ -3881,4 +3902,32 @@ deleteRouteWithRWTransaction(
}
)

getRouteWithROTransaction(
apiRouter,
"/chartViews/:id.references.json",
async (req, res, trx) => {
const id = expectInt(req.params.id)
const name: string | undefined = await trx(ChartViewsTableName)
.select("name")
.where({ id })
.first()
.then((row) => row?.name)

if (!name) {
throw new JsonError(`No chart view found for id ${id}`, 404)
}

const references = {
references: {
postsGdocs: await getPublishedLinksTo(
trx,
[name],
OwidGdocLinkType.ChartView
).then((refs) => uniqBy(refs, "slug")),
},
}
return references
}
)

export { apiRouter }
30 changes: 24 additions & 6 deletions db/model/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,41 @@ import {
OwidGdocLinkType,
} from "@ourworldindata/types"
import { KnexReadonlyTransaction, knexRaw } from "../db.js"
import { BAKED_BASE_URL } from "../../settings/clientSettings.js"

export async function getPublishedLinksTo(
knex: KnexReadonlyTransaction,
ids: string[],
linkType?: OwidGdocLinkType
): Promise<(DbPlainPostGdocLink & { sourceSlug: string })[]> {
): Promise<
(DbPlainPostGdocLink & {
title: string
slug: string
id: string
url: string
})[]
> {
const linkTypeClause = linkType ? "AND linkType = ?" : ""
const params = linkType ? [ids, linkType] : [ids]
const rows = await knexRaw<DbPlainPostGdocLink & { sourceSlug: string }>(
const rows = await knexRaw<
DbPlainPostGdocLink & {
title: string
slug: string
id: string
url: string
}
>(
knex,
`-- sql
SELECT
posts_gdocs_links.*,
posts_gdocs.slug AS sourceSlug
pg.content ->> '$.title' AS title,
pg.slug AS slug,
pg.id AS id,
CONCAT("${BAKED_BASE_URL}","/",pg.slug) as url,
pgl.*
FROM
posts_gdocs_links
JOIN posts_gdocs ON posts_gdocs_links.sourceId = posts_gdocs.id
posts_gdocs_links pgl
JOIN posts_gdocs pg ON pgl.sourceId = pg.id
WHERE
target IN (?)
${linkTypeClause}
Expand Down

0 comments on commit 9a16e9c

Please sign in to comment.