Skip to content

Commit

Permalink
🎉 add topic tags input to di creation dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Feb 18, 2025
1 parent 01c010a commit 55f09da
Showing 1 changed file with 144 additions and 10 deletions.
154 changes: 144 additions & 10 deletions adminSiteClient/CreateDataInsightModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import {
FormItemProps,
} from "antd"
import { ValidateStatus } from "antd/es/form/FormItem"
import { useCallback, useContext, useEffect, useMemo, useState } from "react"
import {
Fragment,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react"
import cx from "classnames"
import {
fetchFigmaProvidedImageUrl,
Expand All @@ -29,31 +36,45 @@ import { AdminAppContext } from "./AdminAppContext"
import { GRAPHER_DYNAMIC_THUMBNAIL_URL } from "../settings/clientSettings"
import { LoadingImage } from "./ReuploadImageForDataInsightModal"
import { ApiChartViewOverview } from "../adminShared/AdminTypes"
import { downloadImage, isEmpty, RequiredBy } from "@ourworldindata/utils"
import {
downloadImage,
isEmpty,
MinimalTag,
MinimalTagWithIsTopic,
RequiredBy,
} from "@ourworldindata/utils"
import { match } from "ts-pattern"

const DEFAULT_RUNNING_MESSAGE: Record<Task, string> = {
createDI: "Creating data insight...",
uploadImage: "Uploading image...",
loadFigmaImage: "Loading Figma image...",
suggestAltText: "Suggesting alt text...",
setTopicTags: "Setting topic tags...",
} as const

const DEFAULT_SUCCESS_MESSAGE: Record<Task, string> = {
createDI: "Data insight created successfully",
uploadImage: "Image uploaded successfully",
loadFigmaImage: "Figma image loaded successfully",
suggestAltText: "Alt text suggested successfully",
setTopicTags: "Topic tags assigned",
} as const

const DEFAULT_ERROR_MESSAGE: Record<Task, string> = {
createDI: "Data insight creation failed",
uploadImage: "Uploading image failed",
loadFigmaImage: "Loading Figma image failed",
suggestAltText: "Suggesting alt text failed",
setTopicTags: "Setting topic tags failed",
} as const

type Task = "createDI" | "uploadImage" | "loadFigmaImage" | "suggestAltText"
type Task =
| "createDI"
| "uploadImage"
| "loadFigmaImage"
| "suggestAltText"
| "setTopicTags"

type Progress =
| { status: "idle" }
Expand All @@ -63,14 +84,19 @@ type Progress =
type FormFieldName =
| "title"
| "authors"
| "topicTagIds"
| "grapherUrl"
| "narrativeChart"
| "figmaUrl"
| "imageFilename"
| "imageAltText"
type ImageFormFieldName = "imageFilename" | "imageAltText"

type FormData = Partial<Record<FormFieldName, string>>
type FormData = Partial<
Omit<Record<FormFieldName, string>, "topicTagIds"> & {
topicTagIds?: number[]
}
>
type FormDataWithTitle = RequiredBy<FormData, "title">
type FormDataWithImageFilename = RequiredBy<FormData, "imageFilename">

Expand Down Expand Up @@ -125,12 +151,16 @@ export function CreateDataInsightModal(props: {
"createDI",
"uploadImage",
"loadFigmaImage",
"suggestAltText"
"suggestAltText",
"setTopicTags"
)

// loaded from Figma if a Figma URL is provided
const [figmaImageUrl, setFigmaImageUrl] = useState<string | undefined>()

// used for autocompletion
const [allTopicTags, setAllTopicTags] = useState<MinimalTag[]>([])

// used for autocompletion
const [allNarrativeCharts, setAllNarrativeCharts] = useState<
NarrativeChart[]
Expand All @@ -145,6 +175,10 @@ export function CreateDataInsightModal(props: {
? allNarrativeChartsMap.get(formData.narrativeChart)
: props.narrativeChart

const topicTags = allTopicTags.filter((tag) =>
formData.topicTagIds?.includes(tag.id)
)

const getDataInsightImageUrl = (args?: {
cache: boolean
}): { source: ImageSource; url: string } | undefined => {
Expand Down Expand Up @@ -257,10 +291,25 @@ export function CreateDataInsightModal(props: {
}

// Create the data insight Gdoc
const response = await createDataInsight({ formData })
updateProgress("createDI", response)
const createResponse = await createDataInsight({ formData })
updateProgress("createDI", createResponse)

props.onFinish?.(response)
// Set topic tags if given
const topicTagIds = formData.topicTagIds ?? []
if (createResponse.success && topicTagIds.length > 0) {
setProgress("setTopicTags", "running")
try {
const response = await setTags({
gdocId: createResponse.gdocId,
tagIds: topicTagIds,
})
updateProgress("setTopicTags", response)
} catch {
setProgress("setTopicTags", "failure")
}
}

props.onFinish?.(createResponse)
}

const uploadImage = async ({
Expand Down Expand Up @@ -289,7 +338,7 @@ export function CreateDataInsightModal(props: {
return response
}

const createDataInsight = async ({
const createDataInsight = ({
formData,
}: {
formData: FormDataWithTitle
Expand All @@ -310,6 +359,20 @@ export function CreateDataInsightModal(props: {
)
}

const setTags = ({
gdocId,
tagIds,
}: {
gdocId: string
tagIds: number[]
}): Promise<{ success: true }> => {
return admin.requestJSON(
`/api/gdocs/${gdocId}/setTags`,
{ tagIds },
"POST"
)
}

const fetchFigmaImage = async (figmaUrl: string) => {
setProgress("loadFigmaImage", "running")
try {
Expand Down Expand Up @@ -372,6 +435,20 @@ export function CreateDataInsightModal(props: {
})
}, [admin, hasNarrativeChartField])

const hasTopicTagsField = shouldShowField("topicTagIds")
useEffect(() => {
// no need to load topic tags if the the field is hidden
// since they're used for autocompletion
if (!hasTopicTagsField) return

const fetchTags = () =>
admin.getJSON<{ tags: MinimalTagWithIsTopic[] }>("/api/tags.json")

void fetchTags().then((result) =>
setAllTopicTags(result.tags.filter((tag) => tag.isTopic))
)
}, [admin, hasTopicTagsField])

const showFeedbackBox = ({
formData,
progress,
Expand Down Expand Up @@ -443,6 +520,11 @@ export function CreateDataInsightModal(props: {
show={shouldShowField("authors")}
/>

<TopicTagsSelect
allTopicTags={allTopicTags}
show={shouldShowField("topicTagIds")}
/>

{(shouldShowField("narrativeChart") ||
shouldShowField("grapherUrl")) && (
<Flex gap="middle">
Expand Down Expand Up @@ -598,6 +680,10 @@ export function CreateDataInsightModal(props: {
formData={formData}
progress={progress.createDI}
/>
<TopicTagsFeedback
topicTags={topicTags}
progress={progress.setTopicTags}
/>
</ul>
</div>
)}
Expand Down Expand Up @@ -697,7 +783,31 @@ function NarrativeChartSelect({
label: name,
}))}
onSelect={handleSelect}
allowClear={true}
allowClear
/>
</FormField>
)
}

function TopicTagsSelect({
allTopicTags,
show,
}: {
allTopicTags: MinimalTag[]
show?: boolean
}) {
if (!show) return null
return (
<FormField label="Topic tags" name="topicTagIds">
<Select
placeholder="Select topic tags..."
mode="multiple"
showSearch
options={allTopicTags.map(({ id, name }) => ({
value: id,
label: name,
}))}
allowClear
/>
</FormField>
)
Expand Down Expand Up @@ -790,6 +900,30 @@ function DataInsightCreationFeedback({
)
}

function TopicTagsFeedback({
topicTags,
progress,
}: {
topicTags: MinimalTag[]
progress: Progress
}) {
if (topicTags.length === 0) return null
return (
<li>
<span>
Tagging the newly created data insight, with{" "}
{topicTags.map((tag, index) => (
<Fragment key={tag.id}>
<i>{tag.name}</i>
{index < topicTags.length - 1 && " and "}
</Fragment>
))}
</span>
<FeedbackTag progress={progress} />
</li>
)
}

function FeedbackTag({ progress }: { progress: Progress }) {
if (progress.status === "idle") return null

Expand Down

0 comments on commit 55f09da

Please sign in to comment.