8000 API importer for Notion by hmacr · Pull Request #8710 · outline/outline · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
10000

API importer for Notion #8710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Mar 23, 2025
Merged

API importer for Notion #8710

merged 54 commits into from
Mar 23, 2025

Conversation

hmacr
Copy link
Collaborator
@hmacr hmacr commented Mar 17, 2025

This PR enables importing Notion documents using an API integration.

Pending tasks

  • JSDoc.
  • Restrict imports to "one" active per team.
  • Cleanup task for stuck imports.
  • Cleanup task for completed import_tasks.
  • Add a cancel option in UI.
  • Tests for APIs.

Note: bigger PR, but not as big as it seems to be.. ~9k lines are related to tests.

hmacr and others added 18 commits March 7, 2025 02:15
* tom/misc-fixes (#8650)

* Revert "chore: Upgrade `path-to-regexp` (#8636)" (#8652)

This reverts commit 58c4a48.

* chore(deps): bump axios from 1.7.9 to 1.8.2 (#8653)

Bumps [axios](https://github.com/axios/axios) from 1.7.9 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.7.9...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: Bump dd-trace (#8654)

* chore: Bump @koa/bull-board (#8655)

* create import_tasks table

* rate-limited notion api calls

* completion flow

* ImportsProcessor create event handling

* update imports table migration file

* works till final step

* page title and emoji

* works almost - tiny bugs in persistence

* remove local oauth redirect

* stash

* Passing test suite

* fix: Nested lists

* Add video node

* fix: Missing paragraphs

* Add more mention support

* works with attachments and mentions

* better types

* page type from ui

* wip: database support

* list consolidated imports

* websocket events

* name param in import dialog

* import policy

* handle failures

* abstraction and cleanup

* even more abstraction and typings

* attachments logic to base api-import task

* delete import and policies

* indexes and scopes

* notion database support

* order

* fix: Filter empty rich_text

* Add callout support

* Add column, column_list support (flatted into document until we have support)

* Add link_preview

* Add synced_block (flatten into page)

* refactor persistence

* generate import name

* fix issues; don't use documentCreator as it is lossy based on text

* remove local oauth redirect

* remove commented code

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
@hmacr hmacr requested a review from tommoor March 17, 2025 13:43
@tommoor tommoor requested a review from Copilot March 17, 2025 13:46
Copy link
Contributor
@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces an API importer for Notion, adding both backend API routes and frontend integration components to enable importing Notion pages into the application. Key changes include:

  • New API endpoints and OAuth handling for Notion (plugins/notion/server/api/notion.ts and schema).
  • Frontend integration for initiating and managing Notion imports via a new dialog and import menu.
  • Additional store, WebSocket event listeners, and plugin hook support for managing import-related functionality.

Reviewed Changes

Copilot reviewed 66 out of 66 changed files in this pull request and generated no comments.

Show a summary per file
File Description
plugins/notion/server/api/notion.ts Implements Notion callback and search endpoints.
plugins/notion/client/Imports.tsx Provides the Notion import button and OAuth flow initiation.
plugins/notion/server/env.ts Configures the Notion client credentials.
app/models/Import.ts Introduces a new Import model to track import state.
plugins/notion/client/index.tsx Registers the Notion import UI component via plugin hooks.
app/menus/ImportMenu.tsx Adds a context menu for import items with delete actions.
app/scenes/Settings/components/ImportListItem.tsx Displays import details including status and page counts.
app/stores/ImportsStore.ts Provides a dedicated store for managing Import models.
plugins/notion/server/api/schema.ts Defines schemas for Notion callback and search requests.
plugins/notion/client/components/ImportDialog.tsx Implements a dialog to select pages and set permissions for import.
app/scenes/Settings/Import.ts Updates the import scene to integrate the new Notion import flow.
app/components/WebsocketProvider.ts Adds event listeners for real-time import updates.
app/utils/PluginManager.ts Introduces a new plugin hook for imports.
app/components/Dialogs.tsx Improves dialog onClose handling.
app/hooks/usePolicy.ts Adds an option to force reload policy data.
app/stores/DialogsStore.ts Updates the dialogs store to support onClose callbacks.
app/stores/RootStore.ts Registers the new ImportsStore.
Comments suppressed due to low confidence (1)

plugins/notion/client/components/ImportDialog.tsx:89

  • The dependency array for the handleStartImport callback includes 'name', which is not defined in this scope. Remove 'name' from the dependency array to avoid potential runtime errors.
}], [name, pagesWithPermission, onSubmit]);

export default function usePolicy(entity?: string | Model | null) {
export default function usePolicy(
entity?: string | Model | null,
{ force }: { force: boolean } = { force: false }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems dangerous, would rather not have it 😬

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm we need a way to reload policy when a property in entity changes 🤔
In almost all cases, usePolicy is used in a fresh page / render - so entity remains fresh for the useEffect dep reference checks. But for ImportListItem, the ref remains the same as the realtime progress count keeps getting updated.
Perhaps, force-loading the import model in ImportListItem is better as an one-off case?

import { NotionUtils } from "../shared/NotionUtils";
import env from "./env";

export class NotionOAuth {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this one deserves it's own class… could be static methods on NotionClient perhaps

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, can be included in NotionClient.

@@ -0,0 +1 @@
export const PagePerImportTask = 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth a new file here

@IsNumeric
@Default(0)
@Column(DataType.INTEGER)
pageCount: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more generic term here thinking this will be used for more than just Notion.

progress?
completedCount?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking pageCount is generic enough? In the end, a document or a page from an external source will be imported? 🤔

documentCount maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentCount is better - I would rather use our language than notion's

import { allow } from "./cancan";
import { and, isTeamAdmin, isTeamMutable } from "./utils";

allow(User, ["createImport", "readImport"], Team, (actor, team) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
allow(User, ["createImport", "readImport"], Team, (actor, team) =>
allow(User, ["createImport", "listImports"], Team, (actor, team) =>

This would be more typical

public async perform(event: ImportEvent) {
const importModel = await Import.findByPk<Import<T>>(event.modelId, {
rejectOnEmpty: true,
paranoid: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably query with an update lock

authorize(user, "readImport", user.team);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const where: WhereOptions<Import<any>> = service
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to include teamId in query here

const importModel = await Import.findByPk(id, {
rejectOnEmpty: true,
transaction,
lock: transaction.LOCK.UPDATE,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for update lock or transaction here as we're only querying


authorize(user, "createImport", user.team);

const pendingImport = await Import.findOne({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nbd but could just use Import.count here


const attachment = await Attachment.findByPk(item.attachmentId, {
rejectOnEmpty: true,
include: [{ association: "user" }],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear why user is needed here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, not needed here.

const isRootDocument =
!parentExternalId || !!importInput[parentExternalId];

await Document.createWithCtx(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On par with what's happening in existing file-based import flow, i.e. publish documents.create event for each document.

I noticed that this leads to flood of requests from the app when the import is complete. I really think we should suppress this event, and retain just the collections.create events.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing you'll see is that a lot of processors suppress handling when the event is from an import, e.g.

event.data.source === "import"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I was aware of it - current impl sets the data source.
But I didn't debug enough to find what was causing these additional requests, looks like WebsocketsProcessor needs some updating (missing apiImportId check) - will change it to check for event.data.source === "import"

if (event.name === "documents.create" && document.importId) {
return;
}

@hmacr
Copy link
Collaborator Author
hmacr commented Mar 18, 2025

All comments are addressed, pushed a couple of improvements - lmk if something needs changing.

Meanwhile, I'll work on adding a "cancel" option and some tests for APIs & tasks.

@tommoor tommoor merged commit 6e98568 into main Mar 23, 2025
12 checks passed
@hmacr hmacr deleted the notion-importer branch March 27, 2025 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0