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

feat(admin): allow tagging on the chart basic tab #4506

Merged
merged 3 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions adminSiteClient/ChartEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
omit,
CHART_VIEW_PROPS_TO_OMIT,
} from "@ourworldindata/utils"
import { DbChartTagJoin } from "@ourworldindata/types"
import { action, computed, observable, runInAction } from "mobx"
import { BAKED_GRAPHER_URL } from "../settings/clientSettings.js"
import {
Expand Down Expand Up @@ -48,6 +49,8 @@ export interface ChartEditorManager extends AbstractChartEditorManager {
references: References | undefined
redirects: ChartRedirect[]
pageviews?: RawPageview
tags?: DbChartTagJoin[]
availableTags?: DbChartTagJoin[]
}

export class ChartEditor extends AbstractChartEditor<ChartEditorManager> {
Expand All @@ -71,6 +74,14 @@ export class ChartEditor extends AbstractChartEditor<ChartEditorManager> {
return this.manager.pageviews
}

@computed get tags() {
return this.manager.tags
}

@computed get availableTags() {
return this.manager.availableTags
}

/** parent variable id, derived from the config */
@computed get parentVariableId(): number | undefined {
return getParentVariableIdFromChartConfig(this.liveConfig)
Expand Down
26 changes: 25 additions & 1 deletion adminSiteClient/ChartEditorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
getParentVariableIdFromChartConfig,
RawPageview,
} from "@ourworldindata/utils"
import { GrapherInterface, ChartRedirect } from "@ourworldindata/types"
import {
GrapherInterface,
ChartRedirect,
MinimalTagWithIsTopic,
DbChartTagJoin,
} from "@ourworldindata/types"
import { Admin } from "./Admin.js"
import {
ChartEditor,
Expand All @@ -32,6 +37,8 @@ export class ChartEditorPage
@observable references: References | undefined = undefined
@observable redirects: ChartRedirect[] = []
@observable pageviews?: RawPageview = undefined
@observable tags?: DbChartTagJoin[] = undefined
@observable availableTags?: MinimalTagWithIsTopic[] = undefined

patchConfig: GrapherInterface = {}
parentConfig: GrapherInterface | undefined = undefined
Expand Down Expand Up @@ -114,6 +121,21 @@ export class ChartEditorPage
runInAction(() => (this.pageviews = json.pageviews))
}

async fetchTags(): Promise<void> {
const { grapherId } = this.props
const { admin } = this.context
const json =
grapherId === undefined
? {}
: await admin.getJSON(`/api/charts/${grapherId}.tags.json`)
runInAction(() => (this.tags = json.tags))
}

async fetchAvailableTags() {
const json = (await this.admin.getJSON("/api/tags.json")) as any
this.availableTags = json.tags
}

@computed get admin(): Admin {
return this.context.admin
}
Expand All @@ -129,6 +151,8 @@ export class ChartEditorPage
void this.fetchRefs()
void this.fetchRedirects()
void this.fetchPageviews()
void this.fetchTags()
void this.fetchAvailableTags()
}

componentDidMount(): void {
Expand Down
66 changes: 65 additions & 1 deletion adminSiteClient/EditorBasicTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
ALL_GRAPHER_CHART_TYPES,
GrapherChartType,
GRAPHER_CHART_TYPES,
DbChartTagJoin,
TaggableType,
} from "@ourworldindata/types"
import {
DimensionSlot,
Expand Down Expand Up @@ -41,12 +43,14 @@ import {
} from "react-beautiful-dnd"
import { AbstractChartEditor } from "./AbstractChartEditor.js"
import { EditorDatabase } from "./ChartEditorView.js"
import { isChartEditorInstance } from "./ChartEditor.js"
import { ChartEditor, isChartEditorInstance } from "./ChartEditor.js"
import { ErrorMessagesForDimensions } from "./ChartEditorTypes.js"
import {
IndicatorChartEditor,
isIndicatorChartEditorInstance,
} from "./IndicatorChartEditor.js"
import { EditableTags } from "./EditableTags.js"
import { AdminAppContext, AdminAppContextType } from "./AdminAppContext.js"

@observer
class DimensionSlotView<
Expand Down Expand Up @@ -353,6 +357,41 @@ class VariablesSection<
}
}

// eslint-disable-next-line react-refresh/only-export-components
const TagsSection = (props: {
chartId: number | undefined
editor: ChartEditor
onSaveTags: (tags: DbChartTagJoin[]) => void
}) => {
const { tags, availableTags } = props.editor
const canTag = !!props.chartId && tags && availableTags
return (
<Section name="Tags">
{canTag ? (
<>
<EditableTags
onSave={props.onSaveTags}
tags={tags}
suggestions={availableTags}
hasKeyChartSupport
hasSuggestionsSupport
taggable={{
type: TaggableType.Charts,
id: props.chartId,
}}
/>
<small className="form-text text-muted">
Changes to tags will be applied instantly, without the
need to save the chart.
</small>
</>
) : (
<p>Can't tag this chart. Maybe it hasn't been saved yet?</p>
)}
</Section>
)
}

@observer
export class EditorBasicTab<
Editor extends AbstractChartEditor,
Expand All @@ -361,6 +400,9 @@ export class EditorBasicTab<
database: EditorDatabase
errorMessagesForDimensions: ErrorMessagesForDimensions
}> {
static contextType = AdminAppContext
context!: AdminAppContextType

private chartTypeOptionNone = "None"

@action.bound private updateParentConfig() {
Expand Down Expand Up @@ -451,6 +493,20 @@ export class EditorBasicTab<
}
}

@action.bound onSaveTags(tags: DbChartTagJoin[]) {
void this.saveTags(tags)
}

async saveTags(tags: DbChartTagJoin[]) {
const { editor } = this.props
const { grapher } = editor
await this.context.admin.requestJSON(
`/api/charts/${grapher.id}/setTags`,
{ tags },
"POST"
)
}

render() {
const { editor } = this.props
const { grapher } = editor
Expand Down Expand Up @@ -493,6 +549,14 @@ export class EditorBasicTab<
}
/>
)}

{isChartEditorInstance(editor) && (
<TagsSection
chartId={grapher.id}
editor={editor}
onSaveTags={this.onSaveTags}
/>
)}
</div>
)
}
Expand Down
6 changes: 6 additions & 0 deletions adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import {
setChartTagsHandler,
updateChart,
deleteChart,
getChartTagsJson,
} from "./apiRoutes/charts.js"

const apiRouter = new FunctionalRouter()
Expand Down Expand Up @@ -183,6 +184,11 @@ getRouteWithROTransaction(
"/charts/:chartId.pageviews.json",
getChartPageviewsJson
)
getRouteWithROTransaction(
apiRouter,
"/charts/:chartId.tags.json",
getChartTagsJson
)
postRouteWithRWTransaction(apiRouter, "/charts", createChart)
postRouteWithRWTransaction(
apiRouter,
Expand Down
21 changes: 21 additions & 0 deletions adminSiteServer/apiRoutes/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DbInsertChartRevision,
DbRawChartConfig,
ChartConfigsTableName,
DbChartTagJoin,
} from "@ourworldindata/types"
import {
diffGrapherConfigs,
Expand Down Expand Up @@ -685,6 +686,26 @@ export async function getChartPageviewsJson(
}
}

export async function getChartTagsJson(
req: Request,
res: e.Response<any, Record<string, any>>,
trx: db.KnexReadonlyTransaction
) {
const chartId = expectInt(req.params.chartId)
const chartTags = await db.knexRaw<DbChartTagJoin>(
trx,
`-- sql
SELECT ct.tagId as id, ct.keyChartLevel, ct.isApproved, t.name
FROM chart_tags ct
JOIN charts c ON c.id=ct.chartId
JOIN tags t ON t.id=ct.tagId
WHERE ct.chartId = ?
`,
[chartId]
)
return { tags: chartTags }
}

export async function createChart(
req: Request,
res: e.Response<any, Record<string, any>>,
Expand Down
7 changes: 1 addition & 6 deletions db/model/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,7 @@ export async function assignTagsForCharts(
knex: db.KnexReadonlyTransaction,
charts: {
id: number
tags?: {
id: number
name: string
keyChartLevel: number
isApproved: boolean
}[]
tags?: DbChartTagJoin[]
}[]
): Promise<void> {
const chartTags = await db.knexRaw<{
Expand Down