mirror of
https://github.com/outline/outline.git
synced 2026-02-22 12:28:49 -06:00
Fix: Handle Notion database not found errors gracefully (#8860)
* Fix: Handle Notion database not found errors gracefully * Fix: Use Logger.warn instead of console.log in Notion import task * Applied automatic fixes * Touch to trigger actions * Fix: Implement additional improvements for Notion import error handling * Applied automatic fixes * Change to trigger CI * Fix TypeScript error: Add type assertion for filtered parsedPages --------- Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com> Co-authored-by: Tom Moor <tom@getoutline.com> Co-authored-by: Codegen <codegen@example.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { APIResponseError, APIErrorCode } from "@notionhq/client";
|
||||
import { ImportTaskInput, ImportTaskOutput } from "@shared/schema";
|
||||
import { IntegrationService, ProsemirrorDoc } from "@shared/types";
|
||||
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import { Integration } from "@server/models";
|
||||
import ImportTask from "@server/models/ImportTask";
|
||||
import APIImportTask, {
|
||||
@@ -39,7 +41,10 @@ export default class NotionAPIImportTask extends APIImportTask<IntegrationServic
|
||||
importTask.input.map(async (item) => this.processPage({ item, client }))
|
||||
);
|
||||
|
||||
const taskOutput: ImportTaskOutput = parsedPages.map((parsedPage) => ({
|
||||
// Filter out any null results (from pages/databases that couldn't be accessed)
|
||||
const validParsedPages = parsedPages.filter(Boolean) as ParsePageOutput[];
|
||||
|
||||
const taskOutput: ImportTaskOutput = validParsedPages.map((parsedPage) => ({
|
||||
externalId: parsedPage.externalId,
|
||||
title: parsedPage.title,
|
||||
emoji: parsedPage.emoji,
|
||||
@@ -50,7 +55,7 @@ export default class NotionAPIImportTask extends APIImportTask<IntegrationServic
|
||||
}));
|
||||
|
||||
const childTasksInput: ImportTaskInput<IntegrationService.Notion> =
|
||||
parsedPages.flatMap((parsedPage) =>
|
||||
validParsedPages.flatMap((parsedPage) =>
|
||||
parsedPage.children.map((childPage) => ({
|
||||
type: childPage.type,
|
||||
externalId: childPage.externalId,
|
||||
@@ -88,36 +93,55 @@ export default class NotionAPIImportTask extends APIImportTask<IntegrationServic
|
||||
}: {
|
||||
item: ImportTaskInput<IntegrationService.Notion>[number];
|
||||
client: NotionClient;
|
||||
}): Promise<ParsePageOutput> {
|
||||
}): Promise<ParsePageOutput | null> {
|
||||
const collectionExternalId = item.collectionExternalId ?? item.externalId;
|
||||
|
||||
// Convert Notion database to an empty page with "pages in database" as its children.
|
||||
if (item.type === PageType.Database) {
|
||||
const { pages, ...databaseInfo } = await client.fetchDatabase(
|
||||
item.externalId
|
||||
);
|
||||
try {
|
||||
// Convert Notion database to an empty page with "pages in database" as its children.
|
||||
if (item.type === PageType.Database) {
|
||||
const { pages, ...databaseInfo } = await client.fetchDatabase(
|
||||
item.externalId
|
||||
);
|
||||
|
||||
return {
|
||||
...databaseInfo,
|
||||
externalId: item.externalId,
|
||||
content: ProsemirrorHelper.getEmptyDocument() as ProsemirrorDoc,
|
||||
collectionExternalId,
|
||||
children: pages.map((page) => ({
|
||||
type: page.type,
|
||||
externalId: page.id,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
const { blocks, ...pageInfo } = await client.fetchPage(item.externalId);
|
||||
|
||||
return {
|
||||
...databaseInfo,
|
||||
...pageInfo,
|
||||
externalId: item.externalId,
|
||||
content: ProsemirrorHelper.getEmptyDocument() as ProsemirrorDoc,
|
||||
content: NotionConverter.page({ children: blocks } as NotionPage),
|
||||
collectionExternalId,
|
||||
children: pages.map((page) => ({
|
||||
type: page.type,
|
||||
externalId: page.id,
|
||||
})),
|
||||
children: this.parseChildPages(blocks),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof APIResponseError) {
|
||||
// Skip this page/database if it's not found or not accessible
|
||||
if (
|
||||
error.code === APIErrorCode.ObjectNotFound ||
|
||||
error.code === APIErrorCode.Unauthorized
|
||||
) {
|
||||
Logger.warn(
|
||||
`Skipping Notion ${
|
||||
item.type === PageType.Database ? "database" : "page"
|
||||
} ${item.externalId} - Error code: ${error.code} - ${error.message}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Re-throw other errors to be handled by the parent try/catch
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { blocks, ...pageInfo } = await client.fetchPage(item.externalId);
|
||||
|
||||
return {
|
||||
...pageInfo,
|
||||
externalId: item.externalId,
|
||||
content: NotionConverter.page({ children: blocks } as NotionPage),
|
||||
collectionExternalId,
|
||||
children: this.parseChildPages(blocks),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -304,6 +304,15 @@ export default abstract class ImportsProcessor<
|
||||
|
||||
const output = outputMap[externalId];
|
||||
|
||||
// Skip this item if it has no output (likely due to an error during processing)
|
||||
if (!output) {
|
||||
Logger.debug(
|
||||
"processor",
|
||||
`Skipping item with no output: ${externalId}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const collectionItem = importInput[externalId];
|
||||
|
||||
const attachments = await Attachment.findAll({
|
||||
@@ -444,7 +453,7 @@ export default abstract class ImportsProcessor<
|
||||
importInput: Record<string, ImportInput<any>[number]>;
|
||||
actorId: string;
|
||||
}): ProsemirrorDoc {
|
||||
// special case when the doc content is empty
|
||||
// special case when the doc content is empty.
|
||||
if (!content.content.length) {
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ export default abstract class APIImportTask<
|
||||
await importTask.save({ transaction });
|
||||
|
||||
const associatedImport = importTask.import;
|
||||
associatedImport.documentCount += importTask.input.length;
|
||||
associatedImport.documentCount += taskOutputWithReplacements.length;
|
||||
await associatedImport.saveWithCtx(
|
||||
createContext({
|
||||
user: associatedImport.createdBy,
|
||||
|
||||
Reference in New Issue
Block a user