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
18 changes: 13 additions & 5 deletions plugins/notion/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ import { FieldMapping } from "./FieldMapping"
import { NoTableAccess } from "./NoAccess"
import { Progress } from "./Progress"
import { SelectDataSource } from "./SelectDataSource"
import { showAccessErrorUI, showFieldMappingUI, showLoginUI, showProgressUI } from "./ui"
import {
closePluginAfterSyncWithErrors,
showAccessErrorUI,
showFieldMappingUI,
showLoginUI,
showProgressUI,
} from "./ui"

interface AppProps {
collection: ManagedCollection
Expand Down Expand Up @@ -59,7 +65,7 @@ export function App({
void showProgressUI()

try {
const { didSync } = await syncExistingCollection(
const sync = await syncExistingCollection(
collection,
previousDatabaseId,
previousSlugFieldId,
Expand All @@ -70,12 +76,14 @@ export function App({
setProgress
)

if (didSync) {
if (!sync.didSync) {
setIsSyncMode(false)
} else if (sync.result.status === "completed-with-errors") {
closePluginAfterSyncWithErrors(sync.result)
} else {
framer.closePlugin("Synchronization successful", {
variant: "success",
})
} else {
setIsSyncMode(false)
}
} catch (error) {
if (error instanceof FramerPluginClosedError) return
Expand Down
10 changes: 8 additions & 2 deletions plugins/notion/src/FieldMapping.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
syncCollection,
} from "./data"
import { Progress } from "./Progress"
import { closePluginAfterSyncWithErrors } from "./ui"
import { assert, syncMethods } from "./utils"

const labelByFieldTypeOption: Record<VirtualFieldType, string> = {
Expand Down Expand Up @@ -271,7 +272,7 @@ export function FieldMapping({
}

await collection.setFields(fieldsToSync)
await syncCollection(
const result = await syncCollection(
collection,
dataSource,
fieldsToSync,
Expand All @@ -281,7 +282,12 @@ export function FieldMapping({
existingFields,
setSyncProgress
)
framer.closePlugin("Synchronization successful", { variant: "success" })

if (result.status === "completed-with-errors") {
closePluginAfterSyncWithErrors(result)
} else {
framer.closePlugin("Synchronization successful", { variant: "success" })
}
} catch (error) {
if (error instanceof FramerPluginClosedError) return
console.error(error)
Expand Down
35 changes: 30 additions & 5 deletions plugins/notion/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ export interface SyncError {
error: unknown
}

export type SyncResult =
| { status: "completed" }
| {
status: "completed-with-errors"
succeeded: number
failed: number
}

export async function syncCollection(
collection: ManagedCollection,
dataSource: DataSource,
Expand All @@ -123,7 +131,7 @@ export async function syncCollection(
lastSynced: string | null,
existingFields?: readonly ManagedCollectionFieldInput[],
onProgress?: (progress: SyncProgress) => void
) {
): Promise<SyncResult> {
const fieldsById = new Map(fields.map(field => [field.id, field]))
const contentFieldEnabled = fieldsById.has(pageContentProperty.id)
const reportProgress = (p: { current: number; total: number; hasFinishedLoading: boolean }) =>
Expand All @@ -144,6 +152,10 @@ export async function syncCollection(

const seenItemIds = new Set<string>()

// Save a conservative checkpoint from before reading Notion. If an item is edited
// while this sync is running, its edit time will be newer than this checkpoint and
// the item will be picked up by the next sync.
const syncStartedAt = new Date().toISOString()
const databaseItems = await getDatabaseItems(dataSource.database, reportProgress)

// Validate slugs before fetching page content
Expand Down Expand Up @@ -377,10 +389,23 @@ export async function syncCollection(
ignoredFieldIds.size > 0 ? JSON.stringify(Array.from(ignoredFieldIds)) : null
),
collection.setPluginData(PLUGIN_KEYS.DATABASE_ID, dataSource.database.id),
collection.setPluginData(PLUGIN_KEYS.LAST_SYNCED, new Date().toISOString()),
collection.setPluginData(PLUGIN_KEYS.SLUG_FIELD_ID, slugField.id),
collection.setPluginData(PLUGIN_KEYS.DATABASE_NAME, richTextToPlainText(dataSource.database.title)),
// The Notion plugin uses `LAST_SYNCED` to only sync items that have changed since the last sync.
// We don’t want to update this value if some items failed to sync. This gives these items
// a chance to retry on the next sync.
syncErrors.length === 0 ? collection.setPluginData(PLUGIN_KEYS.LAST_SYNCED, syncStartedAt) : Promise.resolve(),
])

if (syncErrors.length > 0) {
return {
status: "completed-with-errors",
succeeded: items.length,
failed: syncErrors.length,
}
}

return { status: "completed" }
}

const IgnoredFieldIdsSchema = v.array(v.string())
Expand Down Expand Up @@ -415,7 +440,7 @@ export async function syncExistingCollection(
previousDatabaseName: string | null,
databaseIdMap: DatabaseIdMap,
onProgress?: (progress: SyncProgress) => void
): Promise<{ didSync: boolean }> {
): Promise<{ didSync: false } | { didSync: true; result: SyncResult }> {
if (
!shouldSyncExistingCollection({ previousSlugFieldId, previousDatabaseId }) ||
!previousSlugFieldId ||
Expand Down Expand Up @@ -449,7 +474,7 @@ export async function syncExistingCollection(
existingFields.some(existingField => existingField.id === field.id) && !ignoredFieldIds.has(field.id)
)

await syncCollection(
const result = await syncCollection(
collection,
dataSource,
fieldsToSync,
Expand All @@ -459,7 +484,7 @@ export async function syncExistingCollection(
existingFields,
onProgress
)
return { didSync: true }
return { didSync: true, result }
} catch (error) {
console.error(error)
framer.notify(
Expand Down
10 changes: 10 additions & 0 deletions plugins/notion/src/ui.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { framer } from "framer-plugin"
import type { SyncResult } from "./data"

type SyncResultWithErrors = Extract<SyncResult, { status: "completed-with-errors" }>

export function closePluginAfterSyncWithErrors(result: SyncResultWithErrors) {
const pluralSuffix = result.failed === 1 ? "" : "s"
framer.closePlugin(`Failed to sync ${result.failed} item${pluralSuffix}. Please try again.`, {
variant: "error",
})
}

export async function showAccessErrorUI() {
await framer.showUI({
Expand Down
Loading