Skip to content

feat: add notion as issue provider#2660

Open
janburzinski wants to merge 26 commits into
mainfrom
jan/eng-1660-feat-add-notion-as-a-supported-issue-tracker
Open

feat: add notion as issue provider#2660
janburzinski wants to merge 26 commits into
mainfrom
jan/eng-1660-feat-add-notion-as-a-supported-issue-tracker

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • added notion as an issue provider
  • supports searching linked notion pages/databases
  • fetches notion page as issue context

Screenshot/Recording (if applicable)

https://streamable.com/gmawsc

Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@janburzinski janburzinski changed the title Jan/eng 1660 feat add notion as a supported issue tracker feat: add notion as issue provider Jun 24, 2026
@janburzinski janburzinski marked this pull request as ready for review June 24, 2026 11:42
@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Notion as an issue provider, supporting both an "all-shared" scope (search everything the integration can access) and an explicit "data-sources" scope (specific pages/databases by URL or 32-char ID). It introduces a connection service, an issue provider with list/search/context fetching, a setup form with token-preservation on scope edits, and improved empty-state error display for any provider.

  • Backend (notion-connection-service.ts, notion-issue-provider.ts): credentials are stored encrypted; list/search use mapWithConcurrency (concurrency cap of 4); getIssueContext fetches page properties plus block children up to 300 blocks / 3 levels deep.
  • Frontend: A new NotionSetupForm determines edit-vs-connect mode from hasCredentials (not from the modal's mode prop); IssueSearchEmptyState replaces the old plain error string with provider-aware, action-linked error UI; compactIssueIdentifier truncates long Notion UUIDs in the UI.
  • Shared: IssueProviderType and telemetry event types now derive from ISSUE_PROVIDER_CAPABILITIES instead of a manually maintained union, cleaning up several now-stale hardcoded lists.

Confidence Score: 5/5

Safe to merge — the Notion integration is well-contained, all API calls go through the existing encrypted secrets store and rate-limiting patterns, and the previous review concerns (concurrency, separator, double-sort) are all resolved.

The credential service, issue provider, and UI forms are all thoroughly tested. The only noteworthy inconsistency is that the modal mode prop changes the dialog header but the form independently decides its own edit/connect state from a live query — a cosmetic mismatch in an unlikely edge case, not a functional regression.

integration-setup-modal.tsx — the mode prop / hasCredentials split between the modal header and the form could eventually cause label drift if other providers adopt the edit pattern.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/main/core/notion/notion-connection-service.ts New credential management service; handles URL parsing, token validation via /users/me, and in-memory caching. The ', ' separator (not newline) for stored sourceUrls correctly round-trips through the single-line Input in the edit form.
apps/emdash-desktop/src/main/core/notion/notion-issue-provider.ts Issue provider with all four code paths (all-shared + search, all-shared + list, data-sources + search, data-sources + list). All paths now use mapWithConcurrency(4); redundant double-sort removed; block context capped at 300 blocks / depth 3.
apps/emdash-desktop/src/renderer/features/integrations/integration-setup-modal.tsx Adds mode prop (connect/edit) that changes the modal header, but the prop is not forwarded to the form. NotionSetupForm determines its own edit/connect state independently from hasCredentials, which can produce a title/button label mismatch in edge cases.
apps/emdash-desktop/src/renderer/features/integrations/NotionSetupForm.tsx Setup form that fetches its own configuration state (staleTime:0). Edit mode detected via hasCredentials, not modal mode prop. Loading skeleton disables submit correctly.
apps/emdash-desktop/src/renderer/features/tasks/components/issue-selector/issue-search-empty-state.tsx New component replacing the plain error string; dispatches to parseIssueSearchError for provider-aware title/description/action. Clean implementation.
apps/emdash-desktop/src/renderer/features/tasks/components/issue-selector/parse-issue-search-error.ts Error parsing logic with provider-specific handling for Notion and GitHub; falls back to a generic error display. Well-tested.
apps/emdash-desktop/src/shared/telemetry.ts Replaces hardcoded provider union literals with IssueProviderType, fixing several pre-existing stale enumerations (monday, trello, notion were missing from telemetry types).
apps/emdash-desktop/src/main/core/notion/notion-connection-service.test.ts Comprehensive tests covering credential storage, URL parsing, token preservation, legacy migration, and API error normalization.
apps/emdash-desktop/src/main/core/notion/notion-issue-provider.test.ts Good test coverage across all provider code paths including pagination, search, data-source queries, and block-context collection.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as Renderer (NotionSetupForm)
    participant RPC as RPC Router (notionController)
    participant CS as NotionConnectionService
    participant KS as EncryptedSecretsStore
    participant NA as Notion API

    UI->>RPC: "saveCredentials({token, databaseUrls})"
    RPC->>CS: saveCredentials(input)
    CS->>CS: parseDatabaseUrls(databaseUrls)
    CS->>NA: GET /users/me (validate token)
    NA-->>CS: "{id, bot.workspace_name}"
    CS->>KS: setSecret("emdash-notion-credentials", JSON)
    CS-->>RPC: "{success:true, displayName}"
    RPC-->>UI: "{success:true, displayName}"

    Note over UI,NA: Issue listing (data-sources scope)
    UI->>RPC: "listIssues({limit:50})"
    RPC->>CS: getStoredCredentials()
    CS->>KS: getSecret (cached after first read)
    KS-->>CS: credentials
    CS-->>RPC: credentials
    RPC->>NA: "POST /data_sources/{id}/query (x N, concurrency=4)"
    NA-->>RPC: "{results:[pages]}"
    RPC-->>UI: "{success:true, issues:[]}"

    Note over UI,NA: getIssueContext
    UI->>RPC: "getIssueContext({identifier})"
    RPC->>NA: "GET /pages/{id}"
    NA-->>RPC: page
    RPC->>NA: "GET /blocks/{id}/children (paginated, depth<=3)"
    NA-->>RPC: blocks
    RPC-->>UI: "{success:true, issue:{...context}}"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as Renderer (NotionSetupForm)
    participant RPC as RPC Router (notionController)
    participant CS as NotionConnectionService
    participant KS as EncryptedSecretsStore
    participant NA as Notion API

    UI->>RPC: "saveCredentials({token, databaseUrls})"
    RPC->>CS: saveCredentials(input)
    CS->>CS: parseDatabaseUrls(databaseUrls)
    CS->>NA: GET /users/me (validate token)
    NA-->>CS: "{id, bot.workspace_name}"
    CS->>KS: setSecret("emdash-notion-credentials", JSON)
    CS-->>RPC: "{success:true, displayName}"
    RPC-->>UI: "{success:true, displayName}"

    Note over UI,NA: Issue listing (data-sources scope)
    UI->>RPC: "listIssues({limit:50})"
    RPC->>CS: getStoredCredentials()
    CS->>KS: getSecret (cached after first read)
    KS-->>CS: credentials
    CS-->>RPC: credentials
    RPC->>NA: "POST /data_sources/{id}/query (x N, concurrency=4)"
    NA-->>RPC: "{results:[pages]}"
    RPC-->>UI: "{success:true, issues:[]}"

    Note over UI,NA: getIssueContext
    UI->>RPC: "getIssueContext({identifier})"
    RPC->>NA: "GET /pages/{id}"
    NA-->>RPC: page
    RPC->>NA: "GET /blocks/{id}/children (paginated, depth<=3)"
    NA-->>RPC: blocks
    RPC-->>UI: "{success:true, issue:{...context}}"
Loading

Reviews (3): Last reviewed commit: "fix(notion): keep database urls single-l..." | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/main/core/notion/notion-issue-provider.ts
Comment thread apps/emdash-desktop/src/main/core/notion/notion-issue-provider.ts Outdated
@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

@janburzinski

Copy link
Copy Markdown
Collaborator Author
Screenshot 2026-06-24 at 12 14 55 example error state:

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.

1 participant