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
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:
0
zerver/webhooks/shortcut/__init__.py
Normal file
0
zerver/webhooks/shortcut/__init__.py
Normal file
28
zerver/webhooks/shortcut/doc.md
Normal file
28
zerver/webhooks/shortcut/doc.md
Normal 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!}
|
||||
|
||||

|
||||
|
||||
{!event-filtering-additional-feature.md!}
|
||||
|
||||
### Related documentation
|
||||
|
||||
{!webhooks-url-specification.md!}
|
||||
21
zerver/webhooks/shortcut/fixtures/epic_archive.json
Normal file
21
zerver/webhooks/shortcut/fixtures/epic_archive.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
30
zerver/webhooks/shortcut/fixtures/epic_create.json
Normal file
30
zerver/webhooks/shortcut/fixtures/epic_create.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
69
zerver/webhooks/shortcut/fixtures/epic_create_story.json
Normal file
69
zerver/webhooks/shortcut/fixtures/epic_create_story.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
zerver/webhooks/shortcut/fixtures/epic_delete.json
Normal file
15
zerver/webhooks/shortcut/fixtures/epic_delete.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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":""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
23
zerver/webhooks/shortcut/fixtures/story_archive.json
Normal file
23
zerver/webhooks/shortcut/fixtures/story_archive.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
31
zerver/webhooks/shortcut/fixtures/story_comment_updated.json
Normal file
31
zerver/webhooks/shortcut/fixtures/story_comment_updated.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
zerver/webhooks/shortcut/fixtures/story_create.json
Normal file
37
zerver/webhooks/shortcut/fixtures/story_create.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
zerver/webhooks/shortcut/fixtures/story_delete.json
Normal file
16
zerver/webhooks/shortcut/fixtures/story_delete.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
zerver/webhooks/shortcut/fixtures/story_task_complete.json
Normal file
31
zerver/webhooks/shortcut/fixtures/story_task_complete.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
zerver/webhooks/shortcut/fixtures/story_task_create.json
Normal file
31
zerver/webhooks/shortcut/fixtures/story_task_create.json
Normal 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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
30
zerver/webhooks/shortcut/fixtures/story_task_delete.json
Normal file
30
zerver/webhooks/shortcut/fixtures/story_task_delete.json
Normal 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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
zerver/webhooks/shortcut/fixtures/story_unarchive.json
Normal file
23
zerver/webhooks/shortcut/fixtures/story_unarchive.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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":""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
29
zerver/webhooks/shortcut/fixtures/story_update_add_epic.json
Normal file
29
zerver/webhooks/shortcut/fixtures/story_update_add_epic.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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. :)"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
441
zerver/webhooks/shortcut/tests.py
Normal file
441
zerver/webhooks/shortcut/tests.py
Normal 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)
|
||||
791
zerver/webhooks/shortcut/view.py
Normal file
791
zerver/webhooks/shortcut/view.py
Normal 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)
|
||||
Reference in New Issue
Block a user