Skip to content

Commit

Permalink
🎉 (grapher) make charts inherit indicator-level settings
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Jul 19, 2024
1 parent 598cbc1 commit ca24134
Show file tree
Hide file tree
Showing 32 changed files with 1,818 additions and 726 deletions.
12 changes: 12 additions & 0 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,6 +155,17 @@ 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
250 changes: 181 additions & 69 deletions adminSiteClient/ChartEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,24 @@
*
*/

import { Grapher } from "@ourworldindata/grapher"
import {
type DetailDictionary,

Check warning on line 9 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

'DetailDictionary' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 9 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

'DetailDictionary' is defined but never used. Allowed unused vars must match /^_/u
type RawPageview,
Topic,
PostReference,
ChartRedirect,
DimensionProperty,
Json,
GrapherInterface,
diffGrapherConfigs,
isEqual,
omit,
GrapherInterface,
diffGrapherConfigs,
getParentIndicatorIdFromChartConfig,
} from "@ourworldindata/utils"
import { computed, observable, runInAction, when } from "mobx"
import { computed, observable, reaction, runInAction, when } from "mobx"
import { BAKED_GRAPHER_URL } from "../settings/clientSettings.js"
import { Admin } from "./Admin.js"
import { EditorFeatures } from "./EditorFeatures.js"

type EditorTab = string
import { Admin } from "./Admin.js"
import { Grapher } from "@ourworldindata/grapher"

interface Variable {
id: number
Expand Down Expand Up @@ -54,14 +52,6 @@ export interface References {
explorers: string[]
}

export const getFullReferencesCount = (references: References): number => {
return (
references.postsWordpress.length +
references.postsGdocs.length +
references.explorers.length
)
}

export interface Namespace {

Check failure on line 55 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Namespace'

Check failure on line 55 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Namespace'
name: string
description?: string
Expand All @@ -70,7 +60,6 @@ export interface Namespace {

// This contains the dataset/variable metadata for the entire database
// Used for variable selector interface

export interface NamespaceData {

Check failure on line 63 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'NamespaceData'

Check failure on line 63 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'NamespaceData'
datasets: Dataset[]
}
Expand All @@ -97,45 +86,91 @@ export interface DimensionErrorMessage {
displayName?: string
}

export interface ChartEditorManager {
admin: Admin
grapher: Grapher
database: EditorDatabase
baseGrapherConfig: GrapherInterface
logs: Log[]
references: References | undefined
redirects: ChartRedirect[]
pageviews?: RawPageview
allTopics: Topic[]
details: DetailDictionary
invalidDetailReferences: DetailReferences
errorMessages: Partial<Record<FieldWithDetailReferences, string>>
errorMessagesForDimensions: Record<
DimensionProperty,
DimensionErrorMessage[]
>
type EditorTab =
| "basic"
| "data"
| "text"
| "customize"
| "map"
| "scatter"
| "marimekko"
| "revisions"
| "refs"
| "export"

interface Variable {
id: number
name: string
}

interface VariableIdUsageRecord {
variableId: number
usageCount: number
export interface Dataset {

Check failure on line 106 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Dataset'

Check failure on line 106 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Dataset'
id: number
name: string
namespace: string
version: string | undefined
variables: Variable[]
isPrivate: boolean
nonRedistributable: boolean
}

export class ChartEditor {
manager: ChartEditorManager
export interface Log {

Check failure on line 116 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Log'

Check failure on line 116 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Log'
userId: number
userName: string
config: Json
createdAt: string
}

export interface References {

Check failure on line 123 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'References'

Check failure on line 123 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'References'
postsWordpress: PostReference[]
postsGdocs: PostReference[]
explorers: string[]
}

export const getFullReferencesCount = (references: References): number => {
return (
references.postsWordpress.length +
references.postsGdocs.length +
references.explorers.length
)
}

export interface Namespace {

Check failure on line 137 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Namespace'

Check failure on line 137 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

Multiple exports of name 'Namespace'
name: string
description?: string
isArchived: boolean
}

// This contains the dataset/variable metadata for the entire database
// Used for variable selector interface
export interface NamespaceData {
datasets: Dataset[]
}

export interface DimensionErrorMessage {
displayName?: string
}

export interface AbstractChartEditorManager {
admin: Admin
grapher: Grapher
}

export abstract class AbstractChartEditor<
Manager extends AbstractChartEditorManager = AbstractChartEditorManager,
> {
manager: Manager
// Whether the current chart state is saved or not
@observable.ref currentRequest: Promise<any> | undefined
@observable.ref tab: EditorTab = "basic"
@observable.ref errorMessage?: { title: string; content: string }
@observable.ref previewMode: "mobile" | "desktop"
@observable.ref showStaticPreview = false
@observable.ref savedPatchConfig: GrapherInterface = {}
@observable.ref parentConfig: GrapherInterface = {}

// This gets set when we save a new chart for the first time
// so the page knows to update the url
@observable.ref newChartId?: number
private cachedParentConfigs = new Map<number, GrapherInterface>()

constructor(props: { manager: ChartEditorManager }) {
constructor(props: { manager: Manager }) {
this.manager = props.manager
this.previewMode =
localStorage.getItem("editorPreviewMode") === "mobile"
Expand All @@ -145,16 +180,56 @@ export class ChartEditor {
() => this.grapher.isReady,
() => (this.savedPatchConfig = this.patchConfig)
)

reaction(
() => this.parentIndicatorId,
async () => {
if (!this.parentIndicatorId) {
this.parentConfig = {}
console.log(
"no parent",
this.parentIndicatorId,
this.parentConfig
)
return
}
if (this.cachedParentConfigs.has(this.parentIndicatorId)) {
this.parentConfig = this.cachedParentConfigs.get(
this.parentIndicatorId
)!
console.log(
"cached parent",
this.parentIndicatorId,
this.parentConfig
)
return
}
this.parentConfig = await this.manager.admin.getJSON(
`/api/variables/mergedGrapherConfig/${this.parentIndicatorId}.json`
)
this.cachedParentConfigs.set(
this.parentIndicatorId,
this.parentConfig
)
console.log(
"new parent",
this.parentIndicatorId,
this.parentConfig
)
}
)
}

@computed get fullConfig(): GrapherInterface {
return this.grapher.object
}

@computed get parentIndicatorId(): number | undefined {
return getParentIndicatorIdFromChartConfig(this.fullConfig)
}

@computed get patchConfig(): GrapherInterface {
const { baseGrapherConfig } = this.manager
if (!baseGrapherConfig) return this.fullConfig
return diffGrapherConfigs(this.fullConfig, baseGrapherConfig)
return diffGrapherConfigs(this.fullConfig, this.parentConfig)
}

@computed get isModified(): boolean {
Expand All @@ -168,10 +243,29 @@ export class ChartEditor {
return this.manager.grapher
}

@computed get database() {
return this.manager.database
@computed get features(): EditorFeatures {
return new EditorFeatures(this)
}

abstract get isNewGrapher(): boolean
abstract get availableTabs(): EditorTab[]

abstract saveGrapher(props?: { onError?: () => void }): Promise<void>
}

export interface ChartEditorManager extends AbstractChartEditorManager {
logs: Log[]
references: References | undefined
redirects: ChartRedirect[]
pageviews?: RawPageview
allTopics: Topic[]
}

export class ChartEditor extends AbstractChartEditor<ChartEditorManager> {
// This gets set when we save a new chart for the first time
// so the page knows to update the url
@observable.ref newChartId?: number

@computed get logs() {
return this.manager.logs
}
Expand All @@ -192,10 +286,6 @@ export class ChartEditor {
return this.manager.allTopics
}

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

@computed get availableTabs(): EditorTab[] {
const tabs: EditorTab[] = ["basic", "data", "text", "customize"]
if (this.grapher.hasMapTab) tabs.push("map")
Expand All @@ -211,23 +301,6 @@ export class ChartEditor {
return this.grapher.id === undefined
}

@computed get features() {
return new EditorFeatures(this)
}

async loadVariableUsageCounts(): Promise<void> {
const data = (await this.manager.admin.getJSON(
`/api/variables.usages.json`
)) as VariableIdUsageRecord[]
const finalData = new Map(
data.map(({ variableId, usageCount }: VariableIdUsageRecord) => [
variableId,
+usageCount,
])
)
runInAction(() => (this.database.variableUsageCounts = finalData))
}

async saveGrapher({
onError,
}: { onError?: () => void } = {}): Promise<void> {
Expand Down Expand Up @@ -312,3 +385,42 @@ export class ChartEditor {
}
}
}

export interface IndicatorChartEditorManager
extends AbstractChartEditorManager {
isNewGrapher: boolean
}

export class IndicatorChartEditor extends AbstractChartEditor<IndicatorChartEditorManager> {
@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("export")
return tabs
}

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

async saveGrapher({
onError,

Check warning on line 409 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

'onError' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 409 in adminSiteClient/ChartEditor.ts

View workflow job for this annotation

GitHub Actions / eslint

'onError' is assigned a value but never used. Allowed unused vars must match /^_/u
}: { onError?: () => void } = {}): Promise<void> {
// TODO(inheritance)
console.log("save indicator chart")
}
}

export function isChartEditorInstance(
editor: AbstractChartEditor
): editor is ChartEditor {
return editor instanceof ChartEditor
}

export function isIndicatorChartEditorInstance(
editor: AbstractChartEditor
): editor is IndicatorChartEditor {
return editor instanceof IndicatorChartEditor
}
7 changes: 7 additions & 0 deletions adminSiteClient/ChartEditorContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { DimensionProperty } from "@ourworldindata/types"

Check warning on line 1 in adminSiteClient/ChartEditorContext.ts

View workflow job for this annotation

GitHub Actions / eslint

'DimensionProperty' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 1 in adminSiteClient/ChartEditorContext.ts

View workflow job for this annotation

GitHub Actions / eslint

'DimensionProperty' is defined but never used. Allowed unused vars must match /^_/u
import React from "react"

export const ChartEditorContext = React.createContext({
errorMessages: {},
errorMessagesForDimensions: {},
})
Loading

0 comments on commit ca24134

Please sign in to comment.