Skip to content

Commit

Permalink
🔨 (grapher) turn related questions into an object
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Nov 4, 2024
1 parent 520d3e6 commit e9ee616
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 112 deletions.
101 changes: 42 additions & 59 deletions adminSiteClient/EditorTextTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,6 @@ export class EditorTextTab<
}
}

@action.bound onAddRelatedQuestion() {
const { grapher } = this.props.editor
if (!grapher.relatedQuestions) grapher.relatedQuestions = []
grapher.relatedQuestions.push({
text: "",
url: "",
})
}

@action.bound onRemoveRelatedQuestion(idx: number) {
const { grapher } = this.props.editor
if (!grapher.relatedQuestions) grapher.relatedQuestions = []
grapher.relatedQuestions.splice(idx, 1)
}

@action.bound onToggleTitleAnnotationEntity(value: boolean) {
const { grapher } = this.props.editor
grapher.hideAnnotationFieldsInTitle ??= {}
Expand Down Expand Up @@ -86,7 +71,6 @@ export class EditorTextTab<
render() {
const { editor } = this.props
const { grapher, features } = editor
const { relatedQuestions = [] } = grapher

return (
<div className="EditorTextTab">
Expand Down Expand Up @@ -250,50 +234,49 @@ export class EditorTextTab<
</Section>

<Section name="Related">
{relatedQuestions.map(
(question: RelatedQuestionsConfig, idx: number) => (
<div key={idx}>
<TextField
label="Related question"
value={question.text}
onValue={action((value: string) => {
question.text = value
})}
placeholder="e.g. How did countries respond to the pandemic?"
helpText="Short question promoting exploration of related content"
softCharacterLimit={50}
/>
{question.text && (
<TextField
label="URL"
value={question.url}
onValue={action((value: string) => {
question.url = value
})}
placeholder="e.g. https://ourworldindata.org/coronavirus"
helpText="Page or section of a page where the answer to the previous question can be found."
errorMessage={getErrorMessageRelatedQuestionUrl(
question
)}
/>
)}
<Button
onClick={() =>
this.onRemoveRelatedQuestion(idx)
<div>
<TextField
label="Related question"
value={grapher.relatedQuestion?.text}
onValue={action((value: string) => {
if (grapher.relatedQuestion) {
grapher.relatedQuestion.text = value
} else {
grapher.relatedQuestion = {
text: value,
url: "",
}
>
<FontAwesomeIcon icon={faMinus} /> Remove
related question
</Button>
</div>
)
)}
{!relatedQuestions.length && (
<Button onClick={this.onAddRelatedQuestion}>
<FontAwesomeIcon icon={faPlus} /> Add related
question
</Button>
)}
}
})}
placeholder="e.g. How did countries respond to the pandemic?"
helpText="Short question promoting exploration of related content"
softCharacterLimit={50}
/>
{grapher.relatedQuestion?.text && (
<TextField
label="URL"
value={grapher.relatedQuestion.url}
onValue={action((value: string) => {
grapher.relatedQuestion!.url = value
})}
placeholder="e.g. https://ourworldindata.org/coronavirus"
helpText="Page or section of a page where the answer to the previous question can be found."
errorMessage={getErrorMessageRelatedQuestionUrl(
grapher.relatedQuestion
)}
/>
)}
{grapher.relatedQuestion?.text && (
<Button
onClick={() =>
(grapher.relatedQuestion = undefined)
}
>
<FontAwesomeIcon icon={faMinus} /> Remove
related question
</Button>
)}
</div>
</Section>
<Section name="Misc">
<BindString
Expand Down
91 changes: 91 additions & 0 deletions db/migration/1730722779919-TurnRelatedQuestionIntoObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class TurnRelatedQuestionIntoObject1730722779919
implements MigrationInterface
{
private async updateSchema(
queryRunner: QueryRunner,
newSchema: string
): Promise<void> {
await queryRunner.query(
`
-- sql
UPDATE chart_configs cc
SET cc.patch = JSON_SET(cc.patch, '$.$schema', ?),
cc.full = JSON_SET(cc.full, '$.$schema', ?)
`,
[newSchema, newSchema]
)
}

private async turnRelatedQuestionIntoObject(
queryRunner: QueryRunner,
config: "patch" | "full"
): Promise<void> {
await queryRunner.query(
`
-- sql
UPDATE chart_configs
SET
?? = JSON_REMOVE(
JSON_SET(
??,
'$.relatedQuestion',
??->'$.relatedQuestions[0]'
),
'$.relatedQuestions'
)
WHERE ?? ->> '$.relatedQuestions' is not null
`,
[config, config, config, config]
)
}

private async turnRelatedQuestionIntoArray(
queryRunner: QueryRunner,
config: "patch" | "full"
): Promise<void> {
await queryRunner.query(
`
-- sql
UPDATE chart_configs
SET
?? = JSON_REMOVE(
JSON_SET(
??,
'$.relatedQuestions',
JSON_ARRAY(
JSON_OBJECT(
'url', ?? ->> '$.relatedQuestion.url',
'text', ?? ->> '$.relatedQuestion.text'
)
)
),
'$.relatedQuestion'
)
WHERE ?? ->> '$.relatedQuestion' IS NOT NULL
`,
[config, config, config, config, config]
)
}

public async up(queryRunner: QueryRunner): Promise<void> {
await this.turnRelatedQuestionIntoObject(queryRunner, "patch")
await this.turnRelatedQuestionIntoObject(queryRunner, "full")

await this.updateSchema(
queryRunner,
"https://files.ourworldindata.org/schemas/grapher-schema.006.json"
)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await this.turnRelatedQuestionIntoArray(queryRunner, "patch")
await this.turnRelatedQuestionIntoArray(queryRunner, "full")

await this.updateSchema(
queryRunner,
"https://files.ourworldindata.org/schemas/grapher-schema.005.json"
)
}
}
4 changes: 2 additions & 2 deletions devTools/schemaProcessor/columns.json
Original file line number Diff line number Diff line change
Expand Up @@ -617,12 +617,12 @@
},
{
"type": "string",
"pointer": "/relatedQuestions/0/url",
"pointer": "/relatedQuestion/url",
"editor": "textfield"
},
{
"type": "string",
"pointer": "/relatedQuestions/0/text",
"pointer": "/relatedQuestion/text",
"editor": "textfield"
},
{
Expand Down
9 changes: 0 additions & 9 deletions explorer/ExplorerProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,15 +400,6 @@ export class ExplorerProgram extends GridProgram {
// assume config is valid against the latest schema
mergedConfig.$schema = defaultGrapherConfig.$schema

// TODO: can be removed once relatedQuestions is refactored
const { relatedQuestionUrl, relatedQuestionText } =
this.explorerGrapherConfig
if (relatedQuestionUrl && relatedQuestionText) {
mergedConfig.relatedQuestions = [
{ url: relatedQuestionUrl, text: relatedQuestionText },
]
}

return mergedConfig
}

Expand Down
4 changes: 2 additions & 2 deletions explorer/GrapherGrammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,13 @@ export const GrapherGrammar: Grammar<GrapherCellDef> = {
keyword: "relatedQuestionText",
description:
"The text used for the related question (at the very bottom of the chart)",
toGrapherObject: () => ({}), // handled in code (can be done properly once the relatedQuestion field is refactored)
toGrapherObject: (value) => ({ relatedQuestion: { text: value } }),

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testdbcheck

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / commit-default-config

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testcheck

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / bundlewatch

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testdbcheck

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testcheck

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 287 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / bundlewatch

Property 'url' is missing in type '{ text: any; }' but required in type 'RelatedQuestionsConfig'.
},
relatedQuestionUrl: {
...UrlCellDef,
keyword: "relatedQuestionUrl",
description: "The link of the related question text",
toGrapherObject: () => ({}), // handled in code (can be done properly once the relatedQuestion field is refactored)
toGrapherObject: (value) => ({ relatedQuestion: { url: value } }),

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testdbcheck

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / commit-default-config

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testcheck

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / bundlewatch

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testdbcheck

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / testcheck

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.

Check failure on line 293 in explorer/GrapherGrammar.ts

View workflow job for this annotation

GitHub Actions / bundlewatch

Property 'text' is missing in type '{ url: any; }' but required in type 'RelatedQuestionsConfig'.
},
mapTargetTime: {
...IntegerCellDef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export interface CaptionedChartManager
detailRenderers: MarkdownTextWrap[]

// related question
relatedQuestions?: RelatedQuestionsConfig[]
relatedQuestion?: RelatedQuestionsConfig
showRelatedQuestion?: boolean
}

Expand Down Expand Up @@ -271,7 +271,7 @@ export class CaptionedChart extends React.Component<CaptionedChartProps> {
}

private renderRelatedQuestion(): React.ReactElement {
const { relatedQuestions } = this.manager
const { relatedQuestion } = this.manager
return (
<div
className="relatedQuestion"
Expand All @@ -283,12 +283,12 @@ export class CaptionedChart extends React.Component<CaptionedChartProps> {
>
Related:&nbsp;
<a
href={relatedQuestions![0].url}
href={relatedQuestion!.url}
target="_blank"
rel="noopener"
data-track-note="chart_click_related"
>
{relatedQuestions![0].text}
{relatedQuestion!.text}
</a>
<FontAwesomeIcon icon={faExternalLinkAlt} />
</div>
Expand Down
23 changes: 7 additions & 16 deletions packages/@ourworldindata/grapher/src/core/Grapher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ export class Grapher
@observable.ref hideTotalValueLabel?: boolean = undefined
@observable.ref missingDataStrategy?: MissingDataStrategy = undefined
@observable.ref showSelectionOnlyInDataTable?: boolean = undefined
@observable relatedQuestion?: RelatedQuestionsConfig = undefined // todo: Persistables?

@observable.ref xAxis = new AxisConfig(undefined, this)
@observable.ref yAxis = new AxisConfig(undefined, this)
Expand All @@ -426,7 +427,6 @@ export class Grapher
*/
@observable includedEntities?: number[] = undefined
@observable comparisonLines?: ComparisonLineConfig[] = undefined // todo: Persistables?
@observable relatedQuestions?: RelatedQuestionsConfig[] = undefined // todo: Persistables?

@observable.ref annotation?: Annotation = undefined

Expand Down Expand Up @@ -1249,10 +1249,8 @@ export class Grapher

// todo: did this name get botched in a merge?
@computed get hasFatalErrors(): boolean {
const { relatedQuestions = [] } = this
return relatedQuestions.some(
(question) => !!getErrorMessageRelatedQuestionUrl(question)
)
if (!this.relatedQuestion) return false
return !!getErrorMessageRelatedQuestionUrl(this.relatedQuestion)
}

disposers: (() => void)[] = []
Expand Down Expand Up @@ -3055,14 +3053,8 @@ export class Grapher
@observable isShareMenuActive = false

@computed get hasRelatedQuestion(): boolean {
if (
this.hideRelatedQuestion ||
!this.relatedQuestions ||
!this.relatedQuestions.length
)
return false
const question = this.relatedQuestions[0]
return !!question && !!question.text && !!question.url
if (this.hideRelatedQuestion) return false
return !!(this.relatedQuestion?.url && this.relatedQuestion?.text)
}

@computed get isRelatedQuestionTargetDifferentFromCurrentPage(): boolean {
Expand All @@ -3073,8 +3065,7 @@ export class Grapher
// "ourworldindata.org" and yet should still yield a match.
// - Note that this won't work on production previews (where the
// path is /admin/posts/preview/ID)
const { hasRelatedQuestion, relatedQuestions = [] } = this
const relatedQuestion = relatedQuestions[0]
const { hasRelatedQuestion, relatedQuestion } = this
return (
hasRelatedQuestion &&
!!relatedQuestion &&
Expand All @@ -3085,7 +3076,7 @@ export class Grapher

@computed get showRelatedQuestion(): boolean {
return (
!!this.relatedQuestions &&
!!this.relatedQuestion &&
!!this.hasRelatedQuestion &&
!!this.isRelatedQuestionTargetDifferentFromCurrentPage
)
Expand Down
1 change: 0 additions & 1 deletion packages/@ourworldindata/grapher/src/schema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Checklist for breaking changes:
- Rename the authorative url inside the schema file to point to the new version number json
- Write the migrations from the last to the current version of the schema, including the migration of pointing to the URL of the new schema version
- Regenerate the default object from the schema and save it to `defaultGrapherConfig.ts` (see below)
- Write a migration to update the `chart_configs.full` column in the database for all stand-alone charts
- Write a migration to update configs in code (see `migrations/migrations.ts`)
- Update the hardcoded default schema version in ETL

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

import { GrapherInterface } from "@ourworldindata/types"

export const latestSchemaVersion = "005" as const
export const outdatedSchemaVersions = ["001", "002", "003", "004"] as const
export const latestSchemaVersion = "006" as const
export const outdatedSchemaVersions = [
"001",
"002",
"003",
"004",
"005",
] as const

export const defaultGrapherConfig = {
$schema: "https://files.ourworldindata.org/schemas/grapher-schema.005.json",
$schema: "https://files.ourworldindata.org/schemas/grapher-schema.006.json",
map: {
projection: "World",
hideTimeline: false,
Expand Down
Loading

0 comments on commit e9ee616

Please sign in to comment.