Skip to content

Commit

Permalink
Merge pull request #3012 from owid/posts-admin-shows-slug-successors
Browse files Browse the repository at this point in the history
Posts admin: shows slug successors
  • Loading branch information
danyx23 authored Dec 20, 2023
2 parents 95b3b61 + 5272548 commit 249d297
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
"[sql]": {
"editor.defaultFormatter": "inferrinizzard.prettier-sql-vscode"
}
}
}
67 changes: 55 additions & 12 deletions adminSiteClient/PostsIndexPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
buildSearchWordsFromSearchString,
filterFunctionForSearchWords,
SearchWord,
uniq,
} from "@ourworldindata/utils"
import { AdminLayout } from "./AdminLayout.js"
import { SearchField, FieldsRow, Timeago } from "./Forms.js"
Expand All @@ -23,6 +24,11 @@ import {
faRecycle,
} from "@fortawesome/free-solid-svg-icons"

interface GDocSlugSuccessor {
id: string
published: boolean
}

interface PostIndexMeta {
id: number
title: string
Expand All @@ -31,12 +37,15 @@ interface PostIndexMeta {
authors: string[]
slug: string
updatedAtInWordpress: string
tags: ChartTagJoin[]
tags: ChartTagJoin[] | null
gdocSuccessorId: string | undefined
gdocSuccessorPublished: boolean
gdocSlugSuccessors: GDocSlugSuccessor[] | null
}

enum GdocStatus {
"MISSING" = "MISSING",
"MISSING_NO_SLUG_SUCCESSOR" = "MISSING_NO_SLUG_SUCCESSOR",
"MISSING_WITH_SLUG_SUCCESSOR" = "MISSING_WITH_SLUG_SUCCESSOR",
"CONVERTING" = "CONVERTING",
"CONVERTED" = "CONVERTED",
}
Expand All @@ -51,13 +60,17 @@ class PostRow extends React.Component<PostRowProps> {
static contextType = AdminAppContext
context!: AdminAppContextType

@observable private postGdocStatus: GdocStatus = GdocStatus.MISSING
@observable private postGdocStatus: GdocStatus =
GdocStatus.MISSING_NO_SLUG_SUCCESSOR

constructor(props: PostRowProps) {
super(props)
this.postGdocStatus = props.post.gdocSuccessorId
? GdocStatus.CONVERTED
: GdocStatus.MISSING
: props.post.gdocSlugSuccessors &&
props.post.gdocSlugSuccessors.length > 0
? GdocStatus.MISSING_WITH_SLUG_SUCCESSOR
: GdocStatus.MISSING_NO_SLUG_SUCCESSOR
}

async saveTags(tags: ChartTagJoin[]) {
Expand Down Expand Up @@ -111,7 +124,11 @@ class PostRow extends React.Component<PostRowProps> {
{},
"POST"
)
this.postGdocStatus = GdocStatus.MISSING
this.postGdocStatus =
this.props.post.gdocSlugSuccessors &&
this.props.post.gdocSlugSuccessors.length > 0
? GdocStatus.MISSING_WITH_SLUG_SUCCESSOR
: GdocStatus.MISSING_NO_SLUG_SUCCESSOR
this.props.post.gdocSuccessorId = undefined
}
}
Expand All @@ -120,28 +137,54 @@ class PostRow extends React.Component<PostRowProps> {
const { post, availableTags } = this.props
const { postGdocStatus } = this
const gdocElement = match(postGdocStatus)
.with(GdocStatus.MISSING, () => (
.with(GdocStatus.MISSING_NO_SLUG_SUCCESSOR, () => (
<button
onClick={async () => await this.onConvertGdoc()}
className="btn btn-primary"
className="btn btn-primary btn-sm"
>
Create GDoc
</button>
))
.with(GdocStatus.MISSING_WITH_SLUG_SUCCESSOR, () => (
<>
{uniq(post.gdocSlugSuccessors).map((gdocSlugSuccessor) => (
<a
key={gdocSlugSuccessor.id}
href={`${ADMIN_BASE_URL}/admin/gdocs/${gdocSlugSuccessor.id}/preview`}
className="btn btn-primary btn-sm"
title="Preview GDoc with same slug"
>
<>
<FontAwesomeIcon icon={faEye} /> Preview
{gdocSlugSuccessor.published ? (
<span title="Published"></span>
) : (
<></>
)}
</>
</a>
))}
</>
))
.with(GdocStatus.CONVERTING, () => <span>Converting...</span>)
.with(GdocStatus.CONVERTED, () => (
<>
<a
href={`${ADMIN_BASE_URL}/admin/gdocs/${post.gdocSuccessorId}/preview`}
className="btn btn-primary"
className="btn btn-primary btn-sm button-with-margin"
>
<>
<FontAwesomeIcon icon={faEye} /> Preview
{post.gdocSuccessorPublished ? (
<span title="Published"></span>
) : (
<></>
)}
</>
</a>
<button
onClick={this.onRecreateGdoc}
className="btn btn-primary alert-danger"
className="btn btn-primary alert-danger btn-sm button-with-margin"
>
<FontAwesomeIcon
icon={faRecycle}
Expand All @@ -152,7 +195,7 @@ class PostRow extends React.Component<PostRowProps> {
</button>
<button
onClick={this.onUnlinkGdoc}
className="btn btn-primary alert-danger"
className="btn btn-primary alert-danger btn-sm button-with-margin"
>
<FontAwesomeIcon
icon={faChainBroken}
Expand All @@ -172,7 +215,7 @@ class PostRow extends React.Component<PostRowProps> {
<td>{post.slug}</td>
<td style={{ minWidth: "380px" }}>
<EditableTags
tags={post.tags}
tags={post.tags ?? []}
suggestions={availableTags}
onSave={this.onSaveTags}
/>
Expand Down Expand Up @@ -223,7 +266,7 @@ export class PostsIndexPage extends React.Component {

@computed get postsToShow(): PostIndexMeta[] {
const { searchWords, posts } = this
if (searchWords.length > 0) {
if (posts.length > 0 && searchWords.length > 0) {
const filterFn = filterFunctionForSearchWords(
searchWords,
(post: PostIndexMeta) => [
Expand Down
6 changes: 6 additions & 0 deletions adminSiteClient/admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1357,3 +1357,9 @@ main:not(.ChartEditorPage):not(.GdocsEditPage) {
z-index: 0;
}
}

.PostsIndexPage {
.button-with-margin {
margin: 1px;
}
}
74 changes: 48 additions & 26 deletions adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
DimensionProperty,
TaggableType,
ChartTagJoin,
sortBy,
} from "@ourworldindata/utils"
import {
GrapherInterface,
Expand Down Expand Up @@ -79,7 +80,6 @@ import { ExplorerAdminServer } from "../explorerAdminServer/ExplorerAdminServer.
import {
postsTable,
setTagsForPost,
select,
getTagsByPostId,
} from "../db/model/Post.js"
import {
Expand Down Expand Up @@ -2303,32 +2303,54 @@ apiRouter.delete("/redirects/:id", async (req: Request, res: Response) => {
})

apiRouter.get("/posts.json", async (req) => {
const rows = await select(
"id",
"title",
"type",
"slug",
"status",
"updated_at_in_wordpress",
"gdocSuccessorId"
).from(
db
.knexInstance()
.from(postsTable)
.orderBy("updated_at_in_wordpress", "desc")
const raw_rows = await db.queryMysql(
`-- sql
with posts_tags_aggregated as (
select post_id, if(count(tags.id) = 0, json_array(), json_arrayagg(json_object("id", tags.id, "name", tags.name))) as tags
from post_tags
left join tags on tags.id = post_tags.tag_id
group by post_id
), post_gdoc_slug_successors as (
select posts.id, if (count(gdocSlugSuccessor.id) = 0, json_array(), json_arrayagg(json_object("id", gdocSlugSuccessor.id, "published", gdocSlugSuccessor.published ))) as gdocSlugSuccessors
from posts
left join posts_gdocs gdocSlugSuccessor on gdocSlugSuccessor.slug = posts.slug
group by posts.id
)
select
posts.id as id,
posts.title as title,
posts.type as type,
posts.slug as slug,
status,
updated_at_in_wordpress,
posts.authors, -- authors is a json array of objects with name and order
posts_tags_aggregated.tags as tags,
gdocSuccessorId,
gdocSuccessor.published as isGdocSuccessorPublished,
-- posts can either have explict successors via the gdocSuccessorId column
-- or implicit successors if a gdoc has been created that uses the same slug
-- as a Wp post (the gdoc one wins once it is published)
post_gdoc_slug_successors.gdocSlugSuccessors as gdocSlugSuccessors
from posts
left join post_gdoc_slug_successors on post_gdoc_slug_successors.id = posts.id
left join posts_gdocs gdocSuccessor on gdocSuccessor.id = posts.gdocSuccessorId
left join posts_tags_aggregated on posts_tags_aggregated.post_id = posts.id
order by updated_at_in_wordpress desc`,
[]
)

const tagsByPostId = await getTagsByPostId()

const authorship = await wpdb.getAuthorship()

for (const post of rows) {
const postAsAny = post as any
postAsAny.authors = authorship.get(post.id) || []
postAsAny.tags = tagsByPostId.get(post.id) || []
}

return { posts: rows.map((r) => camelCaseProperties(r)) }
const rows = raw_rows.map((row: any) => ({
...row,
tags: JSON.parse(row.tags),
isGdocSuccessorPublished: !!row.isGdocSuccessorPublished,
gdocSlugSuccessors: JSON.parse(row.gdocSlugSuccessors),
authors: row.authors
? sortBy(JSON.parse(row.authors), "order").map(
(author) => author.author
)
: [],
}))

return { posts: rows }
})

apiRouter.post(
Expand Down

0 comments on commit 249d297

Please sign in to comment.