webhooks: Add Clubhouse integration.

This commit is contained in:
Eeshan Garg 2018-06-18 21:24:57 -02:30 committed by Tim Abbott
parent e7a172293f
commit 061e760d1e
40 changed files with 1627 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -282,6 +282,7 @@ WEBHOOK_INTEGRATIONS = [
legacy=True
),
WebhookIntegration('circleci', ['continuous-integration'], display_name='CircleCI'),
WebhookIntegration('clubhouse', ['project-management']),
WebhookIntegration('codeship', ['continuous-integration', 'deployment']),
WebhookIntegration('crashlytics', ['monitoring']),
WebhookIntegration('dialogflow', ['customer-support'], display_name='Dialogflow'),

View File

View File

@ -0,0 +1,16 @@
Get Zulip notifications for your Clubhouse Stories and Epics!
1. {!create-stream.md!}
1. {!create-bot-contruct-url-indented.md!}
1. Go to your Clubhouse Dashboard, and click on the settings icon in
the top-right corner. Click on **Integrations**, and select **Webhooks**.
Click **+ Add New Webhook**.
1. Set **Payload URL** to the URL constructed above, and click
**Add New Webhook**.
{!congrats.md!}
![](/static/images/integrations/clubhouse/001.png)

View File

@ -0,0 +1,30 @@
{
"id":"5b295f25-6212-40e3-912d-dee6ce7cb0c2",
"changed_at":"2018-06-19T19:53:09.576Z",
"primary_id":20,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":20,
"entity_type":"epic",
"action":"create",
"name":"New Epic!",
"follower_ids":[
"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
],
"requested_by_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"state":"to do",
"description":"",
"epic_state_id":500000002
}
],
"references":[
{
"id":500000002,
"entity_type":"epic-state",
"name":"to do",
"type":"unstarted"
}
]
}

View File

@ -0,0 +1,69 @@
{
"id":"5b2962a4-6e8e-4a80-8a41-0e2be62064b5",
"changed_at":"2018-06-19T20:08:04.359Z",
"primary_id":23,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":23,
"entity_type":"story",
"action":"create",
"app_url":"https://app.clubhouse.io/zulip/story/23",
"description":"",
"story_type":"feature",
"name":"An epic story!",
"epic_id":20,
"requested_by_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"workflow_state_id":500000008,
"object_story_link_ids":[
500000023
],
"follower_ids":[
"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
],
"project_id":6
},
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add super cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"subject_story_link_ids":{
"adds":[
500000023
]
}
}
},
{
"id":500000023,
"entity_type":"story-link",
"action":"create",
"subject_id":11,
"verb":"relates to",
"object_id":23
}
],
"references":[
{
"id":6,
"entity_type":"project",
"name":"Backend"
},
{
"id":20,
"entity_type":"epic",
"name":"New Cool Epic!"
},
{
"id":500000008,
"entity_type":"workflow-state",
"name":"Unscheduled",
"type":"unstarted"
}
]
}

View File

@ -0,0 +1,29 @@
{
"id":"5b2960f1-0aed-46bc-8ef5-8c983f7ef3c1",
"changed_at":"2018-06-19T20:00:49.707Z",
"primary_id":21,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":21,
"entity_type":"epic-comment",
"action":"create",
"text":"Added a comment on this Epic!",
"author_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
},
{
"id":20,
"entity_type":"epic",
"action":"update",
"name":"New Cool Epic!",
"changes":{
"comment_ids":{
"adds":[
21
]
}
}
}
]
}

View File

@ -0,0 +1,21 @@
{
"id":"5b29602a-4906-48d7-a30a-c6feaa938eaa",
"changed_at":"2018-06-19T19:57:30.443Z",
"primary_id":20,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":20,
"entity_type":"epic",
"action":"update",
"name":"New Cool Epic!",
"changes":{
"description":{
"new":"Added a description!",
"old":""
}
}
}
]
}

View File

@ -0,0 +1,21 @@
{
"id":"5b29605e-df5c-4d9a-b37c-d89c9d59400e",
"changed_at":"2018-06-19T19:58:22.497Z",
"primary_id":20,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":20,
"entity_type":"epic",
"action":"update",
"name":"New Cool Epic!",
"changes":{
"description":{
"new":"Changed a description!",
"old":"Added a description!"
}
}
}
]
}

View File

@ -0,0 +1,46 @@
{
"id":"5b296195-5a9d-450f-9b1f-85fe8f4de79e",
"changed_at":"2018-06-19T20:03:33.124Z",
"primary_id":20,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":20,
"entity_type":"epic",
"action":"update",
"name":"New Cool Epic!",
"changes":{
"started_at":{
"new":"2018-06-19T20:03:33Z"
},
"state":{
"new":"in progress",
"old":"to do"
},
"epic_state_id":{
"new":500000003,
"old":500000002
},
"started":{
"new":true,
"old":false
}
}
}
],
"references":[
{
"id":500000003,
"entity_type":"epic-state",
"name":"in progress",
"type":"started"
},
{
"id":500000002,
"entity_type":"epic-state",
"name":"to do",
"type":"unstarted"
}
]
}

View File

@ -0,0 +1,21 @@
{
"id":"5b295f9e-171b-4d5c-bb45-2b709eb0ad4f",
"changed_at":"2018-06-19T19:55:10.245Z",
"primary_id":20,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":20,
"entity_type":"epic",
"action":"update",
"name":"New Cool Epic!",
"changes":{
"name":{
"new":"New Cool Epic!",
"old":"New Epic!"
}
}
}
]
}

View File

@ -0,0 +1,21 @@
{
"id":"5b296099-86f1-493c-a2c7-ad3b6a8f2151",
"changed_at":"2018-06-19T19:59:21.194Z",
"primary_id":20,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":20,
"entity_type":"epic",
"action":"update",
"name":"New Cool Epic!",
"changes":{
"description":{
"new":"",
"old":"Changed a description!"
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b2961f2-a2d0-449c-a4da-e9db7c69e10c",
"changed_at":"2018-06-19T20:05:06.722Z",
"primary_id":9,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":9,
"entity_type":"story",
"action":"update",
"name":"Story 2",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/9",
"changes":{
"archived":{
"new":true,
"old":false
}
}
}
]
}

View File

@ -0,0 +1,37 @@
{
"id":"5b1fdcea-4b6a-4128-9f78-7e58214191fc",
"changed_at":"2018-06-12T14:47:06.221Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"create",
"name":"Add cool feature!",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"project_id":6,
"description":"We should probably add this cool feature!",
"workflow_state_id":500000008,
"follower_ids":[
"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
],
"requested_by_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
}
],
"references":[
{
"id":6,
"entity_type":"project",
"name":"Backend"
},
{
"id":500000008,
"entity_type":"workflow-state",
"name":"Unscheduled",
"type":"unstarted"
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b1fe806-f582-4b45-ac64-44cc7567a5d7",
"changed_at":"2018-06-12T15:34:30.005Z",
"primary_id":15,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":15,
"entity_type":"story-task",
"action":"update",
"description":"A new task for this story",
"story_id":11,
"changes":{
"complete":{
"new":true,
"old":false
}
}
}
],
"references":[
{
"id":11,
"entity_type":"story",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11"
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b1fe66d-25b8-4838-9880-9a4169d369d7",
"changed_at":"2018-06-12T15:27:41.816Z",
"primary_id":14,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":14,
"entity_type":"story-task",
"action":"create",
"description":"Added a new task",
"complete":false
},
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"task_ids":{
"adds":[
14
]
}
}
}
]
}

View File

@ -0,0 +1,30 @@
{
"id":"5b1fe779-e8d0-4a4d-a8d3-3d179da7b55e",
"changed_at":"2018-06-12T15:32:09.246Z",
"primary_id":14,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":14,
"entity_type":"story-task",
"action":"delete",
"description":"Added a new task"
},
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"task_ids":{
"removes":[
14
]
}
}
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b1fe806-f582-4b45-ac64-44cc7567a5d7",
"changed_at":"2018-06-12T15:34:30.005Z",
"primary_id":15,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":15,
"entity_type":"story-task",
"action":"update",
"description":"A new task for this story",
"story_id":11,
"changes":{
"complete":{
"new":false,
"old":true
}
}
}
],
"references":[
{
"id":11,
"entity_type":"story",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11"
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b2961f2-a2d0-449c-a4da-e9db7c69e10c",
"changed_at":"2018-06-19T20:05:06.722Z",
"primary_id":9,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":9,
"entity_type":"story",
"action":"update",
"name":"Story 2",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/9",
"changes":{
"archived":{
"new":false,
"old":true
}
}
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b1fdf0a-80c2-4c95-907b-899efa86374f",
"changed_at":"2018-06-12T14:56:10.984Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"file_ids":{
"adds":[
13
]
}
}
}
],
"references":[
{
"id":13,
"entity_type":"file",
"name":"zuliprc"
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b1fdde6-c65d-4a20-a748-ec6d55bca140",
"changed_at":"2018-06-12T14:51:18.822Z",
"primary_id":12,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":12,
"entity_type":"story-comment",
"action":"create",
"text":"Just leaving a comment here!",
"author_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
},
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"comment_ids":{
"adds":[
12
]
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b1feaa3-b3ad-40ec-b6a7-732febad698f",
"changed_at":"2018-06-12T15:45:39.951Z",
"primary_id":9,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":9,
"entity_type":"story",
"action":"update",
"name":"Story 2",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/9",
"changes":{
"description":{
"new":"Added a description.",
"old":""
}
}
}
]
}

View File

@ -0,0 +1,29 @@
{
"id":"5b1fe052-2ebf-4efd-92f7-64ecdb555af7",
"changed_at":"2018-06-12T15:01:38.093Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"feature",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"epic_id":{
"new":7
}
}
}
],
"references":[
{
"id":7,
"entity_type":"epic",
"name":"Release 1.9"
}
]
}

View File

@ -0,0 +1,22 @@
{
"id":"5b1ff439-0a94-454d-9aa5-a1192f6a8603",
"changed_at":"2018-06-12T16:26:33.129Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"estimate":{
"new":4
}
}
}
]
}

View File

@ -0,0 +1,30 @@
{
"id":"5b295c2c-f734-4a1a-a3a5-03c2594ef2d8",
"changed_at":"2018-06-19T19:40:28.185Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"label_ids":{
"adds":[
18
]
}
}
},
{
"id":18,
"entity_type":"label",
"action":"create",
"name":"mockup"
}
]
}

View File

@ -0,0 +1,35 @@
{
"id":"5b1fefb0-45f1-4e20-bacb-b8038867fa54",
"changed_at":"2018-06-12T16:07:12.821Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"epic_id":{
"new":17,
"old":7
}
}
}
],
"references":[
{
"id":17,
"entity_type":"epic",
"name":"Clubhouse Fork"
},
{
"id":7,
"entity_type":"epic",
"name":"Release 1.9"
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b1ff494-3127-424b-bf77-bbdd17a5a59d",
"changed_at":"2018-06-12T16:28:04.923Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"estimate":{
"new":4,
"old":1
}
}
}
]
}

View File

@ -0,0 +1,35 @@
{
"id":"5b1fed7e-3dc6-47e5-b6af-8820bf6e5554",
"changed_at":"2018-06-12T15:57:50.741Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"project_id":{
"new":16,
"old":6
}
}
}
],
"references":[
{
"id":16,
"entity_type":"project",
"name":"Devops"
},
{
"id":6,
"entity_type":"project",
"name":"Backend"
}
]
}

View File

@ -0,0 +1,49 @@
{
"id":"5b1fe334-1258-408e-85eb-8ecb6acdcc82",
"changed_at":"2018-06-12T15:13:56.523Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"started":{
"new":true,
"old":false
},
"workflow_state_id":{
"new":500000010,
"old":500000008
},
"started_at":{
"new":"2018-06-12T15:13:56Z"
},
"owner_ids":{
"adds":[
"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
]
}
}
}
],
"references":[
{
"id":500000010,
"entity_type":"workflow-state",
"name":"Ready for Review",
"type":"started"
},
{
"id":500000008,
"entity_type":"workflow-state",
"name":"Unscheduled",
"type":"unstarted"
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b295e00-4bff-401e-ac41-23408c878ec9",
"changed_at":"2018-06-19T19:48:16.817Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add super cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"name":{
"new":"Add super cool feature!",
"old":"Add cool feature!"
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b1fe22b-b0b4-4bd4-b73f-c89eedfbfbbe",
"changed_at":"2018-06-12T15:09:31.659Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"story_type":{
"new":"bug",
"old":"feature"
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b1fe4a4-0402-4f06-af4c-0d676e8f0e23",
"changed_at":"2018-06-12T15:20:04.324Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"description":{
"new":"We should probably add this cool feature! Just edited this. :)",
"old":"We should probably add this cool feature!"
}
}
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b295afb-47df-420b-953d-62ec73ab043d",
"changed_at":"2018-06-19T19:35:23.772Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"file_ids":{
"removes":[
13
]
}
}
}
],
"references":[
{
"id":13,
"entity_type":"file",
"name":"zuliprc"
}
]
}

View File

@ -0,0 +1,23 @@
{
"id":"5b1ff306-741f-4084-a544-be7c0b078ebc",
"changed_at":"2018-06-12T16:21:26.472Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"description":{
"new":"",
"old":"We should probably add this cool feature! Just edited this. :)"
}
}
}
]
}

View File

@ -0,0 +1,29 @@
{
"id":"5b1feccb-fb21-4c00-ab51-999728f8c2c1",
"changed_at":"2018-06-12T15:54:51.986Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"epic_id":{
"old":7
}
}
}
],
"references":[
{
"id":7,
"entity_type":"epic",
"name":"Release 1.9"
}
]
}

View File

@ -0,0 +1,22 @@
{
"id":"5b1ff466-e71a-48f6-9cf6-5fb021c65894",
"changed_at":"2018-06-12T16:27:18.704Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"estimate":{
"old":4
}
}
}
]
}

View File

@ -0,0 +1,31 @@
{
"id":"5b295cc0-242a-4ecf-b75c-e2467c25513c",
"changed_at":"2018-06-19T19:42:56.825Z",
"primary_id":11,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":11,
"entity_type":"story",
"action":"update",
"name":"Add cool feature!",
"story_type":"bug",
"app_url":"https://app.clubhouse.io/zulip/story/11",
"changes":{
"label_ids":{
"removes":[
18
]
}
}
}
],
"references":[
{
"id":18,
"entity_type":"label",
"name":"mockup"
}
]
}

View File

@ -0,0 +1,185 @@
from mock import MagicMock, patch
from zerver.lib.test_classes import WebhookTestCase
class ClubhouseWebhookTest(WebhookTestCase):
STREAM_NAME = 'clubhouse'
URL_TEMPLATE = "/api/v1/external/clubhouse?stream={stream}&api_key={api_key}"
FIXTURE_DIR_NAME = 'clubhouse'
def test_story_create(self) -> None:
expected_message = u"New story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) of type **feature** was created."
self.send_and_test_stream_message(
'story_create', "Add cool feature!",
expected_message
)
def test_epic_story_create(self) -> None:
expected_message = u"New story [An epic story!](https://app.clubhouse.io/zulip/story/23) was created and added to the epic **New Cool Epic!**."
self.send_and_test_stream_message(
'epic_create_story', "An epic story!",
expected_message
)
def test_story_archive(self) -> None:
expected_message = u"The story [Story 2](https://app.clubhouse.io/zulip/story/9) was archived."
self.send_and_test_stream_message('story_archive', "Story 2", expected_message)
def test_story_unarchive(self) -> None:
expected_message = u"The story [Story 2](https://app.clubhouse.io/zulip/story/9) was unarchived."
self.send_and_test_stream_message('story_unarchive', "Story 2", expected_message)
def test_epic_create(self) -> None:
expected_message = u"New epic **New Epic!**(to do) was created."
self.send_and_test_stream_message('epic_create', "New Epic!", expected_message)
def test_epic_update_add_comment(self) -> None:
expected_message = u"New comment added to the epic **New Cool Epic!**:\n``` quote\nAdded a comment on this Epic!\n```"
self.send_and_test_stream_message('epic_update_add_comment',
"New Cool Epic!", expected_message)
def test_story_update_add_comment(self) -> None:
expected_message = u"New comment added to the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11):\n``` quote\nJust leaving a comment here!\n```"
self.send_and_test_stream_message('story_update_add_comment',
"Add cool feature!",
expected_message)
def test_epic_update_add_description(self) -> None:
expected_message = u"New description added to the epic **New Cool Epic!**:\n``` quote\nAdded a description!\n```"
self.send_and_test_stream_message('epic_update_add_description',
"New Cool Epic!", expected_message)
def test_epic_update_remove_description(self) -> None:
expected_message = u"Description for the epic **New Cool Epic!** was removed."
self.send_and_test_stream_message('epic_update_remove_description',
"New Cool Epic!", expected_message)
def test_epic_update_change_description(self) -> None:
expected_message = u"Description for the epic **New Cool Epic!** was changed from:\n``` quote\nAdded a description!\n```\nto\n``` quote\nChanged a description!\n```"
self.send_and_test_stream_message('epic_update_change_description',
"New Cool Epic!", expected_message)
def test_story_update_add_description(self) -> None:
expected_message = u"New description added to the story [Story 2](https://app.clubhouse.io/zulip/story/9):\n``` quote\nAdded a description.\n```"
self.send_and_test_stream_message('story_update_add_description',
"Story 2", expected_message)
def test_story_update_remove_description(self) -> None:
expected_message = u"Description for the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was removed."
self.send_and_test_stream_message('story_update_remove_description',
"Add cool feature!", expected_message)
def test_story_update_change_description(self) -> None:
expected_message = u"Description for the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was changed from:\n``` quote\nWe should probably add this cool feature!\n```\nto\n``` quote\nWe should probably add this cool feature! Just edited this. :)\n```"
self.send_and_test_stream_message('story_update_description',
"Add cool feature!", expected_message)
def test_epic_update_change_state(self) -> None:
expected_message = u"State of the epic **New Cool Epic!** was changed from **to do** to **in progress**."
self.send_and_test_stream_message('epic_update_change_state',
"New Cool Epic!", expected_message)
def test_story_update_change_state(self) -> None:
expected_message = u"State of the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was changed from **Unscheduled** to **Ready for Review**."
self.send_and_test_stream_message('story_update_change_state',
"Add cool feature!", expected_message)
def test_epic_update_change_name(self) -> None:
expected_message = u"The name of the epic **New Cool Epic!** was changed from:\n``` quote\nNew Epic!\n```\nto\n``` quote\nNew Cool Epic!\n```"
self.send_and_test_stream_message('epic_update_change_title', "New Cool Epic!",
expected_message)
def test_story_update_change_name(self) -> None:
expected_message = u"The name of the story [Add super cool feature!](https://app.clubhouse.io/zulip/story/11) was changed from:\n``` quote\nAdd cool feature!\n```\nto\n``` quote\nAdd super cool feature!\n```"
self.send_and_test_stream_message('story_update_change_title', "Add super cool feature!",
expected_message)
def test_story_task_created(self) -> None:
expected_message = u"Task **Added a new task** was added to the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11)."
self.send_and_test_stream_message('story_task_create', "Add cool feature!",
expected_message)
def test_story_task_deleted(self) -> None:
expected_message = u"Task **Added a new task** was removed from the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11)."
self.send_and_test_stream_message('story_task_delete', "Add cool feature!",
expected_message)
def test_story_task_completed(self) -> None:
expected_message = u"Task **A new task for this story** ([Add cool feature!](https://app.clubhouse.io/zulip/story/11)) was completed. :tada:"
self.send_and_test_stream_message('story_task_complete', "Add cool feature!",
expected_message)
@patch('zerver.lib.webhooks.common.check_send_webhook_message')
def test_story_task_incomplete_ignore(
self, check_send_webhook_message_mock: MagicMock) -> None:
payload = self.get_body('story_task_not_complete')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)
def test_story_epic_changed(self) -> None:
expected_message = (u"The story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was moved from **Release 1.9**"
u" to **Clubhouse Fork**.")
self.send_and_test_stream_message('story_update_change_epic', "Add cool feature!",
expected_message)
def test_story_epic_added(self) -> None:
expected_message = u"The story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was added to the epic **Release 1.9**."
self.send_and_test_stream_message('story_update_add_epic', "Add cool feature!",
expected_message)
def test_story_epic_removed(self) -> None:
expected_message = u"The story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was removed from the epic **Release 1.9**."
self.send_and_test_stream_message('story_update_remove_epic', "Add cool feature!",
expected_message)
def test_story_estimate_changed(self) -> None:
expected_message = u"The estimate for the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was set to 4 points."
self.send_and_test_stream_message('story_update_change_estimate', "Add cool feature!",
expected_message)
def test_story_estimate_added(self) -> None:
expected_message = u"The estimate for the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was set to 4 points."
self.send_and_test_stream_message('story_update_add_estimate', "Add cool feature!",
expected_message)
def test_story_estimate_removed(self) -> None:
expected_message = u"The estimate for the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was set to *Unestimated*."
self.send_and_test_stream_message('story_update_remove_estimate', "Add cool feature!",
expected_message)
def test_story_file_attachment_added(self) -> None:
expected_message = u"A file attachment `zuliprc` was added to the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11)."
self.send_and_test_stream_message('story_update_add_attachment', "Add cool feature!",
expected_message)
@patch('zerver.lib.webhooks.common.check_send_webhook_message')
def test_story_file_attachment_removed_ignore(
self, check_send_webhook_message_mock: MagicMock) -> None:
payload = self.get_body('story_update_remove_attachment')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)
def test_story_label_added(self) -> None:
expected_message = u"The label **mockup** was added to the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11)."
self.send_and_test_stream_message('story_update_add_label', "Add cool feature!",
expected_message)
@patch('zerver.lib.webhooks.common.check_send_webhook_message')
def test_story_label_removed_ignore(
self, check_send_webhook_message_mock: MagicMock) -> None:
payload = self.get_body('story_update_remove_label')
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)
def test_story_update_project(self) -> None:
expected_message = u"The story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was moved from the **Backend** project to **Devops**."
self.send_and_test_stream_message('story_update_change_project', "Add cool feature!",
expected_message)
def test_story_update_type(self) -> None:
expected_message = u"The type of the story [Add cool feature!](https://app.clubhouse.io/zulip/story/11) was changed from **feature** to **bug**."
self.send_and_test_stream_message('story_update_change_type', "Add cool feature!",
expected_message)

View File

@ -0,0 +1,448 @@
from functools import partial
from typing import Any, Dict, Iterable, Optional
from django.http import HttpRequest, HttpResponse
from zerver.decorator import api_key_only_webhook_view
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success
from zerver.models import UserProfile
EPIC_NAME_TEMPLATE = "**{name}**"
STORY_NAME_TEMPLATE = "[{name}]({app_url})"
COMMENT_ADDED_TEMPLATE = "New comment added to the {entity} {name_template}:\n``` quote\n{text}\n```"
NEW_DESC_ADDED_TEMPLATE = "New description added to the {entity} {name_template}:\n``` quote\n{new}\n```"
DESC_CHANGED_TEMPLATE = ("Description for the {entity} {name_template} was changed from:\n"
"``` quote\n{old}\n```\nto\n``` quote\n{new}\n```")
DESC_REMOVED_TEMPLATE = "Description for the {entity} {name_template} was removed."
STATE_CHANGED_TEMPLATE = "State of the {entity} {name_template} was changed from **{old}** to **{new}**."
NAME_CHANGED_TEMPLATE = ("The name of the {entity} {name_template} was changed from:\n"
"``` quote\n{old}\n```\nto\n``` quote\n{new}\n```")
STORY_ARCHIVED_TEMPLATE = "The story {name_template} was {action}."
STORY_TASK_TEMPLATE = "Task **{task_description}** was {action} the story {name_template}."
STORY_TASK_COMPLETED_TEMPLATE = "Task **{task_description}** ({name_template}) was completed. :tada:"
STORY_ADDED_REMOVED_EPIC_TEMPLATE = ("The story {story_name_template} was {action} the"
" epic {epic_name_template}.")
STORY_EPIC_CHANGED_TEMPLATE = ("The story {story_name_template} was moved from {old_epic_name_template}"
" to {new_epic_name_template}.")
STORY_ESTIMATE_TEMPLATE = "The estimate for the story {story_name_template} was set to {estimate}."
FILE_ATTACHMENT_TEMPLATE = "A {type} attachment `{file_name}` was added to the story {name_template}."
STORY_LABEL_TEMPLATE = "The label **{label_name}** was added to the story {name_template}."
STORY_UPDATE_PROJECT_TEMPLATE = ("The story {name_template} was moved from"
" the **{old}** project to **{new}**.")
STORY_UPDATE_TYPE_TEMPLATE = ("The type of the story {name_template} was changed"
" from **{old_type}** to **{new_type}**.")
def get_action_with_primary_id(payload: Dict[str, Any]) -> Dict[str, Any]:
for action in payload["actions"]:
if payload["primary_id"] == action["id"]:
action_with_primary_id = action
return action_with_primary_id
def get_body_function_based_on_type(payload: Dict[str, Any]) -> Any:
action = get_action_with_primary_id(payload)
event = "{}_{}".format(action["entity_type"], action["action"])
changes = action.get("changes")
if changes is not None:
if changes.get("description") is not None:
event = "{}_{}".format(event, "description")
elif changes.get("state") is not None:
event = "{}_{}".format(event, "state")
elif changes.get("workflow_state_id") is not None:
event = "{}_{}".format(event, "state")
elif changes.get("name") is not None:
event = "{}_{}".format(event, "name")
elif changes.get("archived") is not None:
event = "{}_{}".format(event, "archived")
elif changes.get("complete") is not None:
event = "{}_{}".format(event, "complete")
elif changes.get("epic_id") is not None:
event = "{}_{}".format(event, "epic")
elif changes.get("estimate") is not None:
event = "{}_{}".format(event, "estimate")
elif changes.get("file_ids") is not None:
event = "{}_{}".format(event, "attachment")
elif changes.get("label_ids") is not None:
event = "{}_{}".format(event, "label")
elif changes.get("project_id") is not None:
event = "{}_{}".format(event, "project")
elif changes.get("story_type") is not None:
event = "{}_{}".format(event, "type")
return EVENT_BODY_FUNCTION_MAPPER.get(event)
def get_topic_function_based_on_type(payload: Dict[str, Any]) -> Any:
entity_type = get_action_with_primary_id(payload)["entity_type"]
return EVENT_TOPIC_FUNCTION_MAPPER.get(entity_type)
def get_story_create_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
if action.get("epic_id") is None:
message = "New story [{name}]({app_url}) of type **{story_type}** was created."
kwargs = action
else:
message = "New story [{name}]({app_url}) was created and added to the epic **{epic_name}**."
kwargs = {
"name": action["name"],
"app_url": action["app_url"],
}
epic_id = action["epic_id"]
refs = payload["references"]
for ref in refs:
if ref["id"] == epic_id:
kwargs["epic_name"] = ref["name"]
return message.format(**kwargs)
def get_epic_create_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
message = "New epic **{name}**({state}) was created."
return message.format(**action)
def get_comment_added_body(payload: Dict[str, Any], entity: str) -> str:
actions = payload["actions"]
kwargs = {"entity": entity}
for action in actions:
if action["id"] == payload["primary_id"]:
kwargs["text"] = action["text"]
elif action["entity_type"] == entity:
name_template = get_name_template(entity).format(
name=action["name"],
app_url=action.get("app_url")
)
kwargs["name_template"] = name_template
return COMMENT_ADDED_TEMPLATE.format(**kwargs)
def get_update_description_body(payload: Dict[str, Any], entity: str) -> str:
action = get_action_with_primary_id(payload)
desc = action["changes"]["description"]
kwargs = {
"entity": entity,
"new": desc["new"],
"old": desc["old"],
"name_template": get_name_template(entity).format(
name=action["name"],
app_url=action.get("app_url")
)
}
if kwargs["new"] and kwargs["old"]:
body = DESC_CHANGED_TEMPLATE.format(**kwargs)
elif kwargs["new"]:
body = NEW_DESC_ADDED_TEMPLATE.format(**kwargs)
else:
body = DESC_REMOVED_TEMPLATE.format(**kwargs)
return body
def get_epic_update_state_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
state = action["changes"]["state"]
kwargs = {
"entity": "epic",
"new": state["new"],
"old": state["old"],
"name_template": EPIC_NAME_TEMPLATE.format(name=action["name"])
}
return STATE_CHANGED_TEMPLATE.format(**kwargs)
def get_story_update_state_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
workflow_state_id = action["changes"]["workflow_state_id"]
references = payload["references"]
state = {}
for ref in references:
if ref["id"] == workflow_state_id["new"]:
state["new"] = ref["name"]
if ref["id"] == workflow_state_id["old"]:
state["old"] = ref["name"]
kwargs = {
"entity": "story",
"new": state["new"],
"old": state["old"],
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action.get("app_url"),
)
}
return STATE_CHANGED_TEMPLATE.format(**kwargs)
def get_update_name_body(payload: Dict[str, Any], entity: str) -> str:
action = get_action_with_primary_id(payload)
name = action["changes"]["name"]
kwargs = {
"entity": entity,
"new": name["new"],
"old": name["old"],
"name_template": get_name_template(entity).format(
name=action["name"],
app_url=action.get("app_url")
)
}
return NAME_CHANGED_TEMPLATE.format(**kwargs)
def get_story_update_archived_body(payload: Dict[str, Any]) -> str:
primary_action = get_action_with_primary_id(payload)
archived = primary_action["changes"]["archived"]
if archived["new"]:
action = "archived"
else:
action = "unarchived"
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=primary_action["name"],
app_url=primary_action.get("app_url")
),
"action": action,
}
return STORY_ARCHIVED_TEMPLATE.format(**kwargs)
def get_story_task_body(payload: Dict[str, Any], action: str) -> str:
primary_action = get_action_with_primary_id(payload)
kwargs = {
"task_description": primary_action["description"],
"action": action,
}
for a in payload["actions"]:
if a["entity_type"] == "story":
kwargs["name_template"] = STORY_NAME_TEMPLATE.format(
name=a["name"],
app_url=a["app_url"],
)
return STORY_TASK_TEMPLATE.format(**kwargs)
def get_story_task_completed_body(payload: Dict[str, Any]) -> Optional[str]:
action = get_action_with_primary_id(payload)
kwargs = {
"task_description": action["description"],
}
story_id = action["story_id"]
for ref in payload["references"]:
if ref["id"] == story_id:
kwargs["name_template"] = STORY_NAME_TEMPLATE.format(
name=ref["name"],
app_url=ref["app_url"],
)
if action["changes"]["complete"]["new"]:
return STORY_TASK_COMPLETED_TEMPLATE.format(**kwargs)
else:
return None
def get_story_update_epic_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
kwargs = {
"story_name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action["app_url"]
),
}
new_id = action["changes"]["epic_id"].get("new")
old_id = action["changes"]["epic_id"].get("old")
for ref in payload["references"]:
if ref["id"] == new_id:
kwargs["new_epic_name_template"] = EPIC_NAME_TEMPLATE.format(
name=ref["name"])
if ref["id"] == old_id:
kwargs["old_epic_name_template"] = EPIC_NAME_TEMPLATE.format(
name=ref["name"])
if new_id and old_id:
return STORY_EPIC_CHANGED_TEMPLATE.format(**kwargs)
elif new_id:
kwargs["epic_name_template"] = kwargs["new_epic_name_template"]
kwargs["action"] = "added to"
else:
kwargs["epic_name_template"] = kwargs["old_epic_name_template"]
kwargs["action"] = "removed from"
return STORY_ADDED_REMOVED_EPIC_TEMPLATE.format(**kwargs)
def get_story_update_estimate_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
kwargs = {
"story_name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action["app_url"]
),
}
new = action["changes"]["estimate"].get("new")
if new:
kwargs["estimate"] = "{} points".format(new)
else:
kwargs["estimate"] = "*Unestimated*"
return STORY_ESTIMATE_TEMPLATE.format(**kwargs)
def get_story_update_attachment_body(payload: Dict[str, Any]) -> Optional[str]:
action = get_action_with_primary_id(payload)
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action["app_url"]
)
}
file_ids_added = action["changes"]["file_ids"].get("adds")
# If this is a payload for when an attachment is removed, ignore it
if not file_ids_added:
return None
file_id = file_ids_added[0]
for ref in payload["references"]:
if ref["id"] == file_id:
kwargs.update({
"type": ref["entity_type"],
"file_name": ref["name"],
})
return FILE_ATTACHMENT_TEMPLATE.format(**kwargs)
def get_story_label_body(payload: Dict[str, Any]) -> Optional[str]:
action = get_action_with_primary_id(payload)
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action["app_url"]
)
}
label_ids_added = action["changes"]["label_ids"].get("adds")
# If this is a payload for when a label is removed, ignore it
if not label_ids_added:
return None
label_id = label_ids_added[0]
for action in payload["actions"]:
if action["id"] == label_id:
kwargs.update({"label_name": action["name"]})
return STORY_LABEL_TEMPLATE.format(**kwargs)
def get_story_update_project_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action["app_url"]
)
}
new_project_id = action["changes"]["project_id"]["new"]
old_project_id = action["changes"]["project_id"]["old"]
for ref in payload["references"]:
if ref["id"] == new_project_id:
kwargs.update({"new": ref["name"]})
if ref["id"] == old_project_id:
kwargs.update({"old": ref["name"]})
return STORY_UPDATE_PROJECT_TEMPLATE.format(**kwargs)
def get_story_update_type_body(payload: Dict[str, Any]) -> str:
action = get_action_with_primary_id(payload)
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"],
app_url=action["app_url"]
),
"new_type": action["changes"]["story_type"]["new"],
"old_type": action["changes"]["story_type"]["old"]
}
return STORY_UPDATE_TYPE_TEMPLATE.format(**kwargs)
def get_entity_name(payload: Dict[str, Any], entity: Optional[str]=None) -> Optional[str]:
name = get_action_with_primary_id(payload).get("name")
if name is None:
for action in payload["actions"]:
if action["entity_type"] == entity:
name = action["name"]
if name is None:
for ref in payload["references"]:
if ref["entity_type"] == entity:
name = ref["name"]
return name
def get_name_template(entity: str) -> str:
if entity == "story":
return STORY_NAME_TEMPLATE
return EPIC_NAME_TEMPLATE
EVENT_BODY_FUNCTION_MAPPER = {
"story_update_archived": get_story_update_archived_body,
"story_create": get_story_create_body,
"story-task_create": partial(get_story_task_body, action="added to"),
"story-task_delete": partial(get_story_task_body, action="removed from"),
"story-task_update_complete": get_story_task_completed_body,
"story_update_epic": get_story_update_epic_body,
"story_update_estimate": get_story_update_estimate_body,
"story_update_attachment": get_story_update_attachment_body,
"story_update_label": get_story_label_body,
"story_update_project": get_story_update_project_body,
"story_update_type": get_story_update_type_body,
"epic_create": get_epic_create_body,
"epic-comment_create": partial(get_comment_added_body, entity='epic'),
"story-comment_create": partial(get_comment_added_body, entity='story'),
"epic_update_description": partial(get_update_description_body, entity='epic'),
"story_update_description": partial(get_update_description_body, entity='story'),
"epic_update_state": get_epic_update_state_body,
"story_update_state": get_story_update_state_body,
"epic_update_name": partial(get_update_name_body, entity='epic'),
"story_update_name": partial(get_update_name_body, entity='story'),
}
EVENT_TOPIC_FUNCTION_MAPPER = {
"story": partial(get_entity_name, entity='story'),
"story-comment": partial(get_entity_name, entity='story'),
"story-task": partial(get_entity_name, entity='story'),
"epic": partial(get_entity_name, entity='epic'),
"epic-comment": partial(get_entity_name, entity='epic'),
}
@api_key_only_webhook_view('ClubHouse')
@has_request_variables
def api_clubhouse_webhook(
request: HttpRequest, user_profile: UserProfile,
payload: Dict[str, Any]=REQ(argument_type='body')
) -> HttpResponse:
body_func = get_body_function_based_on_type(payload)
topic_func = get_topic_function_based_on_type(payload)
topic = topic_func(payload)
body = body_func(payload)
if topic and body:
check_send_webhook_message(request, user_profile, topic, body)
return json_success()