Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plugins/hubspot/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { BlogAuthor } from "@hubspot/api-client/lib/codegen/cms/blogs/authors/models/BlogAuthor"
import { BlogPost } from "@hubspot/api-client/lib/codegen/cms/blogs/blog_posts/models/BlogPost"
import { HubDbTableRowV3 } from "@hubspot/api-client/lib/codegen/cms/hubdb/models/HubDbTableRowV3"
import { HubDbTableV3 } from "@hubspot/api-client/lib/codegen/cms/hubdb/models/HubDbTableV3"
Expand Down Expand Up @@ -100,6 +101,7 @@ const API_URL = "https://api.hubapi.com"

const queryKeys = {
blogPosts: (limit: number, properties: string[]) => ["blog-posts", limit, properties] as const,
blogAuthor: (id: string) => ["blog-author", id] as const,
publishedTables: (limit: number) => ["hubdb-tables", limit] as const,
publishedTable: (tableId: string) => ["hubdb-table", tableId] as const,
tableRows: (tableId: string, properties: string[], limit: number) =>
Expand Down Expand Up @@ -177,6 +179,13 @@ export const fetchAllBlogPosts = (limit: number, properties: string[]): Promise<
)
}

export const fetchBlogAuthor = (authorId: string): Promise<BlogAuthor> => {
return cachedFetch(
queryKeys.blogAuthor(authorId),
() => request({ path: `/cms/v3/blogs/authors/${authorId}` }) as Promise<BlogAuthor>
)
}

export const fetchPublishedTables = (limit: number): Promise<CMSPaging<HubDbTableV3>> => {
return cachedFetch(
queryKeys.publishedTables(limit),
Expand Down
38 changes: 30 additions & 8 deletions plugins/hubspot/src/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
type ManagedCollection,
type ManagedCollectionFieldInput,
} from "framer-plugin"
import { fetchAllBlogPosts } from "./api"
import { fetchAllBlogPosts, fetchBlogAuthor } from "./api"
import {
computeFieldSets,
createFieldSetHash,
Expand All @@ -22,6 +22,10 @@ import { assert, isDefined } from "./utils"

export const PLUGIN_INCLUDED_FIELDS_HASH = "hubspotPluginBlogIncludedFieldsHash"

// Keep this ID stable for existing managed collections and their Framer bindings.
const AUTHOR_NAME_FIELD_ID = "authorName"
const BLOG_AUTHOR_ID_FIELD_ID = "blogAuthorId"

export interface SyncBlogMutation {
fields: ManagedCollectionFieldInput[]
includedFieldIds: string[]
Expand All @@ -30,6 +34,7 @@ export interface SyncBlogMutation {
export interface ProcessBlogParams {
fields: ManagedCollectionFieldInput[]
fieldsById: FieldsById
authorNamesById: Map<string, string>
unsyncedItemIds: Set<string>
}

Expand Down Expand Up @@ -166,7 +171,7 @@ function getFieldDataEntryInput(
}
}

function processPost({ post, fieldsById, unsyncedItemIds, status }: ProcessPostParams) {
function processPost({ post, fieldsById, authorNamesById, unsyncedItemIds, status }: ProcessPostParams) {
let slugValue: string | null = null
const fieldData: FieldDataInput = {}

Expand All @@ -184,7 +189,12 @@ function processPost({ post, fieldsById, unsyncedItemIds, status }: ProcessPostP
// Not included in field mapping, skip
if (!field) continue

const fieldDataEntryInput = getFieldDataEntryInput(field, propertyValue, post)
// HubSpot uses authorName for the user who updated the post, not its blog author.
// Retain that value when the associated Blog Author cannot be resolved.
const resolvedAuthorName =
propertyName === AUTHOR_NAME_FIELD_ID ? authorNamesById.get(post.blogAuthorId) : undefined
const value = resolvedAuthorName ?? propertyValue
const fieldDataEntryInput = getFieldDataEntryInput(field, value, post)
if (fieldDataEntryInput) {
fieldData[propertyName] = fieldDataEntryInput
} else {
Expand Down Expand Up @@ -232,15 +242,27 @@ export async function syncBlogs({ fields, includedFieldIds }: SyncBlogMutation)

const fieldsById = new Map(fields.map(field => [field.id, field]))
const unsyncedItemIds = new Set(await collection.getItemIds())
const hasAuthorNameField = fieldsById.has(AUTHOR_NAME_FIELD_ID)
const requestedProperties = new Set([...includedFieldIds, "id", "slug"])

if (hasAuthorNameField) requestedProperties.add(BLOG_AUTHOR_ID_FIELD_ID)

const { results: posts } = await fetchAllBlogPosts(MAX_CMS_ITEMS, Array.from(requestedProperties))

const authorNamesById = new Map<string, string>()
if (hasAuthorNameField) {
const authorIds = Array.from(new Set(posts.map(post => post.blogAuthorId)))
const authors = authorIds.length ? await Promise.all(authorIds.map(fetchBlogAuthor)) : []

for (const author of authors) {
authorNamesById.set(author.id, author.displayName)
}
}

// Always include the blog Id and slug
const { results: posts } = await fetchAllBlogPosts(
MAX_CMS_ITEMS,
Array.from(new Set([...includedFieldIds, "id", "slug"]))
)
const { collectionItems, status } = processBlog(posts, {
fields,
fieldsById,
authorNamesById,
unsyncedItemIds,
})

Expand Down
Loading