snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,28 @@
# Zulip Shortcut integration
Get Zulip notifications for your Shortcut Stories and Epics!
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. Go to your Shortcut Dashboard, and click on your profile icon in the
top-right corner. Go to **Integrations**, and select **Webhooks**.
Click **+ Add New Webhook**.
1. Set **Payload URL** to the URL generated above, and click **Add New
Webhook**.
{end_tabs}
{!congrats.md!}
![](/static/images/integrations/shortcut/001.png)
{!event-filtering-additional-feature.md!}
### Related documentation
{!webhooks-url-specification.md!}

View File

@@ -0,0 +1,21 @@
{
"id":"5c6dd2b0-399b-43b9-9758-4d60771bad08",
"changed_at":"2019-02-20T22:20:32.285Z",
"primary_id":29,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":29,
"entity_type":"epic",
"action":"update",
"name":"Zulip is epic!",
"changes":{
"archived":{
"new":true,
"old":false
}
}
}
]
}

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.shortcut.com/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.shortcut.com/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,15 @@
{
"id":"5c1824b6-99eb-456b-a557-9fa9a56cedb0",
"changed_at":"2018-12-17T22:35:34.744Z",
"primary_id":17,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":17,
"entity_type":"epic",
"action":"delete",
"name":"Shortcut Fork"
}
]
}

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.shortcut.com/zulip/story/9",
"changes":{
"archived":{
"new":true,
"old":false
}
}
}
]
}

View File

@@ -0,0 +1,31 @@
{
"id":"5c6dcbd0-f055-4efb-83f9-d745a444aec2",
"changed_at":"2019-02-20T21:51:12.936Z",
"primary_id":12,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":12,
"entity_type":"story-comment",
"action":"update",
"text":"Just leaving a comment here! EDIT: Just editing this",
"story_id":11,
"changes":{
"text":{
"new":"Just leaving a comment here! EDIT: Just editing this",
"old":"Just leaving a comment here!"
}
}
}
],
"references":[
{
"id":11,
"entity_type":"story",
"name":"Add super cool feature!",
"story_type":"bug",
"app_url":"https://app.shortcut.com/zulip/story/11"
}
]
}

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.shortcut.com/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,16 @@
{
"id":"5c182454-1f6d-4c08-8d41-dfa5409386a2",
"changed_at":"2018-12-17T22:33:56.768Z",
"primary_id":25,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":25,
"entity_type":"story",
"action":"delete",
"story_type":"feature",
"name":"New random story"
}
]
}

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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/zulip/story/11",
"changes":{
"estimate":{
"new":4
}
}
}
]
}

View File

@@ -0,0 +1,57 @@
{
"id":"5c491cd1-53dd-4ccb-83a1-c5275eb3c234",
"changed_at":"2019-01-24T02:02:58.000Z",
"primary_id":500000029,
"version":"v1",
"actions":[
{
"id":500000029,
"entity_type":"branch",
"action":"create",
"name":"eeshangarg/ch27/testing-pull-requests-with-story",
"url":"https://github.com/eeshangarg/scheduler/tree/eeshangarg/ch27/testing-pull-requests-with-story"
},
{
"id":27,
"entity_type":"story",
"action":"update",
"name":"Testing pull requests with Story",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/27",
"changes":{
"branch_ids":{
"adds":[
500000029
]
},
"started":{
"new":true,
"old":false
},
"workflow_state_id":{
"new":500000006,
"old":500000008
},
"started_at":{
"new":"2019-01-24T02:02:57Z"
}
}
}
],
"references":[
{
"id":500000006,
"entity_type":"workflow-state",
"name":"In Development",
"type":"started"
},
{
"id":500000008,
"entity_type":"workflow-state",
"name":"Unscheduled",
"type":"unstarted"
}
],
"webhook_id":"5c491814-1d65-44c4-92af-aece6fc41092",
"external_id":"2b82a9e0-1f7c-11e9-89d2-03b6c67fe7a7"
}

View File

@@ -0,0 +1,97 @@
{
"id": "60718721-b87c-4405-96aa-38db82c693de",
"changed_at": "2021-04-10T11:08:17.962Z",
"version": "v1",
"primary_id": 500000032,
"actions": [
{
"id": 500000032,
"entity_type": "pull-request",
"action": "create",
"number": 2,
"title": "Testing pull requests with Story",
"url": "https://github.com/PIG208/test-clubhouse/pull/2"
},
{
"id": 500000031,
"entity_type": "branch",
"action": "create",
"name": "two",
"url": "https://github.com/PIG208/test-clubhouse/tree/two"
},
{
"id": 17,
"entity_type": "story",
"action": "update",
"name": "Story1",
"story_type": "feature",
"app_url": "https://app.shortcut.com/pig208/story/17",
"changes": {
"pull_request_ids": {
"adds": [
500000032
]
},
"started": {
"new": true,
"old": false
},
"position": {
"new": 32147680256,
"old": 12147614720
},
"workflow_state_id": {
"new": 500000006,
"old": 500000008
},
"started_at": {
"new": "2021-04-10T11:08:17Z"
}
}
},
{
"id": 18,
"entity_type": "story",
"action": "update",
"name": "Story2",
"story_type": "feature",
"app_url": "https://app.shortcut.com/pig208/story/18",
"changes": {
"pull_request_ids": {
"adds": [
500000032
]
},
"started": {
"new": true,
"old": false
},
"position": {
"new": 32147745792,
"old": 12147680256
},
"workflow_state_id": {
"new": 500000006,
"old": 500000008
},
"started_at": {
"new": "2021-04-10T11:08:17Z"
}
}
}
],
"references": [
{
"id": 500000006,
"entity_type": "workflow-state",
"name": "In Development",
"type": "started"
},
{
"id": 500000008,
"entity_type": "workflow-state",
"name": "Unscheduled",
"type": "unstarted"
}
]
}

View File

@@ -0,0 +1,90 @@
{
"id": "60721a44-7d30-476b-ae19-514a47359c42",
"changed_at": "2021-04-10T21:36:04.138Z",
"version": "v1",
"primary_id": 500000029,
"actions": [
{
"id": 500000029,
"entity_type": "pull-request",
"action": "comment",
"number": 1,
"title": "readme: Update README.md.",
"url": "https://github.com/PIG208/test-clubhouse/pull/1"
},
{
"id": 26,
"entity_type": "story",
"action": "update",
"name": "new1",
"story_type": "feature",
"app_url": "https://app.shortcut.com/pig208/story/26",
"changes": {
"pull_request_ids": {
"adds": [
500000029
]
},
"started": {
"new": true,
"old": false
},
"position": {
"new": 32147549184,
"old": 12147549184
},
"workflow_state_id": {
"new": 500000006,
"old": 500000008
},
"started_at": {
"new": "2021-04-10T21:36:03Z"
}
}
},
{
"id": 27,
"entity_type": "story",
"action": "update",
"name": "new2",
"story_type": "feature",
"app_url": "https://app.shortcut.com/pig208/story/27",
"changes": {
"pull_request_ids": {
"adds": [
500000029
]
},
"started": {
"new": true,
"old": false
},
"position": {
"new": 32147614720,
"old": 12147614720
},
"workflow_state_id": {
"new": 500000006,
"old": 500000008
},
"started_at": {
"new": "2021-04-10T21:36:03Z"
}
}
}
],
"references": [
{
"id": 500000006,
"entity_type": "workflow-state",
"name": "In Development",
"type": "started"
},
{
"id": 500000008,
"entity_type": "workflow-state",
"name": "Unscheduled",
"type": "unstarted"
}
]
}

View File

@@ -0,0 +1,59 @@
{
"changed_at":"2019-01-24T02:18:20.031Z",
"primary_id":500000035,
"references":[
{
"id":500000010,
"entity_type":"workflow-state",
"name":"Ready for Review",
"type":"started"
},
{
"id":500000008,
"entity_type":"workflow-state",
"name":"Unscheduled",
"type":"unstarted"
}
],
"actions":[
{
"id":500000035,
"entity_type":"pull-request",
"action":"create",
"number":10,
"title":"Testing pull requests with Story",
"url":"https://github.com/eeshangarg/Scheduler/pull/10"
},
{
"id":28,
"entity_type":"story",
"action":"update",
"name":"Testing pull requests with Story",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/28",
"changes":{
"started":{
"new":true,
"old":false
},
"pull_request_ids": {
"adds": [
500000032
]
},
"workflow_state_id":{
"new":500000010,
"old":500000008
},
"started_at":{
"new":"2019-01-24T02:18:20Z"
}
}
}
],
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"external_id":"50e81c40-1f7e-11e9-8645-17f96d5e1138",
"id":"5c49206c-506f-4312-a1b1-b1b8bfccbc9b",
"version":"v1",
"webhook_id":"5c491814-1d65-44c4-92af-aece6fc41092"
}

View File

@@ -0,0 +1,32 @@
{
"id": "6071f11a-1681-41f8-afa8-fd24ae747a5d",
"changed_at": "2021-04-10T18:40:26.876Z",
"version": "v1",
"primary_id": 500000032,
"actions": [
{
"id": 500000032,
"entity_type": "pull-request",
"action": "comment",
"number": 2,
"title": "Two",
"url": "https://github.com/PIG208/test-clubhouse/pull/2"
},
{
"id": 15,
"entity_type": "story",
"action": "update",
"name": "asd2",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/15",
"changes":
{
"pull_request_ids": {
"adds": [
500000032
]
}
}
}
]
}

View File

@@ -0,0 +1,55 @@
{
"changed_at":"2019-01-24T02:18:20.031Z",
"primary_id":500000035,
"references":[
{
"id":500000010,
"entity_type":"workflow-state",
"name":"Ready for Review",
"type":"started"
},
{
"id":500000008,
"entity_type":"workflow-state",
"name":"Unscheduled",
"type":"unstarted"
}
],
"actions":[
{
"id":500000035,
"entity_type":"pull-request",
"action":"create",
"number":10,
"title":"Testing pull requests with Story",
"url":"https://github.com/eeshangarg/Scheduler/pull/10"
},
{
"id":28,
"entity_type":"story",
"action":"update",
"name":"Testing pull requests with Story",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/28",
"changes":{
"started":{
"new":true,
"old":false
},
"pull_request_ids": {
"adds": [
500000035
]
},
"started_at":{
"new":"2019-01-24T02:18:20Z"
}
}
}
],
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"external_id":"50e81c40-1f7e-11e9-8645-17f96d5e1138",
"id":"5c49206c-506f-4312-a1b1-b1b8bfccbc9b",
"version":"v1",
"webhook_id":"5c491814-1d65-44c4-92af-aece6fc41092"
}

View File

@@ -0,0 +1,31 @@
{
"id":"5c37d3a3-6e3d-4b17-963c-adfa0a023d84",
"changed_at":"2019-01-10T23:22:11.694Z",
"primary_id":23,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":23,
"entity_type":"story",
"action":"update",
"name":"An epic story!",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/23",
"changes":{
"label_ids":{
"adds":[
18
]
}
}
}
],
"references":[
{
"id":18,
"entity_type":"label",
"name":"mockup"
}
]
}

View File

@@ -0,0 +1,30 @@
{
"member_id":"5bc77d81-sdkf-23fk-sdkf-bfea45f0b806",
"version":"v1",
"primary_id":11200,
"id":"5c488ff2-d76e-4b34-8cdb-4f6098ae79ec",
"actions":[
{
"action":"update",
"story_type":"chore",
"entity_type":"story",
"changes":{
"label_ids":{
"adds":[
12135
]
}
},
"id":11200,
"app_url":"https:\/\/app.shortcut.com\/zulip\/story\/28",
"name":"An emotional story!"
},
{
"action":"create",
"id":12135,
"entity_type":"label",
"name":"sad"
}
],
"changed_at":"2019-01-23T16:01:54.470Z"
}

View File

@@ -0,0 +1,37 @@
{
"id":"5c37d3a3-6e3d-4b17-963c-adfa0a023d84",
"changed_at":"2019-01-10T23:22:11.694Z",
"primary_id":23,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":23,
"entity_type":"story",
"action":"update",
"name":"An epic story!",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/23",
"changes":{
"label_ids":{
"adds":[
18,
19
]
}
}
}
],
"references":[
{
"id":18,
"entity_type":"label",
"name":"mockup"
},
{
"id":19,
"entity_type":"label",
"name":"label"
}
]
}

View File

@@ -0,0 +1,24 @@
{
"id":"5c2fd1dc-7156-4427-8d55-cd6dfbc3ddc9",
"changed_at":"2019-01-04T21:36:29.000Z",
"primary_id":26,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":26,
"entity_type":"story",
"action":"update",
"name":"A new story by Shakespeare!",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/26",
"changes":{
"owner_ids":{
"adds":[
"5b1fda16-626f-487f-9ecf-f3a4abf42b8b"
]
}
}
}
]
}

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.shortcut.com/zulip/story/11",
"changes":{
"epic_id":{
"new":17,
"old":7
}
}
}
],
"references":[
{
"id":17,
"entity_type":"epic",
"name":"Shortcut 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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/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,233 @@
{
"id": "60723fdc-2c6d-4b31-b160-ef4d438dc5bc",
"changed_at": "2021-04-11T00:16:28.845Z",
"version": "v1",
"member_id": "6071752f-e16e-4f79-b41e-7c78b76aa4bd",
"actions": [
{
"id": 17,
"entity_type": "story",
"action": "update",
"name": "asd4",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/17",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
},
"epic_id": {
"new": 23,
"old": 29
},
"requested_by_id": {
"new": "60723f5f-28ca-4ec2-a3a2-37b2dc5606ae",
"old": "6071752f-e16e-4f79-b41e-7c78b76aa4bd"
},
"label_ids": {
"adds": [
8
]
},
"group_id": {
"new": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"old": "6071752f-ece0-4772-854c-05a9666c480f"
},
"workflow_state_id": {
"new": 500000010,
"old": 500000006
},
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"position": {
"new": 42147811328,
"old": 32147483648
},
"project_id": {
"new": 28,
"old": 2
},
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
},
{
"id": 26,
"entity_type": "story",
"action": "update",
"name": "new1",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/26",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
},
"epic_id": {
"new": 23,
"old": 29
},
"requested_by_id": {
"new": "60723f5f-28ca-4ec2-a3a2-37b2dc5606ae",
"old": "6071752f-e16e-4f79-b41e-7c78b76aa4bd"
},
"label_ids": {
"adds": [
8
]
},
"group_id": {
"new": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"old": "6071752f-ece0-4772-854c-05a9666c480f"
},
"workflow_state_id": {
"new": 500000010,
"old": 500000006
},
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"position": {
"new": 42147942400,
"old": 32147680256
},
"project_id": {
"new": 28,
"old": 2
},
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
},
{
"id": 27,
"entity_type": "story",
"action": "update",
"name": "new2",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/27",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
},
"epic_id": {
"new": 23,
"old": 29
},
"requested_by_id": {
"new": "60723f5f-28ca-4ec2-a3a2-37b2dc5606ae",
"old": "6071752f-e16e-4f79-b41e-7c78b76aa4bd"
},
"label_ids": {
"adds": [
8
]
},
"group_id": {
"new": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"old": "6071752f-ece0-4772-854c-05a9666c480f"
},
"workflow_state_id": {
"new": 500000010,
"old": 500000006
},
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"position": {
"new": 42147876864,
"old": 32147614720
},
"project_id": {
"new": 28,
"old": 2
},
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
}
],
"references": [
{
"id": "6071752f-ece0-4772-854c-05a9666c480f",
"entity_type": "group",
"name": "Team 1"
},
{
"id": 500000010,
"entity_type": "workflow-state",
"name": "Ready for Review",
"type": "started"
},
{
"id": 500000006,
"entity_type": "workflow-state",
"name": "In Development",
"type": "started"
},
{
"id": 8,
"entity_type": "label",
"name": "low priority",
"app_url": "https://app.shortcut.com/pig208/label/8"
},
{
"id": 23,
"entity_type": "epic",
"name": "testeipc",
"app_url": "https://app.shortcut.com/pig208/epic/23"
},
{
"id": 2,
"entity_type": "project",
"name": "Product Development",
"app_url": "https://app.shortcut.com/pig208/project/2"
},
{
"id": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"entity_type": "group",
"name": "team2"
},
{
"id": 29,
"entity_type": "epic",
"name": "epic",
"app_url": "https://app.shortcut.com/pig208/epic/29"
},
{
"id": 28,
"entity_type": "project",
"name": "test2",
"app_url": "https://app.shortcut.com/pig208/project/28"
}
]
}

View File

@@ -0,0 +1,233 @@
{
"id": "60723fdc-2c6d-4b31-b160-ef4d438dc5bc",
"changed_at": "2021-04-11T00:16:28.845Z",
"version": "v1",
"member_id": "6071752f-e16e-4f79-b41e-7c78b76aa4bd",
"actions": [
{
"id": 17,
"entity_type": "story",
"action": "update",
"name": "asd4",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/17",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
},
"epic_id": {
"new": 23,
"old": 29
},
"requested_by_id": {
"new": "60723f5f-28ca-4ec2-a3a2-37b2dc5606ae",
"old": "6071752f-e16e-4f79-b41e-7c78b76aa4bd"
},
"label_ids": {
"removes": [
8
]
},
"group_id": {
"new": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"old": "6071752f-ece0-4772-854c-05a9666c480f"
},
"workflow_state_id": {
"new": 500000010,
"old": 500000006
},
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"position": {
"new": 42147811328,
"old": 32147483648
},
"project_id": {
"new": 28,
"old": 2
},
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
},
{
"id": 26,
"entity_type": "story",
"action": "update",
"name": "new1",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/26",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
},
"epic_id": {
"new": 23,
"old": 29
},
"requested_by_id": {
"new": "60723f5f-28ca-4ec2-a3a2-37b2dc5606ae",
"old": "6071752f-e16e-4f79-b41e-7c78b76aa4bd"
},
"label_ids": {
"removes": [
8
]
},
"group_id": {
"new": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"old": "6071752f-ece0-4772-854c-05a9666c480f"
},
"workflow_state_id": {
"new": 500000010,
"old": 500000006
},
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"position": {
"new": 42147942400,
"old": 32147680256
},
"project_id": {
"new": 28,
"old": 2
},
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
},
{
"id": 27,
"entity_type": "story",
"action": "update",
"name": "new2",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/27",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
},
"epic_id": {
"new": 23,
"old": 29
},
"requested_by_id": {
"new": "60723f5f-28ca-4ec2-a3a2-37b2dc5606ae",
"old": "6071752f-e16e-4f79-b41e-7c78b76aa4bd"
},
"label_ids": {
"removes": [
8
]
},
"group_id": {
"new": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"old": "6071752f-ece0-4772-854c-05a9666c480f"
},
"workflow_state_id": {
"new": 500000010,
"old": 500000006
},
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
},
"position": {
"new": 42147876864,
"old": 32147614720
},
"project_id": {
"new": 28,
"old": 2
},
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
}
],
"references": [
{
"id": "6071752f-ece0-4772-854c-05a9666c480f",
"entity_type": "group",
"name": "Team 1"
},
{
"id": 500000010,
"entity_type": "workflow-state",
"name": "Ready for Review",
"type": "started"
},
{
"id": 500000006,
"entity_type": "workflow-state",
"name": "In Development",
"type": "started"
},
{
"id": 8,
"entity_type": "label",
"name": "low priority",
"app_url": "https://app.shortcut.com/pig208/label/8"
},
{
"id": 23,
"entity_type": "epic",
"name": "testeipc",
"app_url": "https://app.shortcut.com/pig208/epic/23"
},
{
"id": 2,
"entity_type": "project",
"name": "Product Development",
"app_url": "https://app.shortcut.com/pig208/project/2"
},
{
"id": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"entity_type": "group",
"name": "team2"
},
{
"id": 29,
"entity_type": "epic",
"name": "epic",
"app_url": "https://app.shortcut.com/pig208/epic/29"
},
{
"id": 28,
"entity_type": "project",
"name": "test2",
"app_url": "https://app.shortcut.com/pig208/project/28"
}
]
}

View File

@@ -0,0 +1,133 @@
{
"id": "60723fdc-2c6d-4b31-b160-ef4d438dc5bc",
"changed_at": "2021-04-11T00:16:28.845Z",
"version": "v1",
"member_id": "6071752f-e16e-4f79-b41e-7c78b76aa4bd",
"actions": [
{
"id": 17,
"entity_type": "story",
"action": "update",
"name": "asd4",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/17",
"changes": {
"story_type": {
"new": "bug",
"old": "feature"
}
}
},
{
"id": 26,
"entity_type": "story",
"action": "update",
"name": "new1",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/26",
"changes": {
"epic_id": {
"new": 23,
"old": 29
}
}
},
{
"id": 27,
"entity_type": "story",
"action": "update",
"name": "new2",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/27",
"changes": {
"label_ids": {
"adds": [
8
]
}
}
},
{
"id": 28,
"entity_type": "story",
"action": "update",
"name": "new3",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/28",
"changes": {
"workflow_state_id": {
"new": 500000010,
"old": 500000006
}
}
},
{
"id": 29,
"entity_type": "story",
"action": "update",
"name": "new4",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/29",
"changes": {
"project_id": {
"new": 28,
"old": 2
}
}
}
],
"references": [
{
"id": "6071752f-ece0-4772-854c-05a9666c480f",
"entity_type": "group",
"name": "Team 1"
},
{
"id": 500000010,
"entity_type": "workflow-state",
"name": "Ready for Review",
"type": "started"
},
{
"id": 500000006,
"entity_type": "workflow-state",
"name": "In Development",
"type": "started"
},
{
"id": 8,
"entity_type": "label",
"name": "low priority",
"app_url": "https://app.shortcut.com/pig208/label/8"
},
{
"id": 23,
"entity_type": "epic",
"name": "testeipc",
"app_url": "https://app.shortcut.com/pig208/epic/23"
},
{
"id": 2,
"entity_type": "project",
"name": "Product Development",
"app_url": "https://app.shortcut.com/pig208/project/2"
},
{
"id": "6071adb0-641f-46c4-b5e1-4945dae399ea",
"entity_type": "group",
"name": "team2"
},
{
"id": 29,
"entity_type": "epic",
"name": "epic",
"app_url": "https://app.shortcut.com/pig208/epic/29"
},
{
"id": 28,
"entity_type": "project",
"name": "test2",
"app_url": "https://app.shortcut.com/pig208/project/28"
}
]
}

View File

@@ -0,0 +1,52 @@
{
"id": "60723fdc-2c6d-4b31-b160-ef4d438dc5bc",
"changed_at": "2021-04-11T00:16:28.845Z",
"version": "v1",
"member_id": "6071752f-e16e-4f79-b41e-7c78b76aa4bd",
"actions": [
{
"id": 17,
"entity_type": "story",
"action": "update",
"name": "asd4",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/17",
"changes": {
"deadline": {
"new": "2021-04-12T16:00:00Z",
"old": "2021-04-11T16:00:00Z"
}
}
},
{
"id": 26,
"entity_type": "story",
"action": "update",
"name": "new1",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/26",
"changes": {
"owner_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
}
}
},
{
"id": 27,
"entity_type": "story",
"action": "update",
"name": "new2",
"story_type": "bug",
"app_url": "https://app.shortcut.com/pig208/story/27",
"changes": {
"follower_ids": {
"adds": [
"60723f5f-28ca-4ec2-a3a2-37b2dc5606ae"
]
}
}
}
]
}

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.shortcut.com/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.shortcut.com/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.shortcut.com/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.shortcut.com/zulip/story/11",
"changes":{
"estimate":{
"old":4
}
}
}
]
}

View File

@@ -0,0 +1,31 @@
{
"id":"5c37d3f8-4a7e-4cdd-a066-7ced3ace1bd7",
"changed_at":"2019-01-10T23:23:36.331Z",
"primary_id":23,
"version":"v1",
"member_id":"5b1fda16-626f-487f-9ecf-f3a4abf42b8b",
"actions":[
{
"id":23,
"entity_type":"story",
"action":"update",
"name":"An epic story!",
"story_type":"feature",
"app_url":"https://app.shortcut.com/zulip/story/23",
"changes":{
"label_ids":{
"removes":[
18
]
}
}
}
],
"references":[
{
"id":18,
"entity_type":"label",
"name":"mockup"
}
]
}

View File

@@ -0,0 +1,441 @@
import json
from unittest.mock import MagicMock, call, patch
from zerver.lib.test_classes import WebhookTestCase
class ShortcutWebhookTest(WebhookTestCase):
def test_legacy_urls(self) -> None:
expected_message = "New story [Add cool feature!](https://app.shortcut.com/zulip/story/11) of type **feature** was created."
legacy_names = ["clubhouse"]
for legacy_name in legacy_names:
self.url = self.build_webhook_url(legacy_name=legacy_name)
self.check_webhook("story_create", "Add cool feature!", expected_message)
def test_story_create(self) -> None:
expected_message = "New story [Add cool feature!](https://app.shortcut.com/zulip/story/11) of type **feature** was created."
self.check_webhook("story_create", "Add cool feature!", expected_message)
def test_story_delete(self) -> None:
expected_message = "The story **New random story** was deleted."
self.check_webhook("story_delete", "New random story", expected_message)
def test_epic_story_create(self) -> None:
expected_message = "New story [An epic story!](https://app.shortcut.com/zulip/story/23) was created and added to the epic **New Cool Epic!**."
self.check_webhook("epic_create_story", "An epic story!", expected_message)
def test_epic_delete(self) -> None:
expected_message = "The epic **Shortcut Fork** was deleted."
self.check_webhook("epic_delete", "Shortcut Fork", expected_message)
def test_story_archive(self) -> None:
expected_message = (
"The story [Story 2](https://app.shortcut.com/zulip/story/9) was archived."
)
self.check_webhook("story_archive", "Story 2", expected_message)
def test_epic_archive(self) -> None:
expected_message = "The epic **Zulip is epic!** was archived."
self.check_webhook("epic_archive", "Zulip is epic!", expected_message)
def test_story_unarchive(self) -> None:
expected_message = (
"The story [Story 2](https://app.shortcut.com/zulip/story/9) was unarchived."
)
self.check_webhook("story_unarchive", "Story 2", expected_message)
def test_epic_create(self) -> None:
expected_message = "New epic **New Epic!**(to do) was created."
self.check_webhook("epic_create", "New Epic!", expected_message)
def test_epic_update_add_comment(self) -> None:
expected_message = "New comment added to the epic **New Cool Epic!**:\n``` quote\nAdded a comment on this Epic!\n```"
self.check_webhook("epic_update_add_comment", "New Cool Epic!", expected_message)
def test_story_update_add_comment(self) -> None:
expected_message = "New comment added to the story [Add cool feature!](https://app.shortcut.com/zulip/story/11):\n``` quote\nJust leaving a comment here!\n```"
self.check_webhook("story_update_add_comment", "Add cool feature!", expected_message)
def test_epic_update_add_description(self) -> None:
expected_message = "New description added to the epic **New Cool Epic!**:\n``` quote\nAdded a description!\n```"
self.check_webhook("epic_update_add_description", "New Cool Epic!", expected_message)
def test_epic_update_remove_description(self) -> None:
expected_message = "Description for the epic **New Cool Epic!** was removed."
self.check_webhook("epic_update_remove_description", "New Cool Epic!", expected_message)
def test_epic_update_change_description(self) -> None:
expected_message = "Description for the epic **New Cool Epic!** was changed from:\n``` quote\nAdded a description!\n```\nto\n``` quote\nChanged a description!\n```"
self.check_webhook("epic_update_change_description", "New Cool Epic!", expected_message)
def test_story_update_add_description(self) -> None:
expected_message = "New description added to the story [Story 2](https://app.shortcut.com/zulip/story/9):\n``` quote\nAdded a description.\n```"
self.check_webhook("story_update_add_description", "Story 2", expected_message)
def test_story_update_remove_description(self) -> None:
expected_message = "Description for the story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was removed."
self.check_webhook("story_update_remove_description", "Add cool feature!", expected_message)
def test_story_update_change_description(self) -> None:
expected_message = "Description for the story [Add cool feature!](https://app.shortcut.com/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.check_webhook("story_update_description", "Add cool feature!", expected_message)
def test_epic_update_change_state(self) -> None:
expected_message = (
"State of the epic **New Cool Epic!** was changed from **to do** to **in progress**."
)
self.check_webhook("epic_update_change_state", "New Cool Epic!", expected_message)
def test_story_update_change_state(self) -> None:
expected_message = "State of the story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was changed from **Unscheduled** to **Ready for Review**."
self.check_webhook("story_update_change_state", "Add cool feature!", expected_message)
def test_epic_update_change_name(self) -> None:
expected_message = "The name of the epic **New Cool Epic!** was changed from:\n``` quote\nNew Epic!\n```\nto\n``` quote\nNew Cool Epic!\n```"
self.check_webhook("epic_update_change_title", "New Cool Epic!", expected_message)
def test_story_update_change_name(self) -> None:
expected_message = "The name of the story [Add super cool feature!](https://app.shortcut.com/zulip/story/11) was changed from:\n``` quote\nAdd cool feature!\n```\nto\n``` quote\nAdd super cool feature!\n```"
self.check_webhook("story_update_change_title", "Add super cool feature!", expected_message)
def test_story_update_add_owner(self) -> None:
expected_message = "New owner added to the story [A new story by Shakespeare!](https://app.shortcut.com/zulip/story/26)."
self.check_webhook(
"story_update_add_owner", "A new story by Shakespeare!", expected_message
)
def test_story_task_created(self) -> None:
expected_message = "Task **Added a new task** was added to the story [Add cool feature!](https://app.shortcut.com/zulip/story/11)."
self.check_webhook("story_task_create", "Add cool feature!", expected_message)
def test_story_task_deleted(self) -> None:
expected_message = "Task **Added a new task** was removed from the story [Add cool feature!](https://app.shortcut.com/zulip/story/11)."
self.check_webhook("story_task_delete", "Add cool feature!", expected_message)
def test_story_task_completed(self) -> None:
expected_message = "Task **A new task for this story** ([Add cool feature!](https://app.shortcut.com/zulip/story/11)) was completed. :tada:"
self.check_webhook("story_task_complete", "Add cool feature!", expected_message)
@patch("zerver.webhooks.shortcut.view.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 = (
"The story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was moved from **Release 1.9**"
" to **Shortcut Fork**."
)
self.check_webhook("story_update_change_epic", "Add cool feature!", expected_message)
def test_story_epic_added(self) -> None:
expected_message = "The story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was added to the epic **Release 1.9**."
self.check_webhook("story_update_add_epic", "Add cool feature!", expected_message)
def test_story_epic_removed(self) -> None:
expected_message = "The story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was removed from the epic **Release 1.9**."
self.check_webhook("story_update_remove_epic", "Add cool feature!", expected_message)
def test_story_estimate_changed(self) -> None:
expected_message = "The estimate for the story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was set to 4 points."
self.check_webhook("story_update_change_estimate", "Add cool feature!", expected_message)
def test_story_estimate_added(self) -> None:
expected_message = "The estimate for the story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was set to 4 points."
self.check_webhook("story_update_add_estimate", "Add cool feature!", expected_message)
def test_story_estimate_removed(self) -> None:
expected_message = "The estimate for the story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was set to *Unestimated*."
self.check_webhook("story_update_remove_estimate", "Add cool feature!", expected_message)
def test_story_file_attachment_added(self) -> None:
expected_message = "A file attachment `zuliprc` was added to the story [Add cool feature!](https://app.shortcut.com/zulip/story/11)."
self.check_webhook("story_update_add_attachment", "Add cool feature!", expected_message)
@patch("zerver.webhooks.shortcut.view.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 = "The label **mockup** was added to the story [An epic story!](https://app.shortcut.com/zulip/story/23)."
self.check_webhook("story_update_add_label", "An epic story!", expected_message)
def test_story_label_multiple_added(self) -> None:
expected_message = "The labels **mockup**, **label** were added to the story [An epic story!](https://app.shortcut.com/zulip/story/23)."
self.check_webhook("story_update_add_multiple_labels", "An epic story!", expected_message)
def test_story_label_added_label_name_in_actions(self) -> None:
expected_message = "The label **sad** was added to the story [An emotional story!](https://app.shortcut.com/zulip/story/28)."
self.check_webhook(
"story_update_add_label_name_in_action", "An emotional story!", expected_message
)
@patch("zerver.webhooks.shortcut.view.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 = "The story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was moved from the **Backend** project to **Devops**."
self.check_webhook("story_update_change_project", "Add cool feature!", expected_message)
def test_story_update_type(self) -> None:
expected_message = "The type of the story [Add cool feature!](https://app.shortcut.com/zulip/story/11) was changed from **feature** to **bug**."
self.check_webhook("story_update_change_type", "Add cool feature!", expected_message)
def test_story_update_add_github_pull_request(self) -> None:
expected_message = "New GitHub PR [#10](https://github.com/eeshangarg/Scheduler/pull/10) opened for story [Testing pull requests with Story](https://app.shortcut.com/zulip/story/28) (Unscheduled -> Ready for Review)."
self.check_webhook(
"story_update_add_github_pull_request",
"Testing pull requests with Story",
expected_message,
)
def test_story_update_add_github_pull_request_without_workflow_state(self) -> None:
expected_message = "New GitHub PR [#10](https://github.com/eeshangarg/Scheduler/pull/10) opened for story [Testing pull requests with Story](https://app.shortcut.com/zulip/story/28)."
self.check_webhook(
"story_update_add_github_pull_request_without_workflow_state",
"Testing pull requests with Story",
expected_message,
)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_update_add_github_multiple_pull_requests(
self, check_send_webhook_message_mock: MagicMock
) -> None:
payload = self.get_body("story_update_add_github_multiple_pull_requests")
self.client_post(self.url, payload, content_type="application/json")
expected_message = "New GitHub PR [#2](https://github.com/PIG208/test-clubhouse/pull/2) opened for story [{name}]({url}) (Unscheduled -> In Development)."
request, user_profile = (
check_send_webhook_message_mock.call_args_list[0][0][0],
check_send_webhook_message_mock.call_args_list[0][0][1],
)
expected_list = [
call(
request,
user_profile,
"Story1",
expected_message.format(
name="Story1", url="https://app.shortcut.com/pig208/story/17"
),
"pull-request_create",
),
call(
request,
user_profile,
"Story2",
expected_message.format(
name="Story2", url="https://app.shortcut.com/pig208/story/18"
),
"pull-request_create",
),
]
self.assertEqual(check_send_webhook_message_mock.call_args_list, expected_list)
def test_story_update_add_github_pull_request_with_comment(self) -> None:
expected_message = "Existing GitHub PR [#2](https://github.com/PIG208/test-clubhouse/pull/2) associated with story [asd2](https://app.shortcut.com/pig208/story/15)."
self.check_webhook(
"story_update_add_github_pull_request_with_comment",
"asd2",
expected_message,
)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_update_add_github_multiple_pull_requests_with_comment(
self, check_send_webhook_message_mock: MagicMock
) -> None:
payload = self.get_body("story_update_add_github_multiple_pull_requests_with_comment")
self.client_post(self.url, payload, content_type="application/json")
expected_message = "Existing GitHub PR [#1](https://github.com/PIG208/test-clubhouse/pull/1) associated with story [{name}]({url}) (Unscheduled -> In Development)."
request, user_profile = (
check_send_webhook_message_mock.call_args_list[0][0][0],
check_send_webhook_message_mock.call_args_list[0][0][1],
)
expected_list = [
call(
request,
user_profile,
"new1",
expected_message.format(
name="new1", url="https://app.shortcut.com/pig208/story/26"
),
"pull-request_comment",
),
call(
request,
user_profile,
"new2",
expected_message.format(
name="new2", url="https://app.shortcut.com/pig208/story/27"
),
"pull-request_comment",
),
]
self.assertEqual(check_send_webhook_message_mock.call_args_list, expected_list)
def test_story_update_add_github_branch(self) -> None:
expected_message = "New GitHub branch [eeshangarg/ch27/testing-pull-requests-with-story](https://github.com/eeshangarg/scheduler/tree/eeshangarg/ch27/testing-pull-requests-with-story) associated with story [Testing pull requests with Story](https://app.shortcut.com/zulip/story/27) (Unscheduled -> In Development)."
self.check_webhook(
"story_update_add_github_branch", "Testing pull requests with Story", expected_message
)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_update_batch(self, check_send_webhook_message_mock: MagicMock) -> None:
payload = self.get_body("story_update_everything_at_once")
self.client_post(self.url, payload, content_type="application/json")
expected_message = "The story [{name}]({url}) was moved from Epic **epic** to **testeipc**, Project **Product Development** to **test2**, and changed from type **feature** to **bug**, and added with the new label **low priority** (In Development -> Ready for Review)."
request, user_profile = (
check_send_webhook_message_mock.call_args_list[0][0][0],
check_send_webhook_message_mock.call_args_list[0][0][1],
)
expected_list = [
call(
request,
user_profile,
"asd4",
expected_message.format(
name="asd4", url="https://app.shortcut.com/pig208/story/17"
),
"story_update_batch",
),
call(
request,
user_profile,
"new1",
expected_message.format(
name="new1", url="https://app.shortcut.com/pig208/story/26"
),
"story_update_batch",
),
call(
request,
user_profile,
"new2",
expected_message.format(
name="new2", url="https://app.shortcut.com/pig208/story/27"
),
"story_update_batch",
),
]
self.assertEqual(check_send_webhook_message_mock.call_args_list, expected_list)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_update_batch_skip_removed_labels(
self, check_send_webhook_message_mock: MagicMock
) -> None:
payload = self.get_body("story_update_everything_at_once_skip_removed_labels")
self.client_post(self.url, payload, content_type="application/json")
expected_message = "The story [{name}]({url}) was moved from Epic **epic** to **testeipc**, Project **Product Development** to **test2**, and changed from type **feature** to **bug** (In Development -> Ready for Review)."
request, user_profile = (
check_send_webhook_message_mock.call_args_list[0][0][0],
check_send_webhook_message_mock.call_args_list[0][0][1],
)
expected_list = [
call(
request,
user_profile,
"asd4",
expected_message.format(
name="asd4", url="https://app.shortcut.com/pig208/story/17"
),
"story_update_batch",
),
call(
request,
user_profile,
"new1",
expected_message.format(
name="new1", url="https://app.shortcut.com/pig208/story/26"
),
"story_update_batch",
),
call(
request,
user_profile,
"new2",
expected_message.format(
name="new2", url="https://app.shortcut.com/pig208/story/27"
),
"story_update_batch",
),
]
self.assertEqual(check_send_webhook_message_mock.call_args_list, expected_list)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_update_batch_each_with_one_change(
self, check_send_webhook_message_mock: MagicMock
) -> None:
payload = self.get_body("story_update_multiple_at_once")
self.client_post(self.url, payload, content_type="application/json")
expected_messages = [
(
"asd4",
"The type of the story [asd4](https://app.shortcut.com/pig208/story/17) was changed from **feature** to **bug**.",
),
(
"new1",
"The story [new1](https://app.shortcut.com/pig208/story/26) was moved from **epic** to **testeipc**.",
),
(
"new2",
"The label **low priority** was added to the story [new2](https://app.shortcut.com/pig208/story/27).",
),
(
"new3",
"State of the story [new3](https://app.shortcut.com/pig208/story/28) was changed from **In Development** to **Ready for Review**.",
),
(
"new4",
"The story [new4](https://app.shortcut.com/pig208/story/29) was moved from the **Product Development** project to **test2**.",
),
]
request, user_profile = (
check_send_webhook_message_mock.call_args_list[0][0][0],
check_send_webhook_message_mock.call_args_list[0][0][1],
)
expected_list = [
call(
request,
user_profile,
expected_message[0],
expected_message[1],
"story_update_batch",
)
for expected_message in expected_messages
]
self.assertEqual(check_send_webhook_message_mock.call_args_list, expected_list)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_update_batch_not_supported_ignore(
self, check_send_webhook_message_mock: MagicMock
) -> None:
payload = self.get_body("story_update_multiple_not_supported")
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_empty_post_request_body_ignore(
self, check_send_webhook_message_mock: MagicMock
) -> None:
payload = json.dumps(None)
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)
@patch("zerver.webhooks.shortcut.view.check_send_webhook_message")
def test_story_comment_updated_ignore(self, check_send_webhook_message_mock: MagicMock) -> None:
payload = self.get_body("story_comment_updated")
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)

View File

@@ -0,0 +1,791 @@
from collections.abc import Callable, Iterable, Iterator
from django.http import HttpRequest, HttpResponse
from zerver.decorator import webhook_view
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
from zerver.lib.partial import partial
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import (
WildValue,
check_bool,
check_int,
check_list,
check_none_or,
check_string,
check_string_or_int,
)
from zerver.lib.webhooks.common import check_send_webhook_message
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```"
)
ARCHIVED_TEMPLATE = "The {entity} {name_template} was {operation}."
STORY_TASK_TEMPLATE = "Task **{task_description}** was {operation} 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 {operation} 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}."
)
LABEL_TEMPLATE = "**{name}**"
STORY_LABEL_TEMPLATE = "The label {labels} was added to the story {name_template}."
STORY_LABEL_PLURAL_TEMPLATE = "The labels {labels} were 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}**."
)
DELETE_TEMPLATE = "The {entity_type} **{name}** was deleted."
STORY_UPDATE_OWNER_TEMPLATE = "New owner added to the story {name_template}."
TRAILING_WORKFLOW_STATE_CHANGE_TEMPLATE = " ({old} -> {new})"
STORY_GITHUB_PR_TEMPLATE = (
"New GitHub PR [#{name}]({url}) opened for story {name_template}{workflow_state_template}."
)
STORY_GITHUB_COMMENT_PR_TEMPLATE = "Existing GitHub PR [#{name}]({url}) associated with story {name_template}{workflow_state_template}."
STORY_GITHUB_BRANCH_TEMPLATE = "New GitHub branch [{name}]({url}) associated with story {name_template}{workflow_state_template}."
STORY_UPDATE_BATCH_TEMPLATE = "The story {name_template} {templates}{workflow_state_template}."
STORY_UPDATE_BATCH_CHANGED_TEMPLATE = "{operation} from {sub_templates}"
STORY_UPDATE_BATCH_CHANGED_SUB_TEMPLATE = "{entity_type} **{old}** to **{new}**"
STORY_UPDATE_BATCH_ADD_REMOVE_TEMPLATE = "{operation} with {entity}"
def get_action_with_primary_id(payload: WildValue) -> WildValue:
for action in payload["actions"]:
if payload["primary_id"].tame(check_int) == action["id"].tame(check_int):
action_with_primary_id = action
return action_with_primary_id
def get_event(payload: WildValue, action: WildValue) -> str | None:
event = "{}_{}".format(
action["entity_type"].tame(check_string), action["action"].tame(check_string)
)
# We only consider the change to be a batch update only if there are multiple stories (thus there is no primary_id)
if event == "story_update" and "primary_id" not in payload:
return "{}_{}".format(event, "batch")
if event in IGNORED_EVENTS:
return None
if "changes" in action:
changes = action["changes"]
if "description" in changes:
event = "{}_{}".format(event, "description")
elif "state" in changes:
event = "{}_{}".format(event, "state")
elif "workflow_state_id" in changes:
event = "{}_{}".format(event, "state")
elif "name" in changes:
event = "{}_{}".format(event, "name")
elif "archived" in changes:
event = "{}_{}".format(event, "archived")
elif "complete" in changes:
event = "{}_{}".format(event, "complete")
elif "epic_id" in changes:
event = "{}_{}".format(event, "epic")
elif "estimate" in changes:
event = "{}_{}".format(event, "estimate")
elif "file_ids" in changes:
event = "{}_{}".format(event, "attachment")
elif "label_ids" in changes:
event = "{}_{}".format(event, "label")
elif "project_id" in changes:
event = "{}_{}".format(event, "project")
elif "story_type" in changes:
event = "{}_{}".format(event, "type")
elif "owner_ids" in changes:
event = "{}_{}".format(event, "owner")
return event
def get_topic_function_based_on_type(
payload: WildValue, action: WildValue
) -> Callable[[WildValue, WildValue], str | None] | None:
entity_type = action["entity_type"].tame(check_string)
return EVENT_TOPIC_FUNCTION_MAPPER.get(entity_type)
def get_delete_body(payload: WildValue, action: WildValue) -> str:
return DELETE_TEMPLATE.format(
entity_type=action["entity_type"].tame(check_string),
name=action["name"].tame(check_string),
)
def get_story_create_body(payload: WildValue, action: WildValue) -> str:
if "epic_id" not in action:
message = "New story [{name}]({app_url}) of type **{story_type}** was created."
kwargs = {
"name": action["name"].tame(check_string),
"app_url": action["app_url"].tame(check_string),
"story_type": action["story_type"].tame(check_string),
}
else:
message = "New story [{name}]({app_url}) was created and added to the epic **{epic_name}**."
kwargs = {
"name": action["name"].tame(check_string),
"app_url": action["app_url"].tame(check_string),
}
epic_id = action["epic_id"].tame(check_int)
refs = payload["references"]
for ref in refs:
if ref["id"].tame(check_string_or_int) == epic_id:
kwargs["epic_name"] = ref["name"].tame(check_string)
return message.format(**kwargs)
def get_epic_create_body(payload: WildValue, action: WildValue) -> str:
message = "New epic **{name}**({state}) was created."
return message.format(
name=action["name"].tame(check_string),
state=action["state"].tame(check_string),
)
def get_comment_added_body(entity: str, payload: WildValue, ignored_action: WildValue) -> str:
actions = payload["actions"]
kwargs = {"entity": entity}
for action in actions:
if action["id"].tame(check_int) == payload["primary_id"].tame(check_int):
kwargs["text"] = action["text"].tame(check_string)
elif action["entity_type"].tame(check_string) == entity:
name_template = get_name_template(entity).format(
name=action["name"].tame(check_string),
app_url=action.get("app_url").tame(check_none_or(check_string)),
)
kwargs["name_template"] = name_template
return COMMENT_ADDED_TEMPLATE.format(**kwargs)
def get_update_description_body(entity: str, payload: WildValue, action: WildValue) -> str:
desc = action["changes"]["description"]
kwargs = {
"entity": entity,
"new": desc["new"].tame(check_string),
"old": desc["old"].tame(check_string),
"name_template": get_name_template(entity).format(
name=action["name"].tame(check_string),
app_url=action.get("app_url").tame(check_none_or(check_string)),
),
}
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: WildValue, action: WildValue) -> str:
state = action["changes"]["state"]
kwargs = {
"entity": "epic",
"new": state["new"].tame(check_string),
"old": state["old"].tame(check_string),
"name_template": EPIC_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
),
}
return STATE_CHANGED_TEMPLATE.format(**kwargs)
def get_story_update_state_body(payload: WildValue, action: WildValue) -> str:
workflow_state_id = action["changes"]["workflow_state_id"]
references = payload["references"]
state = {}
for ref in references:
if ref["id"].tame(check_string_or_int) == workflow_state_id["new"].tame(check_int):
state["new"] = ref["name"].tame(check_string)
if ref["id"].tame(check_string_or_int) == workflow_state_id["old"].tame(check_int):
state["old"] = ref["name"].tame(check_string)
kwargs = {
"entity": "story",
"new": state["new"],
"old": state["old"],
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action.get("app_url").tame(check_none_or(check_string)),
),
}
return STATE_CHANGED_TEMPLATE.format(**kwargs)
def get_update_name_body(entity: str, payload: WildValue, action: WildValue) -> str:
name = action["changes"]["name"]
kwargs = {
"entity": entity,
"new": name["new"].tame(check_string),
"old": name["old"].tame(check_string),
"name_template": get_name_template(entity).format(
name=action["name"].tame(check_string),
app_url=action.get("app_url").tame(check_none_or(check_string)),
),
}
return NAME_CHANGED_TEMPLATE.format(**kwargs)
def get_update_archived_body(entity: str, payload: WildValue, action: WildValue) -> str:
archived = action["changes"]["archived"]
if archived["new"]:
operation = "archived"
else:
operation = "unarchived"
kwargs = {
"entity": entity,
"name_template": get_name_template(entity).format(
name=action["name"].tame(check_string),
app_url=action.get("app_url").tame(check_none_or(check_string)),
),
"operation": operation,
}
return ARCHIVED_TEMPLATE.format(**kwargs)
def get_story_task_body(operation: str, payload: WildValue, action: WildValue) -> str:
kwargs = {
"task_description": action["description"].tame(check_string),
"operation": operation,
}
for a in payload["actions"]:
if a["entity_type"].tame(check_string) == "story":
kwargs["name_template"] = STORY_NAME_TEMPLATE.format(
name=a["name"].tame(check_string),
app_url=a["app_url"].tame(check_string),
)
return STORY_TASK_TEMPLATE.format(**kwargs)
def get_story_task_completed_body(payload: WildValue, action: WildValue) -> str | None:
kwargs = {
"task_description": action["description"].tame(check_string),
}
story_id = action["story_id"].tame(check_int)
for ref in payload["references"]:
if ref["id"].tame(check_string_or_int) == story_id:
kwargs["name_template"] = STORY_NAME_TEMPLATE.format(
name=ref["name"].tame(check_string),
app_url=ref["app_url"].tame(check_string),
)
if action["changes"]["complete"]["new"].tame(check_bool):
return STORY_TASK_COMPLETED_TEMPLATE.format(**kwargs)
else:
return None
def get_story_update_epic_body(payload: WildValue, action: WildValue) -> str:
kwargs = {
"story_name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
}
epic_id = action["changes"]["epic_id"]
new_id = epic_id.get("new").tame(check_none_or(check_int))
old_id = epic_id.get("old").tame(check_none_or(check_int))
for ref in payload["references"]:
if ref["id"].tame(check_string_or_int) == new_id:
kwargs["new_epic_name_template"] = EPIC_NAME_TEMPLATE.format(
name=ref["name"].tame(check_string),
)
if ref["id"].tame(check_string_or_int) == old_id:
kwargs["old_epic_name_template"] = EPIC_NAME_TEMPLATE.format(
name=ref["name"].tame(check_string),
)
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["operation"] = "added to"
else:
kwargs["epic_name_template"] = kwargs["old_epic_name_template"]
kwargs["operation"] = "removed from"
return STORY_ADDED_REMOVED_EPIC_TEMPLATE.format(**kwargs)
def get_story_update_estimate_body(payload: WildValue, action: WildValue) -> str:
kwargs = {
"story_name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
}
estimate = action["changes"]["estimate"]
if "new" in estimate:
new = estimate["new"].tame(check_int)
kwargs["estimate"] = f"{new} points"
else:
kwargs["estimate"] = "*Unestimated*"
return STORY_ESTIMATE_TEMPLATE.format(**kwargs)
def get_reference_by_id(payload: WildValue, ref_id: int | None) -> WildValue | None:
ref = None
for reference in payload["references"]:
if reference["id"].tame(check_string_or_int) == ref_id:
ref = reference
return ref
def get_secondary_actions_with_param(
entity: str, changed_attr: str, payload: WildValue
) -> Iterator[WildValue]:
# This function is a generator for secondary actions that have the required changed attributes,
# i.e.: "story" that has "pull-request_ids" changed.
for action in payload["actions"]:
if action["entity_type"].tame(check_string) == entity and changed_attr in action["changes"]:
yield action
def get_story_create_github_entity_body(entity: str, payload: WildValue, action: WildValue) -> str:
pull_request_action: WildValue = get_action_with_primary_id(payload)
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
"name": (
pull_request_action["number"].tame(check_int)
if entity in ("pull-request", "pull-request-comment")
else pull_request_action["name"].tame(check_string)
),
"url": pull_request_action["url"].tame(check_string),
"workflow_state_template": "",
}
# Sometimes the workflow state of the story will not be changed when linking to a PR.
if "workflow_state_id" in action["changes"]:
workflow_state_id = action["changes"]["workflow_state_id"]
new_state_id = workflow_state_id["new"].tame(check_int)
old_state_id = workflow_state_id["old"].tame(check_int)
new_reference = get_reference_by_id(payload, new_state_id)
assert new_reference is not None
new_state = new_reference["name"].tame(check_string)
old_reference = get_reference_by_id(payload, old_state_id)
assert old_reference is not None
old_state = old_reference["name"].tame(check_string)
kwargs["workflow_state_template"] = TRAILING_WORKFLOW_STATE_CHANGE_TEMPLATE.format(
new=new_state, old=old_state
)
if entity == "pull-request":
template = STORY_GITHUB_PR_TEMPLATE
elif entity == "pull-request-comment":
template = STORY_GITHUB_COMMENT_PR_TEMPLATE
else:
template = STORY_GITHUB_BRANCH_TEMPLATE
return template.format(**kwargs)
def get_story_update_attachment_body(payload: WildValue, action: WildValue) -> str | None:
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
}
file_ids = action["changes"]["file_ids"]
# If this is a payload for when an attachment is removed, ignore it
if "adds" not in file_ids:
return None
file_ids_added = file_ids["adds"].tame(check_list(check_int))
file_id = file_ids_added[0]
for ref in payload["references"]:
if ref["id"].tame(check_string_or_int) == file_id:
kwargs.update(
type=ref["entity_type"].tame(check_string),
file_name=ref["name"].tame(check_string),
)
return FILE_ATTACHMENT_TEMPLATE.format(**kwargs)
def get_story_joined_label_list(payload: WildValue, label_ids_added: list[int]) -> str:
labels = []
for label_id in label_ids_added:
label_name = ""
for action in payload["actions"]:
if action["id"].tame(check_int) == label_id:
label_name = action.get("name", "").tame(check_string)
if label_name == "":
reference = get_reference_by_id(payload, label_id)
label_name = "" if reference is None else reference["name"].tame(check_string)
labels.append(LABEL_TEMPLATE.format(name=label_name))
return ", ".join(labels)
def get_story_label_body(payload: WildValue, action: WildValue) -> str | None:
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
}
label_ids = action["changes"]["label_ids"]
# If this is a payload for when no label is added, ignore it
if "adds" not in label_ids:
return None
label_ids_added = label_ids["adds"].tame(check_list(check_int))
kwargs.update(labels=get_story_joined_label_list(payload, label_ids_added))
return (
STORY_LABEL_TEMPLATE.format(**kwargs)
if len(label_ids_added) == 1
else STORY_LABEL_PLURAL_TEMPLATE.format(**kwargs)
)
def get_story_update_project_body(payload: WildValue, action: WildValue) -> str:
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
}
project_id = action["changes"]["project_id"]
new_project_id = project_id["new"].tame(check_int)
old_project_id = project_id["old"].tame(check_int)
for ref in payload["references"]:
if ref["id"].tame(check_string_or_int) == new_project_id:
kwargs.update(new=ref["name"].tame(check_string))
if ref["id"].tame(check_string_or_int) == old_project_id:
kwargs.update(old=ref["name"].tame(check_string))
return STORY_UPDATE_PROJECT_TEMPLATE.format(**kwargs)
def get_story_update_type_body(payload: WildValue, action: WildValue) -> str:
story_type = action["changes"]["story_type"]
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
"new_type": story_type["new"].tame(check_string),
"old_type": story_type["old"].tame(check_string),
}
return STORY_UPDATE_TYPE_TEMPLATE.format(**kwargs)
def get_story_update_owner_body(payload: WildValue, action: WildValue) -> str:
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
}
return STORY_UPDATE_OWNER_TEMPLATE.format(**kwargs)
def get_story_update_batch_body(payload: WildValue, action: WildValue) -> str | None:
# When the user selects one or more stories with the checkbox, they can perform
# a batch update on multiple stories while changing multiple attributes at the
# same time.
changes = action["changes"]
kwargs = {
"name_template": STORY_NAME_TEMPLATE.format(
name=action["name"].tame(check_string),
app_url=action["app_url"].tame(check_string),
),
"workflow_state_template": "",
}
templates = []
last_change = "other"
move_sub_templates = []
if "epic_id" in changes:
last_change = "epic"
epic_id = changes["epic_id"]
old_reference = get_reference_by_id(
payload, epic_id.get("old").tame(check_none_or(check_int))
)
new_reference = get_reference_by_id(
payload, epic_id.get("new").tame(check_none_or(check_int))
)
move_sub_templates.append(
STORY_UPDATE_BATCH_CHANGED_SUB_TEMPLATE.format(
entity_type="Epic",
old=None if old_reference is None else old_reference["name"].tame(check_string),
new=None if new_reference is None else new_reference["name"].tame(check_string),
)
)
if "project_id" in changes:
last_change = "project"
project_id = changes["project_id"]
old_reference = get_reference_by_id(
payload, project_id.get("old").tame(check_none_or(check_int))
)
new_reference = get_reference_by_id(
payload, project_id.get("new").tame(check_none_or(check_int))
)
move_sub_templates.append(
STORY_UPDATE_BATCH_CHANGED_SUB_TEMPLATE.format(
entity_type="Project",
old=None if old_reference is None else old_reference["name"].tame(check_string),
new=None if new_reference is None else new_reference["name"].tame(check_string),
)
)
if len(move_sub_templates) > 0:
templates.append(
STORY_UPDATE_BATCH_CHANGED_TEMPLATE.format(
operation="was moved",
sub_templates=", ".join(move_sub_templates),
)
)
if "story_type" in changes:
last_change = "type"
story_type = changes["story_type"]
templates.append(
STORY_UPDATE_BATCH_CHANGED_TEMPLATE.format(
operation="{} changed".format("was" if len(templates) == 0 else "and"),
sub_templates=STORY_UPDATE_BATCH_CHANGED_SUB_TEMPLATE.format(
entity_type="type",
old=story_type.get("old").tame(check_none_or(check_string)),
new=story_type.get("new").tame(check_none_or(check_string)),
),
)
)
if "label_ids" in changes:
label_ids = changes["label_ids"]
# If this is a payload for when no label is added, ignore it
if "adds" in label_ids:
label_ids_added = label_ids["adds"].tame(check_list(check_int))
last_change = "label"
labels = get_story_joined_label_list(payload, label_ids_added)
templates.append(
STORY_UPDATE_BATCH_ADD_REMOVE_TEMPLATE.format(
operation="{} added".format("was" if len(templates) == 0 else "and"),
entity="the new label{plural} {labels}".format(
plural="s" if len(label_ids) > 1 else "", labels=labels
),
)
)
if "workflow_state_id" in changes:
last_change = "state"
workflow_state_id = changes["workflow_state_id"]
old_reference = get_reference_by_id(
payload, workflow_state_id.get("old").tame(check_none_or(check_int))
)
new_reference = get_reference_by_id(
payload, workflow_state_id.get("new").tame(check_none_or(check_int))
)
kwargs.update(
workflow_state_template=TRAILING_WORKFLOW_STATE_CHANGE_TEMPLATE.format(
old=None if old_reference is None else old_reference["name"].tame(check_string),
new=None if new_reference is None else new_reference["name"].tame(check_string),
)
)
# Use the default template for state change if it is the only one change.
if len(templates) <= 1 or (len(templates) == 0 and last_change == "state"):
event: str = "{}_{}".format("story_update", last_change)
alternative_body_func = EVENT_BODY_FUNCTION_MAPPER.get(event)
# If last_change is not one of "epic", "project", "type", "label" and "state"
# we should ignore the action as there is no way for us to render the changes.
if alternative_body_func is None:
return None
return alternative_body_func(payload, action)
kwargs.update(templates=", ".join(templates))
return STORY_UPDATE_BATCH_TEMPLATE.format(**kwargs)
def get_entity_name(entity: str, payload: WildValue, action: WildValue) -> str | None:
name = action["name"].tame(check_string) if "name" in action else None
if name is None or action["entity_type"].tame(check_string) == "branch":
for other_action in payload["actions"]:
if other_action["entity_type"].tame(check_string) == entity:
name = other_action["name"].tame(check_string)
if name is None:
for ref in payload["references"]:
if ref["entity_type"].tame(check_string) == entity:
name = ref["name"].tame(check_string)
return name
def get_name_template(entity: str) -> str:
if entity == "story":
return STORY_NAME_TEMPLATE
return EPIC_NAME_TEMPLATE
def send_channel_messages_for_actions(
request: HttpRequest,
user_profile: UserProfile,
payload: WildValue,
action: WildValue,
event: str,
) -> None:
body_func = EVENT_BODY_FUNCTION_MAPPER.get(event)
topic_func = get_topic_function_based_on_type(payload, action)
if body_func is None or topic_func is None:
raise UnsupportedWebhookEventTypeError(event)
topic_name = topic_func(payload, action)
body = body_func(payload, action)
if topic_name and body:
check_send_webhook_message(request, user_profile, topic_name, body, event)
EVENT_BODY_FUNCTION_MAPPER: dict[str, Callable[[WildValue, WildValue], str | None]] = {
"story_update_archived": partial(get_update_archived_body, "story"),
"epic_update_archived": partial(get_update_archived_body, "epic"),
"story_create": get_story_create_body,
"pull-request_create": partial(get_story_create_github_entity_body, "pull-request"),
"pull-request_comment": partial(get_story_create_github_entity_body, "pull-request-comment"),
"branch_create": partial(get_story_create_github_entity_body, "branch"),
"story_delete": get_delete_body,
"epic_delete": get_delete_body,
"story-task_create": partial(get_story_task_body, "added to"),
"story-task_delete": partial(get_story_task_body, "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_owner": get_story_update_owner_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, "epic"),
"story-comment_create": partial(get_comment_added_body, "story"),
"epic_update_description": partial(get_update_description_body, "epic"),
"story_update_description": partial(get_update_description_body, "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, "epic"),
"story_update_name": partial(get_update_name_body, "story"),
"story_update_batch": get_story_update_batch_body,
}
ALL_EVENT_TYPES = list(EVENT_BODY_FUNCTION_MAPPER.keys())
EVENT_TOPIC_FUNCTION_MAPPER: dict[str, Callable[[WildValue, WildValue], str | None]] = {
"story": partial(get_entity_name, "story"),
"pull-request": partial(get_entity_name, "story"),
"branch": partial(get_entity_name, "story"),
"story-comment": partial(get_entity_name, "story"),
"story-task": partial(get_entity_name, "story"),
"epic": partial(get_entity_name, "epic"),
"epic-comment": partial(get_entity_name, "epic"),
}
IGNORED_EVENTS = {
"story-comment_update",
}
EVENTS_SECONDARY_ACTIONS_FUNCTION_MAPPER: dict[str, Callable[[WildValue], Iterator[WildValue]]] = {
"pull-request_create": partial(get_secondary_actions_with_param, "story", "pull_request_ids"),
"branch_create": partial(get_secondary_actions_with_param, "story", "branch_ids"),
"pull-request_comment": partial(get_secondary_actions_with_param, "story", "pull_request_ids"),
}
@webhook_view("Clubhouse", all_event_types=ALL_EVENT_TYPES)
@typed_endpoint
def api_shortcut_webhook(
request: HttpRequest,
user_profile: UserProfile,
*,
payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
# Shortcut has a tendency to send empty POST requests to
# third-party endpoints. It is unclear as to which event type
# such requests correspond to. So, it is best to ignore such
# requests for now.
if payload.value is None:
return json_success(request)
if "primary_id" in payload:
action = get_action_with_primary_id(payload)
primary_actions: Iterable[WildValue] = [action]
else:
primary_actions = payload["actions"]
for primary_action in primary_actions:
event = get_event(payload, primary_action)
if event is None:
continue
if event in EVENTS_SECONDARY_ACTIONS_FUNCTION_MAPPER:
sec_actions_func = EVENTS_SECONDARY_ACTIONS_FUNCTION_MAPPER[event]
for sec_action in sec_actions_func(payload):
send_channel_messages_for_actions(request, user_profile, payload, sec_action, event)
else:
send_channel_messages_for_actions(request, user_profile, payload, primary_action, event)
return json_success(request)