Skip to content

Commit 92fc7eb

Browse files
committed
docs(clickup): add an end-to-end Testing guide to the README
A step-by-step pass a human can follow against a real account: create the credentials, verify the client standalone, deploy the task the receiver launches, run it directly, deploy the app, wire the provider up, trigger a real event, and confirm idempotency. Ends with a troubleshooting table mapping each failure mode to its cause. Ordered so each step fails in isolation: the client is exercised before the platform, and the launched task is deployed before the app that looks it up. Also adds the `triage_task` task the webhook example launches. It was looked up by name but defined nowhere, so the example could not work as written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKZrTNjjWVzTZUxDFbn4Nk Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com>
1 parent 611059e commit 92fc7eb

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

plugins/clickup/README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,106 @@ agent = Agent(
185185
API base URL, timeouts, and retries. The module exports `default_config`; pass
186186
a custom `Config` to `ClickUpClient`, `build_mcp_server`, or the app
187187
environment when you need it.
188+
189+
## Testing
190+
191+
An end-to-end pass against a real ClickUp workspace. Use a scratch list —
192+
step 4 creates and comments on real tasks.
193+
194+
**1. Create the credentials.** A personal API token from ClickUp → Settings →
195+
Apps → *Generate*:
196+
197+
```bash
198+
flyte create secret CLICKUP_TOKEN --value pk_...
199+
```
200+
201+
**2. Check the client works before involving the platform**, and find the list
202+
id you will test against:
203+
204+
```bash
205+
export CLICKUP_TOKEN=pk_...
206+
python -c "
207+
from flyteplugins.clickup import ClickUpClient
208+
with ClickUpClient() as c:
209+
ws = c.list_workspaces()[0]
210+
print('workspace', ws['id'], ws['name'])
211+
for s in c.list_spaces(ws['id']):
212+
for lst in c.list_lists(space_id=s['id']):
213+
print(' list', lst['id'], lst['name'])
214+
"
215+
```
216+
217+
**3. Deploy the task the webhook will launch.**
218+
219+
```bash
220+
flyte deploy plugins/clickup/examples/manage_ticket.py env
221+
```
222+
223+
`react_to_clickup_events.py` looks this task up by name (`triage_task`), so it
224+
has to exist before the app can launch it.
225+
226+
**4. Run a task directly**, to confirm writes land before any webhook is
227+
involved:
228+
229+
```bash
230+
flyte run plugins/clickup/examples/manage_ticket.py open_ticket \
231+
--list_id <list-id> --name "Flyte test ticket" --description "created by the plugin test"
232+
```
233+
234+
**5. Deploy the webhook app.**
235+
236+
```bash
237+
python plugins/clickup/examples/react_to_clickup_events.py
238+
```
239+
240+
It prints the app URL. Open it: the dashboard should show the token mounted,
241+
and *Verify ClickUp credentials* should return your user.
242+
243+
**6. Point ClickUp at the app.** Space or workspace Settings → Integrations →
244+
Webhooks → *Create Webhook*:
245+
246+
- Endpoint: `<app-url>/webhook`
247+
- Events: *taskCreated* and *taskStatusUpdated*
248+
249+
ClickUp shows a signing secret on creation. Store it and redeploy so it is
250+
mounted:
251+
252+
```bash
253+
flyte create secret CLICKUP_WEBHOOK_SECRET --value <signing-secret>
254+
```
255+
256+
**7. Trigger a real event.** Create a task in the watched list. Then check, in
257+
order:
258+
259+
- `<app-url>/api/events` — the normalized event, `qualified_type` of
260+
`taskCreated`.
261+
- `flyte get runs` — a run whose `dedupe` label matches.
262+
- The ticket — the triage task's comment.
263+
264+
**8. Confirm later updates get their own runs.** Change the task's status. The
265+
dedupe key folds in ClickUp's own event timestamp, so this is a new key and
266+
launches a second run, while a redelivery of the *same* event does not.
267+
268+
**9. Optional — the allowlist.** Redeploy with `list_ids=["<list-id>"]` and
269+
create a task in a different list. The receiver should answer 200 with a
270+
`skipped` message. The allowlist fails closed, so an event carrying no list id
271+
is skipped too.
272+
273+
**10. Optional — the MCP server.**
274+
275+
```bash
276+
python plugins/clickup/examples/clickup_mcp_server.py
277+
claude mcp add --transport http clickup-mcp <app-url>/mcp/mcp
278+
```
279+
280+
Ask an agent to summarize the list's open tasks. The default surface is
281+
read-only.
282+
283+
### Troubleshooting
284+
285+
| Symptom | Cause |
286+
| --- | --- |
287+
| Webhook delivery returns 401 | `CLICKUP_WEBHOOK_SECRET` does not match the secret ClickUp generated. |
288+
| Delivery returns 503 | `CLICKUP_WEBHOOK_SECRET` is not mounted; check `/api/status`. |
289+
| 200 but no run | No handler matched, or the allowlist skipped it — the response body says which. |
290+
| A status transition fails from a task | ClickUp rejects statuses the list does not define; `close_ticket` calls `list_statuses` first for exactly this reason. |

plugins/clickup/examples/manage_ticket.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ async def close_ticket(task_id: str, done_status: str = "done") -> str:
5252
return task_id
5353

5454

55+
@env.task
56+
async def triage_task(task_id: str) -> str:
57+
"""Comment on a newly created task.
58+
59+
This is the task `react_to_clickup_events.py` launches for every
60+
`taskCreated` event.
61+
"""
62+
async with ClickUpClient() as client:
63+
task = await client.get_task.aio(task_id)
64+
await client.add_comment.aio(task_id, f"Flyte triaged this ticket (status: {task.get('status')}).")
65+
return f"triaged {task_id}"
66+
67+
5568
if __name__ == "__main__":
5669
# Replace with a list id from your ClickUp workspace.
5770
flyte.run(open_ticket, list_id="LIST_ID", name="Test ticket", description="Created by Flyte.")

0 commit comments

Comments
 (0)