Skip to content

[FEATURE] Capability-complete ClickUp integration #603

Description

@therealbrad

Feature Summary

Ship a capability-complete ClickUp integration on release/v1.1, built on the adapter work contributed in #602 but extended to match the bar set by our other tracker integrations: inbound webhooks, per-List custom fields, task attachments, task links/dependencies, and Sprint-based milestone sync.

This issue captures the research already done so the next person to pick it up doesn't repeat it.

Problem Statement

Current Situation

TestPlanIt has no ClickUp integration. @ilmimris contributed one in #602 (OAuth2 IssueAdapter, ~459 lines plus ~500 lines of tests and docs), but it targeted main at a moment when main was ~725 commits behind beta, and beta was about to be promoted to main as the 1.0 release.

Two things were established while triaging that PR:

1. It is not a port problem. The three commits from #602 cherry-pick onto both beta and release/v1.1 with a single conflict — testplanit/prisma/schema.prisma, which no longer exists on either branch after the ZenStack v3 refactor (refactor(zenstack): remove Prisma from the testplanit app + prod images, prisma/db/). That file is obsolete, not conflicting in any meaningful sense. zenstack-openapi.json also conflicts but regenerates itself, and the CLICKUP enum added to schema.zmodel propagates correctly through pnpm generate into zenstack/schema.ts.

The only real code change required is one line: beta added a required milestones field to IssueAdapterCapabilities that post-dates the contributor's branch. With milestones: false added to ClickUpAdapter.getCapabilities() (plus the mirroring assertion in ClickUpAdapter.test.ts), on both beta and release/v1.1:

  • pnpm type-check — 0 errors
  • ClickUpAdapter.test.ts + BaseAdapter.test.ts + IntegrationManager.test.ts — 105/105 passing

2. It is a scope problem. Every tracker adapter we ship declares the same six baseline capabilities (createIssue, updateIssue, linkIssue, syncIssue, searchIssues, comments). Those don't differentiate. These five do:

Provider webhooks customFields attachments linkedIssues milestones
Jira RELEASE + ITERATION
Azure DevOps
MantisBT
Redmine
GitHub
GitLab
Gitea
ClickUp (#602 as submitted)
Simple URL

Merging #602 as-is would make ClickUp the only real tracker integration with none of the five. The only other zero row is Simple URL, which isn't a tracker integration at all — it's a link-only stub that can't create or sync an issue. Even Gitea, our thinnest genuine adapter, ships inbound webhooks.

Desired Outcome

ClickUp lands at or near the Azure DevOps tier — four of five differentiating capabilities, plus milestone sync if the Sprint mapping proves workable. Users connect ClickUp the way they connect Jira, and the integration doesn't need a backfill pass later.

Proposed Solution

Build on #602's adapter rather than starting over. The following carries over largely intact and should not be rewritten:

  • OAuth2 authorize/exchange/refresh flow (ClickUp tokens don't expire or refresh — handled explicitly)
  • The Team → Space → Folder → List hierarchy walk backing getProjects(), since ClickUp has no flat "project" concept
  • The BaseAdapter provider branch for ClickUp's OAuth header, which omits the Bearer prefix unlike GitHub/GitLab/Jira Cloud (guarded by a regression test)
  • ClickUp API quirk handling: the {assignees:{add,rem}} delta update shape, epoch-millisecond timestamps, the inverted 1(urgent)–4(low) integer priority scale
  • CLICKUP added to the IntegrationProvider enum in schema.zmodel
  • Admin Integrations UI wiring (type selector, icon, config form, OAuth-only auth type), project integrations picker allowlist, test-connection OAuth check
  • testplanit/docs/integrations/clickup.md

User Story

As a QA engineer whose team plans in ClickUp, I want TestPlanIt to integrate with ClickUp as completely as it does with Jira so that I can link, sync, and file tasks without my tracker being a second-class citizen.

Acceptance Criteria

  • webhooks — inbound ClickUp webhooks with signature verification, routed through the existing inbound-webhook path
  • customFields — per-List custom field discovery and read/write on tasks
  • attachments — task attachment upload plus listAttachments/downloadAttachment
  • linkedIssues — ClickUp task links and/or dependencies surfaced as LinkedIssueRef[]
  • milestones — Sprint-based ITERATION sync via getExternalMilestones/getMilestoneIssues (see open question below)
  • CLICKUP added to the provider allowlist in lib/services/jira-link-service.ts — missing in feat(integrations): add ClickUp outbound integration (OAuth2) #602, and it would be missing on main too
  • Capability matrix above updated; ClickUp no longer a zero row
  • Unit tests at parity with the existing adapter suites
  • E2E coverage for the OAuth round-trip and inbound webhook path
  • A real OAuth round-trip verified against a live ClickUp workspace — never done for feat(integrations): add ClickUp outbound integration (OAuth2) #602, which was its main outstanding item

Alternative Solutions

Option 1 — Merge #602 as-is to beta, backfill later

Verified to work (see above) and would have shipped ClickUp support as a 1.0 fast-follow for roughly two lines of effort. Rejected: it puts a zero-capability adapter in front of users and creates a backfill obligation while people are already depending on it.

Option 2 — Rework to capability-complete on release/v1.1 (chosen)

Larger up-front effort, but ClickUp arrives at a tier consistent with the rest of the integrations and needs no follow-up pass.

Option 3 — Full literal Jira parity

Rejected as not well-defined. Much of Jira's surface is marketplace-specific with no ClickUp analog — the Forge app, wiki-markup converter, panel generation, quickscript, and ~14 dedicated /api/integrations/jira/* routes. "Capability-complete against the IssueAdapter interface" is the meaningful target.

Technical Considerations

Architectural notes for whoever picks this up, current as of release/v1.1:

The adapter interface. testplanit/lib/integrations/adapters/IssueAdapter.ts is the contract. IssueAdapterCapabilities.milestones is required (false | { kinds: Array<"RELEASE" | "ITERATION">; webhooks: boolean }) — this is what #602 tripped on. The capabilities to implement map to these optional interface methods: registerWebhook/unregisterWebhook/processWebhook, getCustomFields, uploadAttachment/listAttachments/downloadAttachment, getLinkedIssues, and getExternalMilestones/getMilestoneIssues.

Milestone sync is capability-driven, not Jira-hardcoded. lib/integrations/services/MilestoneSyncService.ts (~1450 lines) dispatches through the adapter interface. Implementing getExternalMilestones and declaring the milestones capability is sufficient to plug in — no changes needed inside the service. Note resolveBoardProject exists specifically for Jira sprint webhooks that carry only originBoardId; ClickUp may need an equivalent or may not, depending on its webhook payload shape.

Capability consumers to check when flipping flags on: app/api/integrations/[id]/create-issue/route.ts:360 gates attachment upload on adapter.uploadAttachment && getCapabilities().attachments; lib/integrations/services/SyncService.ts:1407 reads capabilities; projects/settings/[projectId]/integrations/milestone-sync-settings.tsx and projects/milestones/[projectId]/page.tsx mirror the capability shape client-side.

Schema workflow. schema.zmodel is the sole source of truth. prisma/schema.prisma no longer exists — do not hand-mirror it (this was #602's own flagged caveat). pnpm generate regenerates zenstack/ and the OpenAPI specs.

Reference implementations. JiraAdapter.ts (2891 lines) for the full surface including milestones; AzureDevOpsAdapter.ts for the four-of-five shape this issue targets, which is the closer analog.

Dependencies

  • Requires database schema changes — CLICKUP on IntegrationProvider (already done in feat(integrations): add ClickUp outbound integration (OAuth2) #602)
  • Requires API changes — inbound webhook receiver endpoint
  • Requires UI/UX design review
  • Requires third-party integration — ClickUp API v2, OAuth app registration at https://app.clickup.com/settings/apps
  • Other: needs a live ClickUp workspace for OAuth and webhook verification

Security Considerations

  • Inbound webhook signature verification is mandatory — ClickUp signs payloads with an HMAC derived from the webhook secret. Unverified payloads must be rejected, not merely logged.
  • ClickUp OAuth tokens do not expire and have no refresh flow, so a leaked token stays valid until revoked ClickUp-side. Storage must use the existing encrypted-credential path, and revocation/rotation should be documented.
  • The Team → Space → Folder → List walk can enumerate more of a workspace than the user expects. Scope what's fetched and surfaced to what the integration actually needs.
  • Attachment download pulls arbitrary user-supplied files from an external system — same handling as the Jira attachment path, including contentUrl being treated as server-internal and never accepted from a client.

Performance Impact

The hierarchy walk is N+1-ish by nature (Team → Space → Folder → List) and already takes ~2s in the test fixture. It should stay cached/lazy in the List picker rather than being called on hot paths. ClickUp enforces rate limits per token (100 req/min on free tiers), so bulk import and milestone sync need the same backoff treatment as the other adapters.

Business Value

Priority

  • Critical - Core functionality gap
  • High - Significant improvement
  • Medium - Nice to have
  • Low - Future consideration

Affected User Groups

  • Test Managers
  • QA Engineers
  • Developers
  • Project Managers

Expected Usage

  • Daily

Implementation Effort

  • Small (1-2 days)
  • Medium (3-5 days)
  • Large (1-2 weeks)
  • Extra Large (>2 weeks)

Large assumes #602's adapter as the starting point. Extra Large if the Sprint mapping below turns out to need its own design pass.

Open Questions

ClickUp Sprint modeling is the one genuinely unresolved piece and deserves a spike before committing to the milestones criterion. ClickUp has no Fix Version equivalent, so there is no RELEASE analog — ITERATION only. Sprints in ClickUp are Sprint Folders, a specialized folder type rather than a first-class scheduling object, which doesn't map cleanly onto ExternalMilestone (startDate/endDate/state of FUTURE/ACTIVE/CLOSED). Worth confirming what the API actually exposes for sprint state and dates before promising this capability.

If the mapping proves awkward, shipping at the Azure DevOps tier (four of five, milestones: false) is a legitimate landing spot and still clears the bar this issue is about.

Related

Community Interest

Contributed unprompted by a community member, which is a reasonable signal of real demand.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions