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
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
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
Affected User Groups
Expected Usage
Implementation Effort
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.
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 targetedmainat a moment whenmainwas ~725 commits behindbeta, andbetawas about to be promoted tomainas 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
betaandrelease/v1.1with 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.jsonalso conflicts but regenerates itself, and theCLICKUPenum added toschema.zmodelpropagates correctly throughpnpm generateintozenstack/schema.ts.The only real code change required is one line:
betaadded a requiredmilestonesfield toIssueAdapterCapabilitiesthat post-dates the contributor's branch. Withmilestones: falseadded toClickUpAdapter.getCapabilities()(plus the mirroring assertion inClickUpAdapter.test.ts), on bothbetaandrelease/v1.1:pnpm type-check— 0 errorsClickUpAdapter.test.ts+BaseAdapter.test.ts+IntegrationManager.test.ts— 105/105 passing2. 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: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:
getProjects(), since ClickUp has no flat "project" conceptBaseAdapterprovider branch for ClickUp's OAuth header, which omits theBearerprefix unlike GitHub/GitLab/Jira Cloud (guarded by a regression test){assignees:{add,rem}}delta update shape, epoch-millisecond timestamps, the inverted 1(urgent)–4(low) integer priority scaleCLICKUPadded to theIntegrationProviderenum inschema.zmodeltestplanit/docs/integrations/clickup.mdUser 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 pathcustomFields— per-List custom field discovery and read/write on tasksattachments— task attachment upload pluslistAttachments/downloadAttachmentlinkedIssues— ClickUp task links and/or dependencies surfaced asLinkedIssueRef[]milestones— Sprint-basedITERATIONsync viagetExternalMilestones/getMilestoneIssues(see open question below)CLICKUPadded to the provider allowlist inlib/services/jira-link-service.ts— missing in feat(integrations): add ClickUp outbound integration (OAuth2) #602, and it would be missing onmaintooAlternative Solutions
Option 1 — Merge #602 as-is to
beta, backfill laterVerified 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 theIssueAdapterinterface" 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.tsis the contract.IssueAdapterCapabilities.milestonesis 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, andgetExternalMilestones/getMilestoneIssues.Milestone sync is capability-driven, not Jira-hardcoded.
lib/integrations/services/MilestoneSyncService.ts(~1450 lines) dispatches through the adapter interface. ImplementinggetExternalMilestonesand declaring themilestonescapability is sufficient to plug in — no changes needed inside the service. NoteresolveBoardProjectexists specifically for Jira sprint webhooks that carry onlyoriginBoardId; 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:360gates attachment upload onadapter.uploadAttachment && getCapabilities().attachments;lib/integrations/services/SyncService.ts:1407reads capabilities;projects/settings/[projectId]/integrations/milestone-sync-settings.tsxandprojects/milestones/[projectId]/page.tsxmirror the capability shape client-side.Schema workflow.
schema.zmodelis the sole source of truth.prisma/schema.prismano longer exists — do not hand-mirror it (this was #602's own flagged caveat).pnpm generateregenerateszenstack/and the OpenAPI specs.Reference implementations.
JiraAdapter.ts(2891 lines) for the full surface including milestones;AzureDevOpsAdapter.tsfor the four-of-five shape this issue targets, which is the closer analog.Dependencies
CLICKUPonIntegrationProvider(already done in feat(integrations): add ClickUp outbound integration (OAuth2) #602)https://app.clickup.com/settings/appsSecurity Considerations
contentUrlbeing 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
Affected User Groups
Expected Usage
Implementation Effort
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
milestonescriterion. ClickUp has no Fix Version equivalent, so there is noRELEASEanalog —ITERATIONonly. Sprints in ClickUp are Sprint Folders, a specialized folder type rather than a first-class scheduling object, which doesn't map cleanly ontoExternalMilestone(startDate/endDate/stateofFUTURE/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
milestonesfix applied) was produced during triage and can be reused as the starting commit.Community Interest
Contributed unprompted by a community member, which is a reasonable signal of real demand.