Skip to content

Commit e4883b8

Browse files
committed
integrations: Add ClickUp integration.
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.
1 parent c44c84b commit e4883b8

File tree

11 files changed

+235
-218
lines changed

11 files changed

+235
-218
lines changed

zerver/lib/integrations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.utils.translation import gettext_lazy
99
from django_stubs_ext import StrPromise
1010
from typing_extensions import TypeAlias
11+
1112
from zerver.lib.storage import static_path
1213

1314
"""This module declares all of the (documented) integrations available
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from enum import Enum
2+
from typing import List
3+
4+
5+
class ConstantVariable(Enum):
6+
@classmethod
7+
def as_list(cls) -> List[str]:
8+
return [item.value for item in cls]
9+
10+
11+
class EventItemType(ConstantVariable):
12+
TASK: str = "task"
13+
LIST: str = "list"
14+
FOLDER: str = "folder"
15+
GOAL: str = "goal"
16+
SPACE: str = "space"
17+
18+
19+
class EventAcion(ConstantVariable):
20+
CREATED: str = "Created"
21+
UPDATED: str = "Updated"
22+
DELETED: str = "Deleted"
23+
24+
25+
class SimpleFields(ConstantVariable):
26+
# Events with identical payload format
27+
PRIORITY: str = "priority"
28+
STATUS: str = "status"
29+
30+
31+
class SpecialFields(ConstantVariable):
32+
# Event with unique payload
33+
NAME: str = "name"
34+
ASSIGNEE: str = "assignee_add"
35+
COMMENT: str = "comment"
36+
DUE_DATE: str = "due_date"
37+
MOVED: str = "section_moved"
38+
TIME_ESTIMATE: str = "time_estimate"
39+
TIME_SPENT: str = "time_spent"
40+
41+
42+
class SpammyFields(ConstantVariable):
43+
TAG: str = "tag"
44+
TAG_REMOVED: str = "tag_removed"
45+
UNASSIGN: str = "assignee_rem"

zerver/webhooks/clickup/api_endpoints.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import re
12
from typing import Any, Dict
3+
from urllib.parse import urljoin
24

35
import requests
4-
from urllib.parse import urljoin
6+
57
from zerver.lib.outgoing_http import OutgoingSession
8+
from zerver.webhooks.clickup import EventItemType
69

710

811
class Error(Exception):
@@ -22,7 +25,18 @@ def __init__(self, **kwargs: Any) -> None:
2225
super().__init__(role="clickup", timeout=5, **kwargs)
2326

2427

28+
def verify_url_path(path: str) -> bool:
29+
parts = path.split("/")
30+
if len(parts) < 2 or parts[0] not in EventItemType.as_list() or parts[1] == "":
31+
return False
32+
pattern = r"^[a-zA-Z0-9_-]+$"
33+
match = re.match(pattern, parts[1])
34+
return match is not None and match.group() == parts[1]
35+
36+
2537
def make_clickup_request(path: str, api_key: str) -> Dict[str, Any]:
38+
if verify_url_path(path) is False:
39+
raise BadRequestError("Invalid path")
2640
headers: Dict[str, str] = {
2741
"Content-Type": "application/json",
2842
"Authorization": api_key,
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
2-
"id": "457",
3-
"name": "Lord Foldemort",
4-
"orderindex": 0,
5-
"override_statuses": false,
6-
"hidden": false,
7-
"space": {
8-
"id": "789",
9-
"name": "Space Name",
10-
"access": true
11-
},
12-
"task_count": "0",
13-
"lists": []
2+
"id": "457",
3+
"name": "Lord Foldemort",
4+
"orderindex": 0,
5+
"override_statuses": false,
6+
"hidden": false,
7+
"space": {
8+
"id": "789",
9+
"name": "Space Name",
10+
"access": true
11+
},
12+
"task_count": "0",
13+
"lists": []
1414
}
Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
{
22
"goal": {
3-
"id": "e53a033c-900e-462d-a849-4a216b06d930",
4-
"name": "hat-trick",
5-
"team_id": "512",
6-
"date_created": "1568044355026",
7-
"start_date": null,
8-
"due_date": "1568036964079",
9-
"description": "Updated Goal Description",
10-
"private": false,
11-
"archived": false,
12-
"creator": 183,
13-
"color": "#32a852",
14-
"pretty_id": "6",
15-
"multiple_owners": true,
16-
"folder_id": null,
17-
"members": [],
18-
"owners": [
19-
{
20-
"id": 182,
21-
"username": "Pieter CK",
22-
"email": "kwok.pieter@gmail.com",
23-
"color": "#7b68ee",
24-
"initials": "PK",
25-
"profilePicture": "https://attachments-public.clickup.com/profilePictures/182_abc.jpg"
26-
}
27-
],
28-
"key_results": [],
29-
"percent_completed": 0,
30-
"history": [],
31-
"pretty_url": "https://app.clickup.com/512/goals/6"
3+
"id": "e53a033c-900e-462d-a849-4a216b06d930",
4+
"name": "hat-trick",
5+
"team_id": "512",
6+
"date_created": "1568044355026",
7+
"start_date": null,
8+
"due_date": "1568036964079",
9+
"description": "Updated Goal Description",
10+
"private": false,
11+
"archived": false,
12+
"creator": 183,
13+
"color": "#32a852",
14+
"pretty_id": "6",
15+
"multiple_owners": true,
16+
"folder_id": null,
17+
"members": [],
18+
"owners": [
19+
{
20+
"id": 182,
21+
"username": "Pieter CK",
22+
"email": "kwok.pieter@gmail.com",
23+
"color": "#7b68ee",
24+
"initials": "PK",
25+
"profilePicture": "https://attachments-public.clickup.com/profilePictures/182_abc.jpg"
26+
}
27+
],
28+
"key_results": [],
29+
"percent_completed": 0,
30+
"history": [],
31+
"pretty_url": "https://app.clickup.com/512/goals/6"
3232
}
33-
}
33+
}

zerver/webhooks/clickup/callback_fixtures/get_list.json

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,46 @@
44
"orderindex": 1,
55
"content": "Updated List Content",
66
"status": {
7-
"status": "red",
8-
"color": "#e50000",
9-
"hide_label": true
7+
"status": "red",
8+
"color": "#e50000",
9+
"hide_label": true
1010
},
1111
"priority": {
12-
"priority": "high",
13-
"color": "#f50000"
12+
"priority": "high",
13+
"color": "#f50000"
1414
},
1515
"assignee": null,
1616
"due_date": "1567780450202",
1717
"due_date_time": true,
1818
"start_date": null,
1919
"start_date_time": null,
2020
"folder": {
21-
"id": "456",
22-
"name": "Folder Name",
23-
"hidden": false,
24-
"access": true
21+
"id": "456",
22+
"name": "Folder Name",
23+
"hidden": false,
24+
"access": true
2525
},
2626
"space": {
27-
"id": "789",
28-
"name": "Space Name",
29-
"access": true
27+
"id": "789",
28+
"name": "Space Name",
29+
"access": true
3030
},
3131
"inbound_address": "add.task.124.ac725f.31518a6a-05bb-4997-92a6-1dcfe2f527ca@tasks.clickup.com",
3232
"archived": false,
3333
"override_statuses": false,
3434
"statuses": [
35-
{
36-
"status": "to do",
37-
"orderindex": 0,
38-
"color": "#d3d3d3",
39-
"type": "open"
40-
},
41-
{
42-
"status": "complete",
43-
"orderindex": 1,
44-
"color": "#6bc950",
45-
"type": "closed"
46-
}
35+
{
36+
"status": "to do",
37+
"orderindex": 0,
38+
"color": "#d3d3d3",
39+
"type": "open"
40+
},
41+
{
42+
"status": "complete",
43+
"orderindex": 1,
44+
"color": "#6bc950",
45+
"type": "closed"
46+
}
4747
],
4848
"permission_level": "create"
49-
}
49+
}

zerver/webhooks/clickup/callback_fixtures/get_space.json

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,50 @@
33
"name": "the Milky Way",
44
"private": false,
55
"statuses": [
6-
{
7-
"status": "to do",
8-
"type": "open",
9-
"orderindex": 0,
10-
"color": "#d3d3d3"
11-
},
12-
{
13-
"status": "complete",
14-
"type": "closed",
15-
"orderindex": 1,
16-
"color": "#6bc950"
17-
}
6+
{
7+
"status": "to do",
8+
"type": "open",
9+
"orderindex": 0,
10+
"color": "#d3d3d3"
11+
},
12+
{
13+
"status": "complete",
14+
"type": "closed",
15+
"orderindex": 1,
16+
"color": "#6bc950"
17+
}
1818
],
1919
"multiple_assignees": false,
2020
"features": {
21-
"due_dates": {
22-
"enabled": false,
23-
"start_date": false,
24-
"remap_due_dates": false,
25-
"remap_closed_due_date": false
26-
},
27-
"time_tracking": {
28-
"enabled": false
29-
},
30-
"tags": {
31-
"enabled": false
32-
},
33-
"time_estimates": {
34-
"enabled": false
35-
},
36-
"checklists": {
37-
"enabled": true
38-
},
39-
"custom_fields": {
40-
"enabled": true
41-
},
42-
"remap_dependencies": {
43-
"enabled": false
44-
},
45-
"dependency_warning": {
46-
"enabled": false
47-
},
48-
"portfolios": {
49-
"enabled": false
50-
}
21+
"due_dates": {
22+
"enabled": false,
23+
"start_date": false,
24+
"remap_due_dates": false,
25+
"remap_closed_due_date": false
26+
},
27+
"time_tracking": {
28+
"enabled": false
29+
},
30+
"tags": {
31+
"enabled": false
32+
},
33+
"time_estimates": {
34+
"enabled": false
35+
},
36+
"checklists": {
37+
"enabled": true
38+
},
39+
"custom_fields": {
40+
"enabled": true
41+
},
42+
"remap_dependencies": {
43+
"enabled": false
44+
},
45+
"dependency_warning": {
46+
"enabled": false
47+
},
48+
"portfolios": {
49+
"enabled": false
50+
}
5151
}
52-
}
52+
}

0 commit comments

Comments
 (0)