Skip to content

Commit

Permalink
feat: basic chart editor for chart views
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelgerber committed Nov 27, 2024
1 parent 9e97c78 commit a35c831
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 0 deletions.
12 changes: 12 additions & 0 deletions adminSiteClient/AdminApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { GdocsIndexPage, GdocsMatchProps } from "./GdocsIndexPage.js"
import { GdocsPreviewPage } from "./GdocsPreviewPage.js"
import { GdocsStoreProvider } from "./GdocsStore.js"
import { IndicatorChartEditorPage } from "./IndicatorChartEditorPage.js"
import { ChartViewEditorPage } from "./ChartViewEditorPage.js"

@observer
class AdminErrorMessage extends React.Component<{ admin: Admin }> {
Expand Down Expand Up @@ -157,6 +158,17 @@ export class AdminApp extends React.Component<{
path="/charts"
component={ChartIndexPage}
/>
<Route
exact
path="/chartViews/:chartViewId/edit"
render={({ match }) => (
<ChartViewEditorPage
chartViewId={parseInt(
match.params.chartViewId
)}
/>
)}
/>
<Route
exact
path={`/${EXPLORERS_ROUTE_FOLDER}/:slug`}
Expand Down
68 changes: 68 additions & 0 deletions adminSiteClient/ChartViewEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { computed, runInAction } from "mobx"
import {
AbstractChartEditor,
AbstractChartEditorManager,
type EditorTab,
} from "./AbstractChartEditor.js"

export interface Chart {
id: number
title?: string
variantName?: string
isChild: boolean
}

export interface ChartViewEditorManager extends AbstractChartEditorManager {
chartViewId: number
}

export class ChartViewEditor extends AbstractChartEditor<ChartViewEditorManager> {
constructor(props: { manager: ChartViewEditorManager }) {
super(props)
}

@computed
get availableTabs(): EditorTab[] {
const tabs: EditorTab[] = ["basic", "data", "text", "customize"]
if (this.grapher.hasMapTab) tabs.push("map")
if (this.grapher.isScatter) tabs.push("scatter")
if (this.grapher.isMarimekko) tabs.push("marimekko")
tabs.push("inheritance")
tabs.push("debug")
return tabs
}

@computed get chartViewId(): number {
return this.manager.chartViewId
}

@computed get isNewGrapher(): boolean {
return false
}

async saveGrapher({
onError,
}: { onError?: () => void } = {}): Promise<void> {
const { patchConfig, chartViewId } = this

const json = await this.manager.admin.requestJSON(
`/api/chartViews/${chartViewId}`,
{ config: patchConfig },
"PUT"
)

if (json.success) {
runInAction(() => {
this.savedPatchConfig = json.savedPatch
})
} else {
onError?.()
}
}
}

export function isChartViewEditorInstance(
editor: AbstractChartEditor
): editor is ChartViewEditor {
return editor instanceof ChartViewEditor
}
64 changes: 64 additions & 0 deletions adminSiteClient/ChartViewEditorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react"
import { observer } from "mobx-react"
import { computed, action } 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"

@observer
export class ChartViewEditorPage
extends React.Component<{
chartViewId: number
}>
implements ChartViewEditorManager, ChartEditorViewManager<ChartViewEditor>
{
static contextType = AdminAppContext
context!: AdminAppContextType

idAndSlug: { id: number; slug: string } | undefined = undefined

patchConfig: GrapherInterface = {}
fullConfig: GrapherInterface = {}
parentChartId: number = 0
parentChartConfigFull: GrapherInterface = {}

isInheritanceEnabled: boolean | undefined = undefined

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

this.idAndSlug = { id: data.id, slug: data.slug }
this.fullConfig = data.configFull
this.patchConfig = data.configPatch
this.parentChartId = data.parentChartId
this.parentChartConfigFull = data.parentConfigFull
}

@computed get admin(): Admin {
return this.context.admin
}

@computed get chartViewId(): number {
return this.props.chartViewId
}

@computed get editor(): ChartViewEditor {
return new ChartViewEditor({ manager: this })
}

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

componentDidMount(): void {
this.refresh()
}

render(): React.ReactElement {
return <ChartEditorView manager={this} />
}
}
57 changes: 57 additions & 0 deletions adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3354,6 +3354,63 @@ getRouteWithROTransaction(apiRouter, "/chartViews", async (req, res, trx) => {
return { chartViews }
})

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

type ChartViewRow = Pick<
DbPlainChartView,
"id" | "slug" | "updatedAt"
> & {
lastEditedByUser: string
chartConfigId: string
configFull: string
configPatch: string
parentChartId: number
parentConfigFull: string
}

const row = await db.knexRawFirst<ChartViewRow>(
trx,
`-- sql
SELECT
cv.id,
cv.slug,
cv.updatedAt,
cv.lastEditedByUserId,
u.fullName as lastEditedByUser,
cv.chartConfigId,
cc.full as configFull,
cc.patch as configPatch,
cv.parentChartId,
pcc.full as parentConfigFull
FROM chart_views cv
JOIN chart_configs cc ON cv.chartConfigId = cc.id
JOIN charts pc ON cv.parentChartId = pc.id
JOIN chart_configs pcc ON pc.configId = pcc.id
JOIN users u ON cv.lastEditedByUserId = u.id
WHERE cv.id = ?
`,
[id]
)

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

const chartView = {
...row,
configFull: JSON.parse(row.configFull),
configPatch: JSON.parse(row.configPatch),
parentConfigFull: JSON.parse(row.parentConfigFull),
}

return chartView
}
)

postRouteWithRWTransaction(apiRouter, "/chartViews", async (req, res, trx) => {
const { slug, parentChartId } = req.body as Pick<
DbPlainChartView,
Expand Down

0 comments on commit a35c831

Please sign in to comment.