Skip to content

Commit

Permalink
integrations: Add ClickUp integration.
Browse files Browse the repository at this point in the history
Creates an incoming webhook integration for ClickUp. The main
use case is getting notifications when new ClickUp items such as task, list, folder, space, goals are created, updated or deleted.

Fixes zulip#26529.
  • Loading branch information
PieterCK committed Apr 9, 2024
1 parent 15cec69 commit 8db806c
Show file tree
Hide file tree
Showing 37 changed files with 1,528 additions and 1 deletion.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/images/integrations/clickup/001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions static/images/integrations/logos/clickup.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion zerver/lib/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.utils.translation import gettext_lazy
from django_stubs_ext import StrPromise
from typing_extensions import TypeAlias

from zerver.lib.storage import static_path

"""This module declares all of the (documented) integrations available
Expand Down Expand Up @@ -375,6 +374,7 @@ def __init__(self, name: str, *args: Any, **kwargs: Any) -> None:
WebhookIntegration("buildbot", ["continuous-integration"], display_name="Buildbot"),
WebhookIntegration("canarytoken", ["monitoring"], display_name="Thinkst Canarytokens"),
WebhookIntegration("circleci", ["continuous-integration"], display_name="CircleCI"),
WebhookIntegration("clickup", ["project-management"], display_name="ClickUp"),
WebhookIntegration("clubhouse", ["project-management"]),
WebhookIntegration("codeship", ["continuous-integration", "deployment"]),
WebhookIntegration("crashlytics", ["monitoring"]),
Expand Down Expand Up @@ -735,6 +735,7 @@ def __init__(self, name: str, *args: Any, **kwargs: Any) -> None:
ScreenshotConfig("bitbucket_job_completed.json", image_name="001.png"),
ScreenshotConfig("github_job_completed.json", image_name="002.png"),
],
"clickup": [ScreenshotConfig("task_created.json")],
"clubhouse": [ScreenshotConfig("story_create.json")],
"codeship": [ScreenshotConfig("error_build.json")],
"crashlytics": [ScreenshotConfig("issue_message.json")],
Expand Down
Empty file.
68 changes: 68 additions & 0 deletions zerver/webhooks/clickup/api_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import Any, Dict

import requests
from urllib.parse import urljoin
from zerver.lib.outgoing_http import OutgoingSession


class Error(Exception):
pass


class APIUnavailableError(Error):
pass


class BadRequestError(Error):
pass


class ClickUpSession(OutgoingSession):
def __init__(self, **kwargs: Any) -> None:
super().__init__(role="clickup", timeout=5, **kwargs)


def make_clickup_request(path: str, api_key: str) -> Dict[str, Any]:
headers: Dict[str, str] = {
"Content-Type": "application/json",
"Authorization": api_key,
}

try:
base_url = "https://api.clickup.com/api/v2/"
api_endpoint = urljoin(base_url, path)
response = ClickUpSession(headers=headers).get(
api_endpoint,
)
response.raise_for_status()
except (requests.ConnectionError, requests.Timeout) as e:
raise APIUnavailableError from e
except requests.HTTPError as e:
raise BadRequestError from e

return response.json()


def get_list(api_key: str, list_id: str) -> Dict[str, Any]:
data = make_clickup_request(f"list/{list_id}", api_key)
return data


def get_task(api_key: str, task_id: str) -> Dict[str, Any]:
data = make_clickup_request(f"task/{task_id}", api_key)
return data


def get_folder(api_key: str, folder_id: str) -> Dict[str, Any]:
data = make_clickup_request(f"folder/{folder_id}", api_key)
return data


def get_goal(api_key: str, goal_id: str) -> Dict[str, Any]:
data = make_clickup_request(f"goal/{goal_id}", api_key)
return data


def get_space(api_key: str, space_id: str) -> Dict[str, Any]:
data = make_clickup_request(f"space/{space_id}", api_key)
return data
14 changes: 14 additions & 0 deletions zerver/webhooks/clickup/callback_fixtures/get_folder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "457",
"name": "Lord Foldemort",
"orderindex": 0,
"override_statuses": false,
"hidden": false,
"space": {
"id": "789",
"name": "Space Name",
"access": true
},
"task_count": "0",
"lists": []
}
33 changes: 33 additions & 0 deletions zerver/webhooks/clickup/callback_fixtures/get_goal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"goal": {
"id": "e53a033c-900e-462d-a849-4a216b06d930",
"name": "hat-trick",
"team_id": "512",
"date_created": "1568044355026",
"start_date": null,
"due_date": "1568036964079",
"description": "Updated Goal Description",
"private": false,
"archived": false,
"creator": 183,
"color": "#32a852",
"pretty_id": "6",
"multiple_owners": true,
"folder_id": null,
"members": [],
"owners": [
{
"id": 182,
"username": "Pieter CK",
"email": "[email protected]",
"color": "#7b68ee",
"initials": "PK",
"profilePicture": "https://attachments-public.clickup.com/profilePictures/182_abc.jpg"
}
],
"key_results": [],
"percent_completed": 0,
"history": [],
"pretty_url": "https://app.clickup.com/512/goals/6"
}
}
49 changes: 49 additions & 0 deletions zerver/webhooks/clickup/callback_fixtures/get_list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"id": "124",
"name": "List-an al Gaib",
"orderindex": 1,
"content": "Updated List Content",
"status": {
"status": "red",
"color": "#e50000",
"hide_label": true
},
"priority": {
"priority": "high",
"color": "#f50000"
},
"assignee": null,
"due_date": "1567780450202",
"due_date_time": true,
"start_date": null,
"start_date_time": null,
"folder": {
"id": "456",
"name": "Folder Name",
"hidden": false,
"access": true
},
"space": {
"id": "789",
"name": "Space Name",
"access": true
},
"inbound_address": "add.task.124.ac725f.31518a6a-05bb-4997-92a6-1dcfe2f527ca@tasks.clickup.com",
"archived": false,
"override_statuses": false,
"statuses": [
{
"status": "to do",
"orderindex": 0,
"color": "#d3d3d3",
"type": "open"
},
{
"status": "complete",
"orderindex": 1,
"color": "#6bc950",
"type": "closed"
}
],
"permission_level": "create"
}
52 changes: 52 additions & 0 deletions zerver/webhooks/clickup/callback_fixtures/get_space.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"id": "790",
"name": "the Milky Way",
"private": false,
"statuses": [
{
"status": "to do",
"type": "open",
"orderindex": 0,
"color": "#d3d3d3"
},
{
"status": "complete",
"type": "closed",
"orderindex": 1,
"color": "#6bc950"
}
],
"multiple_assignees": false,
"features": {
"due_dates": {
"enabled": false,
"start_date": false,
"remap_due_dates": false,
"remap_closed_due_date": false
},
"time_tracking": {
"enabled": false
},
"tags": {
"enabled": false
},
"time_estimates": {
"enabled": false
},
"checklists": {
"enabled": true
},
"custom_fields": {
"enabled": true
},
"remap_dependencies": {
"enabled": false
},
"dependency_warning": {
"enabled": false
},
"portfolios": {
"enabled": false
}
}
}
69 changes: 69 additions & 0 deletions zerver/webhooks/clickup/callback_fixtures/get_task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"id": "string",
"custom_id": "string",
"custom_item_id": 0,
"name": "Tanswer",
"text_content": "string",
"description": "string",
"status": {
"status": "in progress",
"color": "#d3d3d3",
"orderindex": 1,
"type": "custom"
},
"orderindex": "string",
"date_created": "string",
"date_updated": "string",
"date_closed": "string",
"creator": {
"id": 183,
"username": "Pieter CK",
"color": "#827718",
"profilePicture": "https://attachments-public.clickup.com/profilePictures/183_abc.jpg"
},
"assignees": [
"string"
],
"checklists": [
"string"
],
"tags": [
"string"
],
"parent": "string",
"priority": "string",
"due_date": "string",
"start_date": "string",
"time_estimate": "string",
"time_spent": "string",
"custom_fields": [
{
"id": "string",
"name": "string",
"type": "string",
"type_config": {},
"date_created": "string",
"hide_from_guests": true,
"value": {
"id": 183,
"username": "Pieter CK",
"email": "[email protected]",
"color": "#7b68ee",
"initials": "PK",
"profilePicture": null
},
"required": true
}
],
"list": {
"id": "123"
},
"folder": {
"id": "456"
},
"space": {
"id": "789"
},
"url": "https://app.clickup.com/XXXXXXXX/home",
"markdown_description": "string"
}
67 changes: 67 additions & 0 deletions zerver/webhooks/clickup/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
!!! tip ""

Note that [Zapier][1] is usually a simpler way to
integrate ClickUp with Zulip.

Get Zulip notifications from your ClickUp space!

[1]: ./zapier

1. {!create-stream.md!}

1. {!create-an-incoming-webhook.md!}

1. {!generate-integration-url.md!}

You're now going to need to run a ClickUp Integration configuration script from a
computer (any computer) connected to the internet. It won't make any
changes to the computer.

1. Make sure you have a working copy of Python. If you're running
macOS or Linux, you very likely already do. If you're running
Windows you may or may not. If you don't have Python, follow the
installation instructions
[here](https://realpython.com/installing-python/). Note that you
do not need the latest version of Python; anything 2.7 or higher
will do.
1. Download [zulip-clickup.py][2]. `Ctrl+s` or `Cmd+s` on that page should
work in most browsers.

1. To run the script, you require the following 3 items:

* **Team ID**: Go to your ClickUp home. The URL should look like
`https://app.clickup.com/<TEAM_ID>/home`. Note down the
`<TEAM_ID>`.

* **Client ID & Client Secret**: Please follow the instructions below:

- Go to <https://app.clickup.com/settings/team/clickup-api> and click **Create an App** button.

- After that, you will be prompted for Redirect URL(s). You must enter your zulip app URL.
e.g. `YourZulipApp.com`.

- Finally, note down the **Client ID** and **Client Secret**

1. Run the `zulip-clickup` script in a terminal, after replacing the all caps
arguments with the values collected above.

```
python zulip-clickup.py --clickup-team-id TEAM_ID \
--clickup-client-id CLIENT_ID \
--clickup-client-secret CLIENT_SECRET
```
The `zulip-clickup.py` script only needs to be run once, and can be run
on any computer with python.
1. Follow the instructions given by the script.
**Note:** You will be prompted for the **integration url** you just generated in step 2 and watch your browser since you will be redirected to a ClickUp authorization page to proceed.
1. You can delete `zulip-clickup.py` from your computer if you'd like or run it again to
reconfigure your ClickUp integration.
[2]: https://raw.githubusercontent.com/zulip/python-zulip-api/main/zulip/integrations/trello/zulip_trello.py
{!congrats.md!}
![](/static/images/integrations/clickup/001.png)
5 changes: 5 additions & 0 deletions zerver/webhooks/clickup/fixtures/folder_created.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"event": "folderCreated",
"folder_id": "96772212",
"webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204"
}
5 changes: 5 additions & 0 deletions zerver/webhooks/clickup/fixtures/folder_deleted.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"event": "folderDeleted",
"folder_id": "96772212",
"webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204"
}
Loading

0 comments on commit 8db806c

Please sign in to comment.