Skip to content

Commit

Permalink
🎉 (admin) add indicator chart editor
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Sep 3, 2024
1 parent 288f511 commit bec18e8
Show file tree
Hide file tree
Showing 16 changed files with 712 additions and 173 deletions.
1 change: 1 addition & 0 deletions adminSiteClient/AbstractChartEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type EditorTab =
| "refs"
| "export"
| "inheritance"
| "debug"

export interface AbstractChartEditorManager {
admin: Admin
Expand Down
23 changes: 12 additions & 11 deletions adminSiteClient/AdminApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { BulkGrapherConfigEditorPage } from "./BulkGrapherConfigEditor.js"
import { GdocsIndexPage, GdocsMatchProps } from "./GdocsIndexPage.js"
import { GdocsPreviewPage } from "./GdocsPreviewPage.js"
import { GdocsStoreProvider } from "./GdocsStore.js"
import { IndicatorChartEditorPage } from "./IndicatorChartEditorPage.js"

@observer
class AdminErrorMessage extends React.Component<{ admin: Admin }> {
Expand Down Expand Up @@ -154,17 +155,6 @@ export class AdminApp extends React.Component<{
path="/charts"
component={ChartIndexPage}
/>
{/* <Route
exact
path="/indicator-charts/:variableId/edit"
render={({ match }) => (
<IndicatorChartEditorPage
variableId={parseInt(
match.params.variableId
)}
/>
)}
/> */}
<Route
exact
path={`/${EXPLORERS_ROUTE_FOLDER}/:slug`}
Expand Down Expand Up @@ -211,6 +201,17 @@ export class AdminApp extends React.Component<{
path="/users"
component={UsersIndexPage}
/>
<Route
exact
path="/variables/:variableId/config"
render={({ match }) => (
<IndicatorChartEditorPage
variableId={parseInt(
match.params.variableId
)}
/>
)}
/>
<Route
exact
path="/variables/:variableId"
Expand Down
8 changes: 7 additions & 1 deletion adminSiteClient/ChartEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,21 @@ export class ChartEditor extends AbstractChartEditor<ChartEditorManager> {
return this.manager.pageviews
}

/** parent variable id, derived from the config */
@computed get parentVariableId(): number | undefined {
return getParentVariableIdFromChartConfig(this.fullConfig)
}

@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("revisions")
tabs.push("refs")
if (this.parentConfig) tabs.push("inheritance")
if (this.parentVariableId) tabs.push("inheritance")
tabs.push("export")
tabs.push("debug")
return tabs
}

Expand Down
44 changes: 18 additions & 26 deletions adminSiteClient/ChartEditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ import { EditorMapTab } from "./EditorMapTab.js"
import { EditorHistoryTab } from "./EditorHistoryTab.js"
import { EditorReferencesTab } from "./EditorReferencesTab.js"
import { EditorInheritanceTab } from "./EditorInheritanceTab.js"
import {
SaveButtonsForChart,
SaveButtonsForIndicatorChart,
} from "./SaveButtons.js"
import { EditorDebugTab } from "./EditorDebugTab.js"
import { SaveButtons } from "./SaveButtons.js"
import { LoadingBlocker } from "./Forms.js"
import { AdminLayout } from "./AdminLayout.js"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
Expand All @@ -54,7 +52,6 @@ import { EditorMarimekkoTab } from "./EditorMarimekkoTab.js"
import { EditorExportTab } from "./EditorExportTab.js"
import { runDetailsOnDemand } from "../site/detailsOnDemand.js"
import { AbstractChartEditor } from "./AbstractChartEditor.js"
import { isIndicatorChartEditorInstance } from "./IndicatorChartEditor.js"
import {
ErrorMessages,
ErrorMessagesForDimensions,
Expand Down Expand Up @@ -353,23 +350,6 @@ export class ChartEditorView<
const { grapher, availableTabs } = editor

const chartEditor = isChartEditorInstance(editor) ? editor : undefined
const indicatorChartEditor = isIndicatorChartEditorInstance(editor)
? editor
: undefined

const saveButtons = chartEditor ? (
<SaveButtonsForChart
editor={chartEditor}
errorMessages={this.errorMessages}
errorMessagesForDimensions={this.errorMessagesForDimensions}
/>
) : indicatorChartEditor ? (
<SaveButtonsForIndicatorChart
editor={indicatorChartEditor}
errorMessages={this.errorMessages}
errorMessagesForDimensions={this.errorMessagesForDimensions}
/>
) : null

return (
<>
Expand Down Expand Up @@ -403,7 +383,8 @@ export class ChartEditorView<
>
{capitalize(tab)}
{tab === "inheritance" &&
editor.isInheritanceEnabled &&
chartEditor &&
chartEditor.isInheritanceEnabled &&
" (enabled)"}
{tab === "refs" &&
chartEditor?.references
Expand Down Expand Up @@ -456,14 +437,25 @@ export class ChartEditorView<
{chartEditor && chartEditor.tab === "refs" && (
<EditorReferencesTab editor={chartEditor} />
)}
{chartEditor && chartEditor.tab === "inheritance" && (
<EditorInheritanceTab editor={chartEditor} />
{editor.tab === "inheritance" && (
<EditorInheritanceTab editor={editor} />
)}
{editor.tab === "export" && (
<EditorExportTab editor={editor} />
)}
{editor.tab === "debug" && (
<EditorDebugTab editor={editor} />
)}
</div>
{editor.tab !== "export" && saveButtons}
{editor.tab !== "export" && (
<SaveButtons
editor={editor}
errorMessages={this.errorMessages}
errorMessagesForDimensions={
this.errorMessagesForDimensions
}
/>
)}
</div>
<div className="chart-editor-view">
<figure
Expand Down
45 changes: 38 additions & 7 deletions adminSiteClient/EditorBasicTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ import { AbstractChartEditor } from "./AbstractChartEditor.js"
import { EditorDatabase } from "./ChartEditorView.js"
import { isChartEditorInstance } from "./ChartEditor.js"
import { ErrorMessagesForDimensions } from "./ChartEditorTypes.js"
import {
IndicatorChartEditor,
isIndicatorChartEditorInstance,
} from "./IndicatorChartEditor.js"

@observer
class DimensionSlotView<
Expand Down Expand Up @@ -383,8 +387,12 @@ export class EditorBasicTab<
(chartType) => chartType !== ChartTypeName.WorldMap
)

const isIndicatorChart = isIndicatorChartEditorInstance(editor)

return (
<div className="EditorBasicTab">
{isIndicatorChart && <IndicatorChartInfo editor={editor} />}

<Section name="Type of chart">
<SelectField
value={grapher.type}
Expand All @@ -407,14 +415,37 @@ export class EditorBasicTab<
/>
</FieldsRow>
</Section>
<VariablesSection
editor={editor}
database={this.props.database}
errorMessagesForDimensions={
this.props.errorMessagesForDimensions
}
/>
{!isIndicatorChart && (
<VariablesSection
editor={editor}
database={this.props.database}
errorMessagesForDimensions={
this.props.errorMessagesForDimensions
}
/>
)}
</div>
)
}
}

function IndicatorChartInfo(props: { editor: IndicatorChartEditor }) {
const { variableId, grapher } = props.editor

const column = grapher.inputTable.get(variableId?.toString())
const variableLink = (
<a
href={`/admin/variables/${variableId}`}
target="_blank"
rel="noopener"
>
{column?.name ?? variableId}
</a>
)

return (
<Section name="Indicator chart">
<p>This is the Grapher config for indicator {variableLink}.</p>
</Section>
)
}
155 changes: 155 additions & 0 deletions adminSiteClient/EditorDebugTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React from "react"
import { observer } from "mobx-react"
import { Section } from "./Forms.js"
import { ChartEditor, isChartEditorInstance } from "./ChartEditor.js"
import { action } from "mobx"
import { copyToClipboard } from "@ourworldindata/utils"
import YAML from "yaml"
import { notification } from "antd"
import {
IndicatorChartEditor,
isIndicatorChartEditorInstance,
} from "./IndicatorChartEditor.js"
import { AbstractChartEditor } from "./AbstractChartEditor.js"

@observer
export class EditorDebugTab<
Editor extends AbstractChartEditor,
> extends React.Component<{
editor: Editor
}> {
render() {
const { editor } = this.props
if (isChartEditorInstance(editor))
return <EditorDebugTabForChart editor={editor} />
else if (isIndicatorChartEditorInstance(editor))
return <EditorDebugTabForIndicatorChart editor={editor} />
else return null
}
}

@observer
class EditorDebugTabForChart extends React.Component<{
editor: ChartEditor
}> {
@action.bound copyYamlToClipboard() {
// Avoid modifying the original JSON object
// Due to mobx memoizing computed values, the JSON can be mutated.
const patchConfig = {
...this.props.editor.patchConfig,
}
delete patchConfig.id
delete patchConfig.dimensions
delete patchConfig.version
delete patchConfig.isPublished
const chartConfigAsYaml = YAML.stringify(patchConfig)
// Use the Clipboard API to copy the config into the users clipboard
void copyToClipboard(chartConfigAsYaml)
notification["success"]({
message: "Copied YAML to clipboard",
description: "You can now paste this into the ETL",
placement: "bottomRight",
closeIcon: <></>,
})
}

render() {
const {
patchConfig,
parentConfig,
isInheritanceEnabled = false,
fullConfig,
} = this.props.editor

return (
<div>
<Section name="Config">
<button
className="btn btn-primary"
onClick={this.copyYamlToClipboard}
>
Copy YAML for ETL
</button>
<textarea
rows={7}
readOnly
className="form-control"
value={JSON.stringify(patchConfig, undefined, 2)}
/>
</Section>

{parentConfig && (
<Section
name={
isInheritanceEnabled
? "Parent config (applied)"
: "Parent config (not currently applied)"
}
>
<textarea
rows={7}
readOnly
className="form-control"
value={JSON.stringify(parentConfig, undefined, 2)}
/>
</Section>
)}

<Section name="Full Config">
<textarea
rows={7}
readOnly
className="form-control"
value={JSON.stringify(fullConfig, undefined, 2)}
/>
</Section>
</div>
)
}
}

@observer
class EditorDebugTabForIndicatorChart extends React.Component<{
editor: IndicatorChartEditor
}> {
render() {
const { patchConfig, parentConfig, fullConfig } = this.props.editor

return (
<div className="ConfigTab">
<Section name="Config">
<textarea
rows={7}
readOnly
className="form-control"
value={JSON.stringify(patchConfig, undefined, 2)}
/>
</Section>
{parentConfig && (
<>
<Section name="Parent config (authored in the ETL)">
<textarea
rows={7}
readOnly
className="form-control"
value={JSON.stringify(
parentConfig,
undefined,
2
)}
/>
</Section>
<Section name="Merged config">
<textarea
rows={7}
readOnly
className="form-control"
value={JSON.stringify(fullConfig, undefined, 2)}
/>
</Section>
</>
)}
</div>
)
}
}
Loading

0 comments on commit bec18e8

Please sign in to comment.