From 3fc63110ac6360ad7d62b74464eef91823ac4629 Mon Sep 17 00:00:00 2001 From: "nirved.mishra.eee23@itbhu.ac.in" Date: Wed, 15 Jan 2025 00:37:09 +0530 Subject: [PATCH] incoming-webhooks: Add OpenProject integration. Fixes #29944. Co-authored-by: theofficialvedantjoshi Co-authored-by: Niloth P <20315308+Niloth-p@users.noreply.github.com> --- .../integrations/bot_avatars/openproject.png | Bin 0 -> 1746 bytes .../images/integrations/logos/openproject.svg | 1 + .../images/integrations/openproject/001.png | Bin 0 -> 27958 bytes zerver/lib/integrations.py | 2 + zerver/webhooks/openproject/__init__.py | 0 zerver/webhooks/openproject/doc.md | 35 ++ .../fixtures/attachment_created.json | 343 +++++++++++ .../project_created__with_parent.json | 163 ++++++ .../project_created__without_parent.json | 84 +++ .../openproject/fixtures/project_updated.json | 162 +++++ .../time_entry_created__with_invalid_iso.json | 445 ++++++++++++++ .../time_entry_created__with_iso_hm.json | 445 ++++++++++++++ .../time_entry_created__with_workpackage.json | 445 ++++++++++++++ ...me_entry_created__without_workpackage.json | 180 ++++++ .../fixtures/work_package_created.json | 551 ++++++++++++++++++ .../fixtures/work_package_updated.json | 551 ++++++++++++++++++ zerver/webhooks/openproject/tests.py | 102 ++++ zerver/webhooks/openproject/view.py | 123 ++++ 18 files changed, 3632 insertions(+) create mode 100644 static/images/integrations/bot_avatars/openproject.png create mode 100644 static/images/integrations/logos/openproject.svg create mode 100644 static/images/integrations/openproject/001.png create mode 100644 zerver/webhooks/openproject/__init__.py create mode 100644 zerver/webhooks/openproject/doc.md create mode 100644 zerver/webhooks/openproject/fixtures/attachment_created.json create mode 100644 zerver/webhooks/openproject/fixtures/project_created__with_parent.json create mode 100644 zerver/webhooks/openproject/fixtures/project_created__without_parent.json create mode 100644 zerver/webhooks/openproject/fixtures/project_updated.json create mode 100644 zerver/webhooks/openproject/fixtures/time_entry_created__with_invalid_iso.json create mode 100644 zerver/webhooks/openproject/fixtures/time_entry_created__with_iso_hm.json create mode 100644 zerver/webhooks/openproject/fixtures/time_entry_created__with_workpackage.json create mode 100644 zerver/webhooks/openproject/fixtures/time_entry_created__without_workpackage.json create mode 100644 zerver/webhooks/openproject/fixtures/work_package_created.json create mode 100644 zerver/webhooks/openproject/fixtures/work_package_updated.json create mode 100644 zerver/webhooks/openproject/tests.py create mode 100644 zerver/webhooks/openproject/view.py diff --git a/static/images/integrations/bot_avatars/openproject.png b/static/images/integrations/bot_avatars/openproject.png new file mode 100644 index 0000000000000000000000000000000000000000..a13d2b1c8ddff0f28ddcbdce9bf7b4325044e579 GIT binary patch literal 1746 zcma)7dpOezAOGdDqlt|uX<1Xu$t~349Hh-9x8-glm$^laD0RSl5+5VJIDqTuX^YHP73EFPw{GFq? z2S3iuN4&zk_wY6%f#E8J0VUKbGgj1MU7}H%8zkv{=iyrC%5?#VR1xDSI$6W@vhc3r z{*HcO^cYZ6!`YLia-x^Eahb=um@}Q}K%)>ryThqW1;OODzo%}9h5 zmOZAlh4D!1bMDUCFQNW5R+%q7L zBb1HFM~3QwW#~e`(HEvFe^Vv~E;Dq3HcTcGd{ad8sSY!ic+(*#XqHoh`q$7)){JAX z;4!iCaqB)qSNL5!gjy)p{BLY90Cs@z)K{6Ra{WVM>oFyoJ(rYHUe2)mLGYH#i zmZVB>#>2s*-8Lb9jfBbF%}}l9RS#xweam>`F63#|f&?K3GDCSlS7FC~um#6^0vs(( z7-#h9ytC9>FfVY-a*fR>iIqgL4xJc;`n2p-wg^Q{-(SGrU&y&SojuHfxbTcSk1HN9 zHAmYKmMn!!C|dgOcYi|IV5g9GI?5juh3W5O-gdhL8nBJ2#^$MW=#Mx4o49gzVXuKatv#Y2wVZCG# zvHa@d_SPoXa1cr_M#{*YkpLK+K(=q0?~8=3K5?klxW^xIz~gL>I}{I4 zc9Nx%HeVzLn&s|*A9!jR$c0gqtZ*a0%71?2lC5*x>l109K^MZVVjZc-Z7zz-RSu+z z_SO{W)t7|HhB13uj+x4X6eoUtMlcb7s=x5%7P(jsaJ{OtkfdI$&cbMQzf^ETCu?*% z9;m{Po9P*&9yZg!Y7S%hN7xZ**#|=fpR7{Jn+kXI=rBIoBtRNHd(nmWOGrgQKU@?+ z^CBG$64ew}kGon-%ckW_Ucue!pv^~~f48bCiu2C6rX$?#@$}C;!t%mAnW<>0AeTBke0Ph~Sw`p4Cbwl!j9D20DA5InasRD|jfqPdR`H}{KV8~+~lzMVG;OD=!yb?Vtlp%r?}24Nnpo>6T!&J3A~Dx@wCo^WKh zi{Hy04PTe@MkJnFiTiflZ}{!V&xZ#mBr~Hty+K<6_gSiLa`9pvw8|A3V=l<;{wt+@ zY6%S&QxoUsjfJQXLD?L9Z^7#mljGNV3!2`B$n_ky^dt@bq$@Lmi*E`1uCVVSNf74 zcTVyXx8<=5Z?1k_T*PQ=tn8+`lrMDGs5>{GArsf$8;dM%t&Y;%! z2}^or_-r2EMQsAT3bHBpN>r61u;W+er3)5<&KBsdPWaDZB<&fKs5K$u8QJ`#Lzy{{ z6TIurk4FucxPP{2$h-7g-#twP8!UxBXQ%3-H0sODlhUoR4^6W?>qfjaDE@7k!95gr z!TCr?>&hWClyi}%nC2%IniM>^!htz`x!5G@p*g~;!Lyd=O(Ik0!jg z$F&VfG#z|`8$VKS1wUR^WwfS54+QwqP1J8G*qFQb;L0y}F~xVxYc`AKaoL;ahiv}8 e&i)TAza^LAG+M_Uo~CX;DZtqQYySuxdi7sZJvn;- literal 0 HcmV?d00001 diff --git a/static/images/integrations/logos/openproject.svg b/static/images/integrations/logos/openproject.svg new file mode 100644 index 0000000000..5ff601a0ce --- /dev/null +++ b/static/images/integrations/logos/openproject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/integrations/openproject/001.png b/static/images/integrations/openproject/001.png new file mode 100644 index 0000000000000000000000000000000000000000..70d6bd70c44acc56f2ecc0748aa16b0e83b2e48e GIT binary patch literal 27958 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 YAZw)ltYo7kY4Oe~oRiOyy?pck0Oz5v>i_@% literal 0 HcmV?d00001 diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index 2684039a28..a90a75897b 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -528,6 +528,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [ WebhookIntegration("netlify", ["continuous-integration", "deployment"]), WebhookIntegration("newrelic", ["monitoring"], display_name="New Relic"), WebhookIntegration("opencollective", ["financial"], display_name="Open Collective"), + WebhookIntegration("openproject", ["project-management"], display_name="OpenProject"), WebhookIntegration("opensearch", ["monitoring"], display_name="OpenSearch"), WebhookIntegration("opsgenie", ["meta-integration", "monitoring"]), WebhookIntegration("pagerduty", ["monitoring"], display_name="PagerDuty"), @@ -774,6 +775,7 @@ DOC_SCREENSHOT_CONFIG: dict[str, list[BaseScreenshotConfig]] = { "netlify": [ScreenshotConfig("deploy_building.json")], "newrelic": [ScreenshotConfig("incident_activated_new_default_payload.json")], "opencollective": [ScreenshotConfig("one_time_donation.json")], + "openproject": [ScreenshotConfig("project_created__without_parent.json")], "opensearch": [ScreenshotConfig("example_template.txt")], "opsgenie": [ScreenshotConfig("addrecipient.json")], "pagerduty": [ScreenshotConfig("trigger_v2.json")], diff --git a/zerver/webhooks/openproject/__init__.py b/zerver/webhooks/openproject/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zerver/webhooks/openproject/doc.md b/zerver/webhooks/openproject/doc.md new file mode 100644 index 0000000000..8dca7601e9 --- /dev/null +++ b/zerver/webhooks/openproject/doc.md @@ -0,0 +1,35 @@ +# Zulip OpenProject integration + +Get Zulip notifications for your OpenProject work packages and projects! + +{start_tabs} + +1. {!create-an-incoming-webhook.md!} + +1. {!generate-webhook-url-basic.md!} + +1. From your OpenProject organization, click on your user profile icon. + Select **Administration** from the dropdown menu, and navigate to + **API and Webhooks**. Select the **Webhooks** tab from the left panel, + and click on **+ Webhook**. + +1. Enter a name of your choice for the webhook, such as `Zulip`. Set + **Payload URL** to the URL generated above, and ensure the webhook is + enabled. + +1. Select the events and projects you want to receive notifications for, + and click **Create**. + +{end_tabs} + +{!congrats.md!} + +![](/static/images/integrations/openproject/001.png) + +### Related documentation + +* [**OpenProject webhook guide**][1] + +{!webhooks-url-specification.md!} + +[1]: https://www.openproject.org/docs/system-admin-guide/api-and-webhooks/#webhooks diff --git a/zerver/webhooks/openproject/fixtures/attachment_created.json b/zerver/webhooks/openproject/fixtures/attachment_created.json new file mode 100644 index 0000000000..d34293c942 --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/attachment_created.json @@ -0,0 +1,343 @@ +{ + "action": "attachment:created", + "attachment": { + "_type": "Attachment", + "id": 3, + "fileName": "a.out", + "fileSize": 20144, + "description": { + "format": "plain", + "raw": "", + "html": "" + }, + "status": "uploaded", + "contentType": "application/x-pie-executable", + "digest": { + "algorithm": "md5", + "hash": "f9d1a2d6c97e33acc10b858001bc0ea1" + }, + "createdAt": "2025-01-01T21:27:50.230Z", + "_embedded": { + "author": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2025-01-01T18:28:20.852Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "container": { + "_type": "WorkPackage", + "id": 39, + "lockVersion": 1, + "subject": "task2", + "description": { + "format": "markdown", + "raw": "x vxcv x\n\n
", + "html": "

x vxcv x

\n
" + }, + "scheduleManually": false, + "startDate": null, + "dueDate": null, + "derivedStartDate": null, + "derivedDueDate": null, + "estimatedTime": null, + "derivedEstimatedTime": null, + "derivedRemainingTime": null, + "duration": null, + "ignoreNonWorkingDays": false, + "spentTime": "PT0S", + "percentageDone": null, + "derivedPercentageDone": null, + "createdAt": "2025-01-01T20:44:41.255Z", + "updatedAt": "2025-01-01T21:27:53.926Z", + "readonly": false, + "laborCosts": "0.00 EUR", + "materialCosts": "0.00 EUR", + "overallCosts": "0.00 EUR", + "_links": { + "attachments": { + "href": "/api/v3/work_packages/39/attachments" + }, + "prepareAttachment": { + "href": "/api/v3/work_packages/39/attachments/prepare", + "method": "post" + }, + "addAttachment": { + "href": "/api/v3/work_packages/39/attachments", + "method": "post" + }, + "fileLinks": { + "href": "/api/v3/work_packages/39/file_links" + }, + "addFileLink": { + "href": "/api/v3/work_packages/39/file_links", + "method": "post" + }, + "self": { + "href": "/api/v3/work_packages/39", + "title": "task2" + }, + "update": { + "href": "/api/v3/work_packages/39/form", + "method": "post" + }, + "schema": { + "href": "/api/v3/work_packages/schemas/4-1" + }, + "updateImmediately": { + "href": "/api/v3/work_packages/39", + "method": "patch" + }, + "delete": { + "href": "/api/v3/work_packages/39", + "method": "delete" + }, + "logTime": { + "href": "/api/v3/time_entries", + "title": "Log time on work package 'task2'" + }, + "move": { + "href": "/work_packages/39/move/new", + "type": "text/html", + "title": "Move work package 'task2'" + }, + "copy": { + "href": "/work_packages/39/copy", + "type": "text/html", + "title": "Copy work package 'task2'" + }, + "pdf": { + "href": "/work_packages/39.pdf", + "type": "application/pdf", + "title": "Export as PDF" + }, + "atom": { + "href": "/work_packages/39.atom", + "type": "application/rss+xml", + "title": "Atom feed" + }, + "availableRelationCandidates": { + "href": "/api/v3/work_packages/39/available_relation_candidates", + "title": "Potential work packages to relate to" + }, + "customFields": { + "href": "/projects/project-2/settings/custom_fields", + "type": "text/html", + "title": "Custom fields" + }, + "configureForm": { + "href": "/types/1/edit?tab=form_configuration", + "type": "text/html", + "title": "Configure form" + }, + "activities": { + "href": "/api/v3/work_packages/39/activities" + }, + "availableWatchers": { + "href": "/api/v3/work_packages/39/available_watchers" + }, + "relations": { + "href": "/api/v3/work_packages/39/relations" + }, + "revisions": { + "href": "/api/v3/work_packages/39/revisions" + }, + "watchers": { + "href": "/api/v3/work_packages/39/watchers" + }, + "addWatcher": { + "href": "/api/v3/work_packages/39/watchers", + "method": "post", + "payload": { + "user": { + "href": "/api/v3/users/{user_id}" + } + }, + "templated": true + }, + "removeWatcher": { + "href": "/api/v3/work_packages/39/watchers/{user_id}", + "method": "delete", + "templated": true + }, + "addRelation": { + "href": "/api/v3/work_packages/39/relations", + "method": "post", + "title": "Add relation" + }, + "addChild": { + "href": "/api/v3/projects/project-2/work_packages", + "method": "post", + "title": "Add child of task2" + }, + "changeParent": { + "href": "/api/v3/work_packages/39", + "method": "patch", + "title": "Change parent of task2" + }, + "addComment": { + "href": "/api/v3/work_packages/39/activities", + "method": "post", + "title": "Add comment" + }, + "previewMarkup": { + "href": "/api/v3/render/markdown?context=/api/v3/work_packages/39", + "method": "post" + }, + "timeEntries": { + "href": "/api/v3/time_entries?filters=%5B%7B%22work_package_id%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2239%22%5D%7D%7D%5D", + "title": "Time entries" + }, + "ancestors": [], + "category": { + "href": null + }, + "type": { + "href": "/api/v3/types/1", + "title": "Task" + }, + "priority": { + "href": "/api/v3/priorities/8", + "title": "Normal" + }, + "project": { + "href": "/api/v3/projects/4", + "title": "Project 2" + }, + "status": { + "href": "/api/v3/statuses/1", + "title": "New" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "responsible": { + "href": null + }, + "assignee": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "version": { + "href": null + }, + "parent": { + "href": null, + "title": null + }, + "customActions": [], + "logCosts": { + "href": "/work_packages/39/cost_entries/new", + "type": "text/html", + "title": "Log costs on task2" + }, + "showCosts": { + "href": "/projects/4/cost_reports?fields%5B%5D=WorkPackageId&operators%5BWorkPackageId%5D=%3D&set_filter=1&values%5BWorkPackageId%5D=39", + "type": "text/html", + "title": "Show cost entries" + }, + "costsByType": { + "href": "/api/v3/work_packages/39/summarized_costs_by_type" + }, + "meetings": { + "href": "/work_packages/39/tabs/meetings", + "title": "meetings" + }, + "github": { + "href": "/work_packages/39/tabs/github", + "title": "github" + }, + "github_pull_requests": { + "href": "/api/v3/work_packages/39/github_pull_requests", + "title": "GitHub pull requests" + }, + "gitlab": { + "href": "/work_packages/39/tabs/gitlab", + "title": "gitlab" + }, + "gitlab_merge_requests": { + "href": "/api/v3/work_packages/39/gitlab_merge_requests", + "title": "Gitlab merge requests" + }, + "gitlab_issues": { + "href": "/api/v3/work_packages/39/gitlab_issues", + "title": "Gitlab Issues" + }, + "convertBCF": { + "href": "/api/bcf/2.1/projects/project-2/topics", + "title": "Convert to BCF", + "payload": { + "reference_links": [ + "/api/v3/work_packages/39" + ] + }, + "method": "post" + } + } + } + }, + "_links": { + "self": { + "href": "/api/v3/attachments/3", + "title": "a.out" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "container": { + "href": "/api/v3/work_packages/39", + "title": "task2" + }, + "staticDownloadLocation": { + "href": "/api/v3/attachments/3/content" + }, + "downloadLocation": { + "href": "https://saas-openproject-aws-de-trials2-20241119125120412800000002.s3.eu-central-1.amazonaws.com/1735649550_8932652_b7d27008_3959_4391_8bd2_083b23bbd9d0/attachment/file/3/a.out?response-content-disposition=attachment&X-Amz-Expires=600&X-Amz-Date=20250101T212754Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAUF2KCI5YK2B2YLH6%2F20250101%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=ce1ce0ece048498ed44704da6d4eab8aabbe34b38a67780fea218628b91e4e46" + }, + "delete": { + "href": "/api/v3/attachments/3", + "method": "delete" + } + } + } + } diff --git a/zerver/webhooks/openproject/fixtures/project_created__with_parent.json b/zerver/webhooks/openproject/fixtures/project_created__with_parent.json new file mode 100644 index 0000000000..4a25ef08bd --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/project_created__with_parent.json @@ -0,0 +1,163 @@ +{ + "action": "project:created", + "project": { + "_type": "Project", + "id": 3, + "identifier": "ai backend", + "name": "AI Backend", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "", + "html": "" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2024-12-31T18:07:24.609Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_embedded": { + "parent": { + "_type": "Project", + "id": 1, + "identifier": "demo-project", + "name": "Demo project", + "active": true, + "public": true, + "description": { + "format": "markdown", + "raw": "This is a short summary of the goals of this demo project.", + "html": "

This is a short summary of the goals of this demo project.

" + }, + "createdAt": "2024-12-31T12:52:45.287Z", + "updatedAt": "2024-12-31T18:07:24.609Z", + "statusExplanation": { + "format": "markdown", + "raw": "All tasks are on schedule. The people involved know their tasks. The system is completely set up.", + "html": "

All tasks are on schedule. The people involved know their tasks. The system is completely set up.

" + }, + "_links": { + "self": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "createWorkPackage": { + "href": "/api/v3/projects/1/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/1/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/1/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/1/categories" + }, + "versions": { + "href": "/api/v3/projects/1/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/1/types" + }, + "update": { + "href": "/api/v3/projects/1/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/1", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/1", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "parent": { + "href": null + }, + "status": { + "href": "/api/v3/project_statuses/on_track", + "title": "On track" + } + } + } + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "AI Backend" + }, + "createWorkPackage": { + "href": "/api/v3/projects/3/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/3/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/3/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + } +} + diff --git a/zerver/webhooks/openproject/fixtures/project_created__without_parent.json b/zerver/webhooks/openproject/fixtures/project_created__without_parent.json new file mode 100644 index 0000000000..8df4cdcba1 --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/project_created__without_parent.json @@ -0,0 +1,84 @@ +{ + "action": "project:created", + "project": { + "_type": "Project", + "id": 3, + "identifier": "ai backend", + "name": "AI Backend", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "", + "html": "" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2024-12-31T18:07:24.609Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "AI Backend" + }, + "createWorkPackage": { + "href": "/api/v3/projects/3/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/3/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/3/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + } +} diff --git a/zerver/webhooks/openproject/fixtures/project_updated.json b/zerver/webhooks/openproject/fixtures/project_updated.json new file mode 100644 index 0000000000..7458248cd9 --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/project_updated.json @@ -0,0 +1,162 @@ +{ + "action": "project:updated", + "project": { + "_type": "Project", + "id": 3, + "identifier": "ai backend", + "name": "AI Backend", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "dgrfcxbcfbcgf", + "html": "

dgrfcxbcfbcgf

" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2024-12-31T18:08:11.603Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_embedded": { + "parent": { + "_type": "Project", + "id": 1, + "identifier": "demo-project", + "name": "Demo project", + "active": true, + "public": true, + "description": { + "format": "markdown", + "raw": "This is a short summary of the goals of this demo project.", + "html": "

This is a short summary of the goals of this demo project.

" + }, + "createdAt": "2024-12-31T12:52:45.287Z", + "updatedAt": "2024-12-31T18:07:24.609Z", + "statusExplanation": { + "format": "markdown", + "raw": "All tasks are on schedule. The people involved know their tasks. The system is completely set up.", + "html": "

All tasks are on schedule. The people involved know their tasks. The system is completely set up.

" + }, + "_links": { + "self": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "createWorkPackage": { + "href": "/api/v3/projects/1/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/1/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/1/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/1/categories" + }, + "versions": { + "href": "/api/v3/projects/1/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/1/types" + }, + "update": { + "href": "/api/v3/projects/1/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/1", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/1", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "parent": { + "href": null + }, + "status": { + "href": "/api/v3/project_statuses/on_track", + "title": "On track" + } + } + } + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "AI Backend" + }, + "createWorkPackage": { + "href": "/api/v3/projects/3/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/3/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/3/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + } +} diff --git a/zerver/webhooks/openproject/fixtures/time_entry_created__with_invalid_iso.json b/zerver/webhooks/openproject/fixtures/time_entry_created__with_invalid_iso.json new file mode 100644 index 0000000000..cbace75efb --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/time_entry_created__with_invalid_iso.json @@ -0,0 +1,445 @@ +{ + "action": "time_entry:created", + "time_entry": { + "_type": "TimeEntry", + "id": 2, + "ongoing": false, + "comment": { + "format": "plain", + "raw": "", + "html": "" + }, + "spentOn": "2025-01-02", + "hours": "XX", + "createdAt": "2025-01-01T21:20:58.333Z", + "updatedAt": "2025-01-01T21:20:58.333Z", + "_embedded": { + "project": { + "_type": "Project", + "id": 3, + "identifier": "project1", + "name": "Project1", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "1. nucxdswssdfdsf\\_hb\\_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf", + "html": "
    \n
  1. nucxdswssdfdsf_hb_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf
  2. \n
" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2025-01-01T20:41:55.823Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "createWorkPackage": { + "href": "/api/v3/projects/3/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/3/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/3/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + }, + "workPackage": { + "_type": "WorkPackage", + "id": 40, + "lockVersion": 0, + "subject": "kl", + "description": { + "format": "markdown", + "raw": "", + "html": "" + }, + "scheduleManually": false, + "startDate": null, + "dueDate": null, + "derivedStartDate": null, + "derivedDueDate": null, + "estimatedTime": null, + "derivedEstimatedTime": null, + "derivedRemainingTime": null, + "duration": null, + "ignoreNonWorkingDays": false, + "spentTime": "PT0S", + "percentageDone": null, + "derivedPercentageDone": null, + "createdAt": "2025-01-01T20:45:21.718Z", + "updatedAt": "2025-01-01T20:45:21.759Z", + "readonly": false, + "laborCosts": "0.00 EUR", + "materialCosts": "0.00 EUR", + "overallCosts": "0.00 EUR", + "_links": { + "attachments": { + "href": "/api/v3/work_packages/40/attachments" + }, + "prepareAttachment": { + "href": "/api/v3/work_packages/40/attachments/prepare", + "method": "post" + }, + "addAttachment": { + "href": "/api/v3/work_packages/40/attachments", + "method": "post" + }, + "fileLinks": { + "href": "/api/v3/work_packages/40/file_links" + }, + "addFileLink": { + "href": "/api/v3/work_packages/40/file_links", + "method": "post" + }, + "self": { + "href": "/api/v3/work_packages/40", + "title": "kl" + }, + "update": { + "href": "/api/v3/work_packages/40/form", + "method": "post" + }, + "schema": { + "href": "/api/v3/work_packages/schemas/3-1" + }, + "updateImmediately": { + "href": "/api/v3/work_packages/40", + "method": "patch" + }, + "delete": { + "href": "/api/v3/work_packages/40", + "method": "delete" + }, + "logTime": { + "href": "/api/v3/time_entries", + "title": "Log time on work package 'kl'" + }, + "move": { + "href": "/work_packages/40/move/new", + "type": "text/html", + "title": "Move work package 'kl'" + }, + "copy": { + "href": "/work_packages/40/copy", + "type": "text/html", + "title": "Copy work package 'kl'" + }, + "pdf": { + "href": "/work_packages/40.pdf", + "type": "application/pdf", + "title": "Export as PDF" + }, + "atom": { + "href": "/work_packages/40.atom", + "type": "application/rss+xml", + "title": "Atom feed" + }, + "availableRelationCandidates": { + "href": "/api/v3/work_packages/40/available_relation_candidates", + "title": "Potential work packages to relate to" + }, + "customFields": { + "href": "/projects/project1/settings/custom_fields", + "type": "text/html", + "title": "Custom fields" + }, + "configureForm": { + "href": "/types/1/edit?tab=form_configuration", + "type": "text/html", + "title": "Configure form" + }, + "activities": { + "href": "/api/v3/work_packages/40/activities" + }, + "availableWatchers": { + "href": "/api/v3/work_packages/40/available_watchers" + }, + "relations": { + "href": "/api/v3/work_packages/40/relations" + }, + "revisions": { + "href": "/api/v3/work_packages/40/revisions" + }, + "watchers": { + "href": "/api/v3/work_packages/40/watchers" + }, + "addWatcher": { + "href": "/api/v3/work_packages/40/watchers", + "method": "post", + "payload": { + "user": { + "href": "/api/v3/users/{user_id}" + } + }, + "templated": true + }, + "removeWatcher": { + "href": "/api/v3/work_packages/40/watchers/{user_id}", + "method": "delete", + "templated": true + }, + "addRelation": { + "href": "/api/v3/work_packages/40/relations", + "method": "post", + "title": "Add relation" + }, + "addChild": { + "href": "/api/v3/projects/project1/work_packages", + "method": "post", + "title": "Add child of kl" + }, + "changeParent": { + "href": "/api/v3/work_packages/40", + "method": "patch", + "title": "Change parent of kl" + }, + "addComment": { + "href": "/api/v3/work_packages/40/activities", + "method": "post", + "title": "Add comment" + }, + "previewMarkup": { + "href": "/api/v3/render/markdown?context=/api/v3/work_packages/40", + "method": "post" + }, + "timeEntries": { + "href": "/api/v3/time_entries?filters=%5B%7B%22work_package_id%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2240%22%5D%7D%7D%5D", + "title": "Time entries" + }, + "ancestors": [], + "category": { + "href": null + }, + "type": { + "href": "/api/v3/types/1", + "title": "Task" + }, + "priority": { + "href": "/api/v3/priorities/8", + "title": "Normal" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "status": { + "href": "/api/v3/statuses/1", + "title": "New" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "responsible": { + "href": null + }, + "assignee": { + "href": null + }, + "version": { + "href": null + }, + "parent": { + "href": null, + "title": null + }, + "customActions": [], + "logCosts": { + "href": "/work_packages/40/cost_entries/new", + "type": "text/html", + "title": "Log costs on kl" + }, + "showCosts": { + "href": "/projects/3/cost_reports?fields%5B%5D=WorkPackageId&operators%5BWorkPackageId%5D=%3D&set_filter=1&values%5BWorkPackageId%5D=40", + "type": "text/html", + "title": "Show cost entries" + }, + "costsByType": { + "href": "/api/v3/work_packages/40/summarized_costs_by_type" + }, + "meetings": { + "href": "/work_packages/40/tabs/meetings", + "title": "meetings" + }, + "github": { + "href": "/work_packages/40/tabs/github", + "title": "github" + }, + "github_pull_requests": { + "href": "/api/v3/work_packages/40/github_pull_requests", + "title": "GitHub pull requests" + }, + "gitlab": { + "href": "/work_packages/40/tabs/gitlab", + "title": "gitlab" + }, + "gitlab_merge_requests": { + "href": "/api/v3/work_packages/40/gitlab_merge_requests", + "title": "Gitlab merge requests" + }, + "gitlab_issues": { + "href": "/api/v3/work_packages/40/gitlab_issues", + "title": "Gitlab Issues" + }, + "convertBCF": { + "href": "/api/bcf/2.1/projects/project1/topics", + "title": "Convert to BCF", + "payload": { + "reference_links": [ + "/api/v3/work_packages/40" + ] + }, + "method": "post" + } + } + }, + "user": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2025-01-01T18:28:20.852Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "activity": { + "_type": "TimeEntriesActivity", + "id": 1, + "name": "Management", + "position": 1, + "default": true, + "_links": { + "self": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + }, + "projects": [] + } + } + }, + "_links": { + "self": { + "href": "/api/v3/time_entries/2" + }, + "updateImmediately": { + "href": "/api/v3/time_entries/2", + "method": "patch" + }, + "update": { + "href": "/api/v3/time_entries/2/form", + "method": "post" + }, + "delete": { + "href": "/api/v3/time_entries/2", + "method": "delete" + }, + "schema": { + "href": "/api/v3/time_entries/schema" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "workPackage": { + "href": "/api/v3/work_packages/40", + "title": "kl" + }, + "user": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "activity": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + } + } + } +} diff --git a/zerver/webhooks/openproject/fixtures/time_entry_created__with_iso_hm.json b/zerver/webhooks/openproject/fixtures/time_entry_created__with_iso_hm.json new file mode 100644 index 0000000000..58cffe7f7c --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/time_entry_created__with_iso_hm.json @@ -0,0 +1,445 @@ +{ + "action": "time_entry:created", + "time_entry": { + "_type": "TimeEntry", + "id": 2, + "ongoing": false, + "comment": { + "format": "plain", + "raw": "", + "html": "" + }, + "spentOn": "2025-01-02", + "hours": "PT07H42M", + "createdAt": "2025-01-01T21:20:58.333Z", + "updatedAt": "2025-01-01T21:20:58.333Z", + "_embedded": { + "project": { + "_type": "Project", + "id": 3, + "identifier": "project1", + "name": "Project1", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "1. nucxdswssdfdsf\\_hb\\_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf", + "html": "
    \n
  1. nucxdswssdfdsf_hb_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf
  2. \n
" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2025-01-01T20:41:55.823Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "createWorkPackage": { + "href": "/api/v3/projects/3/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/3/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/3/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + }, + "workPackage": { + "_type": "WorkPackage", + "id": 40, + "lockVersion": 0, + "subject": "kl", + "description": { + "format": "markdown", + "raw": "", + "html": "" + }, + "scheduleManually": false, + "startDate": null, + "dueDate": null, + "derivedStartDate": null, + "derivedDueDate": null, + "estimatedTime": null, + "derivedEstimatedTime": null, + "derivedRemainingTime": null, + "duration": null, + "ignoreNonWorkingDays": false, + "spentTime": "PT0S", + "percentageDone": null, + "derivedPercentageDone": null, + "createdAt": "2025-01-01T20:45:21.718Z", + "updatedAt": "2025-01-01T20:45:21.759Z", + "readonly": false, + "laborCosts": "0.00 EUR", + "materialCosts": "0.00 EUR", + "overallCosts": "0.00 EUR", + "_links": { + "attachments": { + "href": "/api/v3/work_packages/40/attachments" + }, + "prepareAttachment": { + "href": "/api/v3/work_packages/40/attachments/prepare", + "method": "post" + }, + "addAttachment": { + "href": "/api/v3/work_packages/40/attachments", + "method": "post" + }, + "fileLinks": { + "href": "/api/v3/work_packages/40/file_links" + }, + "addFileLink": { + "href": "/api/v3/work_packages/40/file_links", + "method": "post" + }, + "self": { + "href": "/api/v3/work_packages/40", + "title": "kl" + }, + "update": { + "href": "/api/v3/work_packages/40/form", + "method": "post" + }, + "schema": { + "href": "/api/v3/work_packages/schemas/3-1" + }, + "updateImmediately": { + "href": "/api/v3/work_packages/40", + "method": "patch" + }, + "delete": { + "href": "/api/v3/work_packages/40", + "method": "delete" + }, + "logTime": { + "href": "/api/v3/time_entries", + "title": "Log time on work package 'kl'" + }, + "move": { + "href": "/work_packages/40/move/new", + "type": "text/html", + "title": "Move work package 'kl'" + }, + "copy": { + "href": "/work_packages/40/copy", + "type": "text/html", + "title": "Copy work package 'kl'" + }, + "pdf": { + "href": "/work_packages/40.pdf", + "type": "application/pdf", + "title": "Export as PDF" + }, + "atom": { + "href": "/work_packages/40.atom", + "type": "application/rss+xml", + "title": "Atom feed" + }, + "availableRelationCandidates": { + "href": "/api/v3/work_packages/40/available_relation_candidates", + "title": "Potential work packages to relate to" + }, + "customFields": { + "href": "/projects/project1/settings/custom_fields", + "type": "text/html", + "title": "Custom fields" + }, + "configureForm": { + "href": "/types/1/edit?tab=form_configuration", + "type": "text/html", + "title": "Configure form" + }, + "activities": { + "href": "/api/v3/work_packages/40/activities" + }, + "availableWatchers": { + "href": "/api/v3/work_packages/40/available_watchers" + }, + "relations": { + "href": "/api/v3/work_packages/40/relations" + }, + "revisions": { + "href": "/api/v3/work_packages/40/revisions" + }, + "watchers": { + "href": "/api/v3/work_packages/40/watchers" + }, + "addWatcher": { + "href": "/api/v3/work_packages/40/watchers", + "method": "post", + "payload": { + "user": { + "href": "/api/v3/users/{user_id}" + } + }, + "templated": true + }, + "removeWatcher": { + "href": "/api/v3/work_packages/40/watchers/{user_id}", + "method": "delete", + "templated": true + }, + "addRelation": { + "href": "/api/v3/work_packages/40/relations", + "method": "post", + "title": "Add relation" + }, + "addChild": { + "href": "/api/v3/projects/project1/work_packages", + "method": "post", + "title": "Add child of kl" + }, + "changeParent": { + "href": "/api/v3/work_packages/40", + "method": "patch", + "title": "Change parent of kl" + }, + "addComment": { + "href": "/api/v3/work_packages/40/activities", + "method": "post", + "title": "Add comment" + }, + "previewMarkup": { + "href": "/api/v3/render/markdown?context=/api/v3/work_packages/40", + "method": "post" + }, + "timeEntries": { + "href": "/api/v3/time_entries?filters=%5B%7B%22work_package_id%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2240%22%5D%7D%7D%5D", + "title": "Time entries" + }, + "ancestors": [], + "category": { + "href": null + }, + "type": { + "href": "/api/v3/types/1", + "title": "Task" + }, + "priority": { + "href": "/api/v3/priorities/8", + "title": "Normal" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "status": { + "href": "/api/v3/statuses/1", + "title": "New" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "responsible": { + "href": null + }, + "assignee": { + "href": null + }, + "version": { + "href": null + }, + "parent": { + "href": null, + "title": null + }, + "customActions": [], + "logCosts": { + "href": "/work_packages/40/cost_entries/new", + "type": "text/html", + "title": "Log costs on kl" + }, + "showCosts": { + "href": "/projects/3/cost_reports?fields%5B%5D=WorkPackageId&operators%5BWorkPackageId%5D=%3D&set_filter=1&values%5BWorkPackageId%5D=40", + "type": "text/html", + "title": "Show cost entries" + }, + "costsByType": { + "href": "/api/v3/work_packages/40/summarized_costs_by_type" + }, + "meetings": { + "href": "/work_packages/40/tabs/meetings", + "title": "meetings" + }, + "github": { + "href": "/work_packages/40/tabs/github", + "title": "github" + }, + "github_pull_requests": { + "href": "/api/v3/work_packages/40/github_pull_requests", + "title": "GitHub pull requests" + }, + "gitlab": { + "href": "/work_packages/40/tabs/gitlab", + "title": "gitlab" + }, + "gitlab_merge_requests": { + "href": "/api/v3/work_packages/40/gitlab_merge_requests", + "title": "Gitlab merge requests" + }, + "gitlab_issues": { + "href": "/api/v3/work_packages/40/gitlab_issues", + "title": "Gitlab Issues" + }, + "convertBCF": { + "href": "/api/bcf/2.1/projects/project1/topics", + "title": "Convert to BCF", + "payload": { + "reference_links": [ + "/api/v3/work_packages/40" + ] + }, + "method": "post" + } + } + }, + "user": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2025-01-01T18:28:20.852Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "activity": { + "_type": "TimeEntriesActivity", + "id": 1, + "name": "Management", + "position": 1, + "default": true, + "_links": { + "self": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + }, + "projects": [] + } + } + }, + "_links": { + "self": { + "href": "/api/v3/time_entries/2" + }, + "updateImmediately": { + "href": "/api/v3/time_entries/2", + "method": "patch" + }, + "update": { + "href": "/api/v3/time_entries/2/form", + "method": "post" + }, + "delete": { + "href": "/api/v3/time_entries/2", + "method": "delete" + }, + "schema": { + "href": "/api/v3/time_entries/schema" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "workPackage": { + "href": "/api/v3/work_packages/40", + "title": "kl" + }, + "user": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "activity": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + } + } + } +} diff --git a/zerver/webhooks/openproject/fixtures/time_entry_created__with_workpackage.json b/zerver/webhooks/openproject/fixtures/time_entry_created__with_workpackage.json new file mode 100644 index 0000000000..9007af28fa --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/time_entry_created__with_workpackage.json @@ -0,0 +1,445 @@ +{ + "action": "time_entry:created", + "time_entry": { + "_type": "TimeEntry", + "id": 2, + "ongoing": false, + "comment": { + "format": "plain", + "raw": "", + "html": "" + }, + "spentOn": "2025-01-02", + "hours": "PT1H", + "createdAt": "2025-01-01T21:20:58.333Z", + "updatedAt": "2025-01-01T21:20:58.333Z", + "_embedded": { + "project": { + "_type": "Project", + "id": 3, + "identifier": "project1", + "name": "Project1", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "1. nucxdswssdfdsf\\_hb\\_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf", + "html": "
    \n
  1. nucxdswssdfdsf_hb_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf
  2. \n
" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2025-01-01T20:41:55.823Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "createWorkPackage": { + "href": "/api/v3/projects/3/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/3/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/3/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + }, + "workPackage": { + "_type": "WorkPackage", + "id": 40, + "lockVersion": 0, + "subject": "kl", + "description": { + "format": "markdown", + "raw": "", + "html": "" + }, + "scheduleManually": false, + "startDate": null, + "dueDate": null, + "derivedStartDate": null, + "derivedDueDate": null, + "estimatedTime": null, + "derivedEstimatedTime": null, + "derivedRemainingTime": null, + "duration": null, + "ignoreNonWorkingDays": false, + "spentTime": "PT0S", + "percentageDone": null, + "derivedPercentageDone": null, + "createdAt": "2025-01-01T20:45:21.718Z", + "updatedAt": "2025-01-01T20:45:21.759Z", + "readonly": false, + "laborCosts": "0.00 EUR", + "materialCosts": "0.00 EUR", + "overallCosts": "0.00 EUR", + "_links": { + "attachments": { + "href": "/api/v3/work_packages/40/attachments" + }, + "prepareAttachment": { + "href": "/api/v3/work_packages/40/attachments/prepare", + "method": "post" + }, + "addAttachment": { + "href": "/api/v3/work_packages/40/attachments", + "method": "post" + }, + "fileLinks": { + "href": "/api/v3/work_packages/40/file_links" + }, + "addFileLink": { + "href": "/api/v3/work_packages/40/file_links", + "method": "post" + }, + "self": { + "href": "/api/v3/work_packages/40", + "title": "kl" + }, + "update": { + "href": "/api/v3/work_packages/40/form", + "method": "post" + }, + "schema": { + "href": "/api/v3/work_packages/schemas/3-1" + }, + "updateImmediately": { + "href": "/api/v3/work_packages/40", + "method": "patch" + }, + "delete": { + "href": "/api/v3/work_packages/40", + "method": "delete" + }, + "logTime": { + "href": "/api/v3/time_entries", + "title": "Log time on work package 'kl'" + }, + "move": { + "href": "/work_packages/40/move/new", + "type": "text/html", + "title": "Move work package 'kl'" + }, + "copy": { + "href": "/work_packages/40/copy", + "type": "text/html", + "title": "Copy work package 'kl'" + }, + "pdf": { + "href": "/work_packages/40.pdf", + "type": "application/pdf", + "title": "Export as PDF" + }, + "atom": { + "href": "/work_packages/40.atom", + "type": "application/rss+xml", + "title": "Atom feed" + }, + "availableRelationCandidates": { + "href": "/api/v3/work_packages/40/available_relation_candidates", + "title": "Potential work packages to relate to" + }, + "customFields": { + "href": "/projects/project1/settings/custom_fields", + "type": "text/html", + "title": "Custom fields" + }, + "configureForm": { + "href": "/types/1/edit?tab=form_configuration", + "type": "text/html", + "title": "Configure form" + }, + "activities": { + "href": "/api/v3/work_packages/40/activities" + }, + "availableWatchers": { + "href": "/api/v3/work_packages/40/available_watchers" + }, + "relations": { + "href": "/api/v3/work_packages/40/relations" + }, + "revisions": { + "href": "/api/v3/work_packages/40/revisions" + }, + "watchers": { + "href": "/api/v3/work_packages/40/watchers" + }, + "addWatcher": { + "href": "/api/v3/work_packages/40/watchers", + "method": "post", + "payload": { + "user": { + "href": "/api/v3/users/{user_id}" + } + }, + "templated": true + }, + "removeWatcher": { + "href": "/api/v3/work_packages/40/watchers/{user_id}", + "method": "delete", + "templated": true + }, + "addRelation": { + "href": "/api/v3/work_packages/40/relations", + "method": "post", + "title": "Add relation" + }, + "addChild": { + "href": "/api/v3/projects/project1/work_packages", + "method": "post", + "title": "Add child of kl" + }, + "changeParent": { + "href": "/api/v3/work_packages/40", + "method": "patch", + "title": "Change parent of kl" + }, + "addComment": { + "href": "/api/v3/work_packages/40/activities", + "method": "post", + "title": "Add comment" + }, + "previewMarkup": { + "href": "/api/v3/render/markdown?context=/api/v3/work_packages/40", + "method": "post" + }, + "timeEntries": { + "href": "/api/v3/time_entries?filters=%5B%7B%22work_package_id%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2240%22%5D%7D%7D%5D", + "title": "Time entries" + }, + "ancestors": [], + "category": { + "href": null + }, + "type": { + "href": "/api/v3/types/1", + "title": "Task" + }, + "priority": { + "href": "/api/v3/priorities/8", + "title": "Normal" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "status": { + "href": "/api/v3/statuses/1", + "title": "New" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "responsible": { + "href": null + }, + "assignee": { + "href": null + }, + "version": { + "href": null + }, + "parent": { + "href": null, + "title": null + }, + "customActions": [], + "logCosts": { + "href": "/work_packages/40/cost_entries/new", + "type": "text/html", + "title": "Log costs on kl" + }, + "showCosts": { + "href": "/projects/3/cost_reports?fields%5B%5D=WorkPackageId&operators%5BWorkPackageId%5D=%3D&set_filter=1&values%5BWorkPackageId%5D=40", + "type": "text/html", + "title": "Show cost entries" + }, + "costsByType": { + "href": "/api/v3/work_packages/40/summarized_costs_by_type" + }, + "meetings": { + "href": "/work_packages/40/tabs/meetings", + "title": "meetings" + }, + "github": { + "href": "/work_packages/40/tabs/github", + "title": "github" + }, + "github_pull_requests": { + "href": "/api/v3/work_packages/40/github_pull_requests", + "title": "GitHub pull requests" + }, + "gitlab": { + "href": "/work_packages/40/tabs/gitlab", + "title": "gitlab" + }, + "gitlab_merge_requests": { + "href": "/api/v3/work_packages/40/gitlab_merge_requests", + "title": "Gitlab merge requests" + }, + "gitlab_issues": { + "href": "/api/v3/work_packages/40/gitlab_issues", + "title": "Gitlab Issues" + }, + "convertBCF": { + "href": "/api/bcf/2.1/projects/project1/topics", + "title": "Convert to BCF", + "payload": { + "reference_links": [ + "/api/v3/work_packages/40" + ] + }, + "method": "post" + } + } + }, + "user": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2025-01-01T18:28:20.852Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "activity": { + "_type": "TimeEntriesActivity", + "id": 1, + "name": "Management", + "position": 1, + "default": true, + "_links": { + "self": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + }, + "projects": [] + } + } + }, + "_links": { + "self": { + "href": "/api/v3/time_entries/2" + }, + "updateImmediately": { + "href": "/api/v3/time_entries/2", + "method": "patch" + }, + "update": { + "href": "/api/v3/time_entries/2/form", + "method": "post" + }, + "delete": { + "href": "/api/v3/time_entries/2", + "method": "delete" + }, + "schema": { + "href": "/api/v3/time_entries/schema" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "workPackage": { + "href": "/api/v3/work_packages/40", + "title": "kl" + }, + "user": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "activity": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + } + } + } +} diff --git a/zerver/webhooks/openproject/fixtures/time_entry_created__without_workpackage.json b/zerver/webhooks/openproject/fixtures/time_entry_created__without_workpackage.json new file mode 100644 index 0000000000..e1a25b3ab8 --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/time_entry_created__without_workpackage.json @@ -0,0 +1,180 @@ +{ + "action": "time_entry:created", + "time_entry": { + "_type": "TimeEntry", + "id": 2, + "ongoing": false, + "comment": { + "format": "plain", + "raw": "", + "html": "" + }, + "spentOn": "2025-01-02", + "hours": "PT1H", + "createdAt": "2025-01-01T21:20:58.333Z", + "updatedAt": "2025-01-01T21:20:58.333Z", + "_embedded": { + "project": { + "_type": "Project", + "id": 3, + "identifier": "project1", + "name": "Project1", + "active": true, + "public": false, + "description": { + "format": "markdown", + "raw": "1. nucxdswssdfdsf\\_hb\\_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf", + "html": "
    \n
  1. nucxdswssdfdsf_hb_xcddsfjsxczxchkxvcxvyuguygyujijdgrfcxbcfxbcgfjhkhksf
  2. \n
" + }, + "createdAt": "2024-12-31T18:07:24.546Z", + "updatedAt": "2025-01-01T20:41:55.823Z", + "statusExplanation": { + "format": "markdown", + "raw": "", + "html": "" + }, + "_links": { + "self": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/3/categories" + }, + "versions": { + "href": "/api/v3/projects/3/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/3/types" + }, + "update": { + "href": "/api/v3/projects/3/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/3", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/3", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [ + { + "href": "/api/v3/projects/1", + "title": "Demo project" + } + ], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%223%22%5D%7D%7D%5D" + }, + "parent": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": null + } + } + }, + "user": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2025-01-01T18:28:20.852Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "activity": { + "_type": "TimeEntriesActivity", + "id": 1, + "name": "Management", + "position": 1, + "default": true, + "_links": { + "self": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + }, + "projects": [] + } + } + }, + "_links": { + "self": { + "href": "/api/v3/time_entries/2" + }, + "updateImmediately": { + "href": "/api/v3/time_entries/2", + "method": "patch" + }, + "update": { + "href": "/api/v3/time_entries/2/form", + "method": "post" + }, + "delete": { + "href": "/api/v3/time_entries/2", + "method": "delete" + }, + "schema": { + "href": "/api/v3/time_entries/schema" + }, + "project": { + "href": "/api/v3/projects/3", + "title": "Project1" + }, + "user": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "activity": { + "href": "/api/v3/time_entries/activities/1", + "title": "Management" + } + } + } +} diff --git a/zerver/webhooks/openproject/fixtures/work_package_created.json b/zerver/webhooks/openproject/fixtures/work_package_created.json new file mode 100644 index 0000000000..3ad891c95a --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/work_package_created.json @@ -0,0 +1,551 @@ +{ + "action": "work_package:created", + "work_package": { + "_type": "WorkPackage", + "id": 37, + "lockVersion": 0, + "subject": "Task1", + "description": { + "format": "markdown", + "raw": "", + "html": "" + }, + "scheduleManually": false, + "startDate": null, + "dueDate": null, + "derivedStartDate": null, + "derivedDueDate": null, + "estimatedTime": null, + "derivedEstimatedTime": null, + "derivedRemainingTime": null, + "duration": null, + "ignoreNonWorkingDays": false, + "percentageDone": null, + "derivedPercentageDone": null, + "createdAt": "2024-12-31T13:09:24.901Z", + "updatedAt": "2024-12-31T13:09:24.969Z", + "readonly": false, + "_embedded": { + "attachments": { + "_type": "Collection", + "total": 0, + "count": 0, + "_embedded": { + "elements": [] + }, + "_links": { + "self": { + "href": "/api/v3/work_packages/37/attachments" + } + } + }, + "fileLinks": { + "_type": "Collection", + "total": 0, + "count": 0, + "pageSize": 30, + "offset": 1, + "_embedded": { + "elements": [] + }, + "_links": { + "self": { + "href": "/api/v3/work_packages/37/file_links?offset=1&pageSize=30" + }, + "jumpTo": { + "href": "/api/v3/work_packages/37/file_links?offset=%7Boffset%7D&pageSize=30", + "templated": true + }, + "changeSize": { + "href": "/api/v3/work_packages/37/file_links?offset=1&pageSize=%7Bsize%7D", + "templated": true + } + } + }, + "relations": { + "_type": "Collection", + "total": 0, + "count": 0, + "_embedded": { + "elements": [] + }, + "_links": { + "self": { + "href": "/api/v3/work_packages/37/relations" + } + } + }, + "type": { + "_type": "Type", + "id": 1, + "name": "Task", + "color": "#1A67A3", + "position": 1, + "isDefault": true, + "isMilestone": false, + "createdAt": "2024-12-31T12:52:41.042Z", + "updatedAt": "2024-12-31T12:52:41.042Z", + "_links": { + "self": { + "href": "/api/v3/types/1", + "title": "Task" + } + } + }, + "priority": { + "_type": "Priority", + "id": 8, + "name": "Normal", + "position": 2, + "color": "#74C0FC", + "isDefault": true, + "isActive": true, + "_links": { + "self": { + "href": "/api/v3/priorities/8", + "title": "Normal" + } + } + }, + "project": { + "_type": "Project", + "id": 1, + "identifier": "demo-project", + "name": "Demo project", + "active": true, + "public": true, + "description": { + "format": "markdown", + "raw": "This is a short summary of the goals of this demo project.", + "html": "

This is a short summary of the goals of this demo project.

" + }, + "createdAt": "2024-12-31T12:52:45.287Z", + "updatedAt": "2024-12-31T12:52:45.287Z", + "statusExplanation": { + "format": "markdown", + "raw": "All tasks are on schedule. The people involved know their tasks. The system is completely set up.", + "html": "

All tasks are on schedule. The people involved know their tasks. The system is completely set up.

" + }, + "_links": { + "self": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "createWorkPackage": { + "href": "/api/v3/projects/1/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/1/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/1/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/1/categories" + }, + "versions": { + "href": "/api/v3/projects/1/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/1/types" + }, + "update": { + "href": "/api/v3/projects/1/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/1", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/1", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "parent": { + "href": null + }, + "status": { + "href": "/api/v3/project_statuses/on_track", + "title": "On track" + } + } + }, + "status": { + "_type": "Status", + "id": 1, + "name": "New", + "isClosed": false, + "color": "#1098AD", + "isDefault": true, + "isReadonly": false, + "excludedFromTotals": false, + "defaultDoneRatio": 0, + "position": 1, + "_links": { + "self": { + "href": "/api/v3/statuses/1", + "title": "New" + } + } + }, + "author": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2024-12-31T12:54:19.804Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "responsible": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2024-12-31T12:54:19.804Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "assignee": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2024-12-31T12:54:19.804Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "customActions": [] + }, + "_links": { + "attachments": { + "href": "/api/v3/work_packages/37/attachments" + }, + "prepareAttachment": { + "href": "/api/v3/work_packages/37/attachments/prepare", + "method": "post" + }, + "addAttachment": { + "href": "/api/v3/work_packages/37/attachments", + "method": "post" + }, + "fileLinks": { + "href": "/api/v3/work_packages/37/file_links" + }, + "addFileLink": { + "href": "/api/v3/work_packages/37/file_links", + "method": "post" + }, + "self": { + "href": "/api/v3/work_packages/37", + "title": "Task1" + }, + "update": { + "href": "/api/v3/work_packages/37/form", + "method": "post" + }, + "schema": { + "href": "/api/v3/work_packages/schemas/1-1" + }, + "updateImmediately": { + "href": "/api/v3/work_packages/37", + "method": "patch" + }, + "delete": { + "href": "/api/v3/work_packages/37", + "method": "delete" + }, + "logTime": { + "href": "/api/v3/time_entries", + "title": "Log time on work package 'Task1'" + }, + "move": { + "href": "/work_packages/37/move/new", + "type": "text/html", + "title": "Move work package 'Task1'" + }, + "copy": { + "href": "/work_packages/37/copy", + "type": "text/html", + "title": "Copy work package 'Task1'" + }, + "pdf": { + "href": "/work_packages/37.pdf", + "type": "application/pdf", + "title": "Export as PDF" + }, + "atom": { + "href": "/work_packages/37.atom", + "type": "application/rss+xml", + "title": "Atom feed" + }, + "availableRelationCandidates": { + "href": "/api/v3/work_packages/37/available_relation_candidates", + "title": "Potential work packages to relate to" + }, + "customFields": { + "href": "/projects/demo-project/settings/custom_fields", + "type": "text/html", + "title": "Custom fields" + }, + "configureForm": { + "href": "/types/1/edit?tab=form_configuration", + "type": "text/html", + "title": "Configure form" + }, + "activities": { + "href": "/api/v3/work_packages/37/activities" + }, + "availableWatchers": { + "href": "/api/v3/work_packages/37/available_watchers" + }, + "relations": { + "href": "/api/v3/work_packages/37/relations" + }, + "revisions": { + "href": "/api/v3/work_packages/37/revisions" + }, + "watchers": { + "href": "/api/v3/work_packages/37/watchers" + }, + "addWatcher": { + "href": "/api/v3/work_packages/37/watchers", + "method": "post", + "payload": { + "user": { + "href": "/api/v3/users/{user_id}" + } + }, + "templated": true + }, + "removeWatcher": { + "href": "/api/v3/work_packages/37/watchers/{user_id}", + "method": "delete", + "templated": true + }, + "addRelation": { + "href": "/api/v3/work_packages/37/relations", + "method": "post", + "title": "Add relation" + }, + "addChild": { + "href": "/api/v3/projects/demo-project/work_packages", + "method": "post", + "title": "Add child of Task1" + }, + "changeParent": { + "href": "/api/v3/work_packages/37", + "method": "patch", + "title": "Change parent of Task1" + }, + "addComment": { + "href": "/api/v3/work_packages/37/activities", + "method": "post", + "title": "Add comment" + }, + "previewMarkup": { + "href": "/api/v3/render/markdown?context=/api/v3/work_packages/37", + "method": "post" + }, + "timeEntries": { + "href": "/api/v3/time_entries?filters=%5B%7B%22work_package_id%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2237%22%5D%7D%7D%5D", + "title": "Time entries" + }, + "ancestors": [], + "category": { + "href": null + }, + "type": { + "href": "/api/v3/types/1", + "title": "Task" + }, + "priority": { + "href": "/api/v3/priorities/8", + "title": "Normal" + }, + "project": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": "/api/v3/statuses/1", + "title": "New" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "responsible": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "assignee": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "version": { + "href": null + }, + "parent": { + "href": null, + "title": null + }, + "customActions": [], + "meetings": { + "href": "/work_packages/37/tabs/meetings", + "title": "meetings" + }, + "github": { + "href": "/work_packages/37/tabs/github", + "title": "github" + }, + "github_pull_requests": { + "href": "/api/v3/work_packages/37/github_pull_requests", + "title": "GitHub pull requests" + }, + "gitlab": { + "href": "/work_packages/37/tabs/gitlab", + "title": "gitlab" + }, + "gitlab_merge_requests": { + "href": "/api/v3/work_packages/37/gitlab_merge_requests", + "title": "Gitlab merge requests" + }, + "gitlab_issues": { + "href": "/api/v3/work_packages/37/gitlab_issues", + "title": "Gitlab Issues" + }, + "convertBCF": { + "href": "/api/bcf/2.1/projects/demo-project/topics", + "title": "Convert to BCF", + "payload": { + "reference_links": [ + "/api/v3/work_packages/37" + ] + }, + "method": "post" + } + } + } + } diff --git a/zerver/webhooks/openproject/fixtures/work_package_updated.json b/zerver/webhooks/openproject/fixtures/work_package_updated.json new file mode 100644 index 0000000000..933c4557da --- /dev/null +++ b/zerver/webhooks/openproject/fixtures/work_package_updated.json @@ -0,0 +1,551 @@ +{ + "action": "work_package:updated", + "work_package": { + "_type": "WorkPackage", + "id": 37, + "lockVersion": 1, + "subject": "Task1", + "description": { + "format": "markdown", + "raw": "dxvdsvds", + "html": "

dxvdsvds

" + }, + "scheduleManually": false, + "startDate": null, + "dueDate": null, + "derivedStartDate": null, + "derivedDueDate": null, + "estimatedTime": null, + "derivedEstimatedTime": null, + "derivedRemainingTime": null, + "duration": null, + "ignoreNonWorkingDays": false, + "percentageDone": null, + "derivedPercentageDone": null, + "createdAt": "2024-12-31T13:09:24.901Z", + "updatedAt": "2024-12-31T18:10:05.102Z", + "readonly": false, + "_embedded": { + "attachments": { + "_type": "Collection", + "total": 0, + "count": 0, + "_embedded": { + "elements": [] + }, + "_links": { + "self": { + "href": "/api/v3/work_packages/37/attachments" + } + } + }, + "fileLinks": { + "_type": "Collection", + "total": 0, + "count": 0, + "pageSize": 30, + "offset": 1, + "_embedded": { + "elements": [] + }, + "_links": { + "self": { + "href": "/api/v3/work_packages/37/file_links?offset=1&pageSize=30" + }, + "jumpTo": { + "href": "/api/v3/work_packages/37/file_links?offset=%7Boffset%7D&pageSize=30", + "templated": true + }, + "changeSize": { + "href": "/api/v3/work_packages/37/file_links?offset=1&pageSize=%7Bsize%7D", + "templated": true + } + } + }, + "relations": { + "_type": "Collection", + "total": 0, + "count": 0, + "_embedded": { + "elements": [] + }, + "_links": { + "self": { + "href": "/api/v3/work_packages/37/relations" + } + } + }, + "type": { + "_type": "Type", + "id": 1, + "name": "Task", + "color": "#1A67A3", + "position": 1, + "isDefault": true, + "isMilestone": false, + "createdAt": "2024-12-31T12:52:41.042Z", + "updatedAt": "2024-12-31T12:52:41.042Z", + "_links": { + "self": { + "href": "/api/v3/types/1", + "title": "Task" + } + } + }, + "priority": { + "_type": "Priority", + "id": 8, + "name": "Normal", + "position": 2, + "color": "#74C0FC", + "isDefault": true, + "isActive": true, + "_links": { + "self": { + "href": "/api/v3/priorities/8", + "title": "Normal" + } + } + }, + "project": { + "_type": "Project", + "id": 1, + "identifier": "demo-project", + "name": "Demo project", + "active": true, + "public": true, + "description": { + "format": "markdown", + "raw": "This is a short summary of the goals of this demo project.", + "html": "

This is a short summary of the goals of this demo project.

" + }, + "createdAt": "2024-12-31T12:52:45.287Z", + "updatedAt": "2024-12-31T18:07:24.609Z", + "statusExplanation": { + "format": "markdown", + "raw": "All tasks are on schedule. The people involved know their tasks. The system is completely set up.", + "html": "

All tasks are on schedule. The people involved know their tasks. The system is completely set up.

" + }, + "_links": { + "self": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "createWorkPackage": { + "href": "/api/v3/projects/1/work_packages/form", + "method": "post" + }, + "createWorkPackageImmediately": { + "href": "/api/v3/projects/1/work_packages", + "method": "post" + }, + "workPackages": { + "href": "/api/v3/projects/1/work_packages" + }, + "storages": [], + "categories": { + "href": "/api/v3/projects/1/categories" + }, + "versions": { + "href": "/api/v3/projects/1/versions" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22project%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "types": { + "href": "/api/v3/projects/1/types" + }, + "update": { + "href": "/api/v3/projects/1/form", + "method": "post" + }, + "updateImmediately": { + "href": "/api/v3/projects/1", + "method": "patch" + }, + "delete": { + "href": "/api/v3/projects/1", + "method": "delete" + }, + "schema": { + "href": "/api/v3/projects/schema" + }, + "ancestors": [], + "projectStorages": { + "href": "/api/v3/project_storages?filters=%5B%7B%22projectId%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D" + }, + "parent": { + "href": null + }, + "status": { + "href": "/api/v3/project_statuses/on_track", + "title": "On track" + } + } + }, + "status": { + "_type": "Status", + "id": 1, + "name": "New", + "isClosed": false, + "color": "#1098AD", + "isDefault": true, + "isReadonly": false, + "excludedFromTotals": false, + "defaultDoneRatio": 0, + "position": 1, + "_links": { + "self": { + "href": "/api/v3/statuses/1", + "title": "New" + } + } + }, + "author": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2024-12-31T12:54:19.804Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "responsible": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2024-12-31T12:54:19.804Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "assignee": { + "_type": "User", + "id": 4, + "name": "Nirved Mishra", + "createdAt": "2024-12-31T12:52:44.786Z", + "updatedAt": "2024-12-31T12:54:19.804Z", + "login": "nirved431@gmail.com", + "admin": true, + "firstName": "Nirved", + "lastName": "Mishra", + "email": "nirved431@gmail.com", + "avatar": "https://secure.gravatar.com/avatar/aef06c318f044c985140e4ecae344c4a?default=404&secure=true", + "status": "active", + "identityUrl": null, + "language": "en", + "_links": { + "self": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "memberships": { + "href": "/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%224%22%5D%7D%7D%5D", + "title": "Memberships" + }, + "showUser": { + "href": "/users/4", + "type": "text/html" + }, + "updateImmediately": { + "href": "/api/v3/users/4", + "title": "Update nirved431@gmail.com", + "method": "patch" + }, + "lock": { + "href": "/api/v3/users/4/lock", + "title": "Set lock on nirved431@gmail.com", + "method": "post" + }, + "delete": { + "href": "/api/v3/users/4", + "title": "Delete nirved431@gmail.com", + "method": "delete" + } + } + }, + "customActions": [] + }, + "_links": { + "attachments": { + "href": "/api/v3/work_packages/37/attachments" + }, + "prepareAttachment": { + "href": "/api/v3/work_packages/37/attachments/prepare", + "method": "post" + }, + "addAttachment": { + "href": "/api/v3/work_packages/37/attachments", + "method": "post" + }, + "fileLinks": { + "href": "/api/v3/work_packages/37/file_links" + }, + "addFileLink": { + "href": "/api/v3/work_packages/37/file_links", + "method": "post" + }, + "self": { + "href": "/api/v3/work_packages/37", + "title": "Task1" + }, + "update": { + "href": "/api/v3/work_packages/37/form", + "method": "post" + }, + "schema": { + "href": "/api/v3/work_packages/schemas/1-1" + }, + "updateImmediately": { + "href": "/api/v3/work_packages/37", + "method": "patch" + }, + "delete": { + "href": "/api/v3/work_packages/37", + "method": "delete" + }, + "logTime": { + "href": "/api/v3/time_entries", + "title": "Log time on work package 'Task1'" + }, + "move": { + "href": "/work_packages/37/move/new", + "type": "text/html", + "title": "Move work package 'Task1'" + }, + "copy": { + "href": "/work_packages/37/copy", + "type": "text/html", + "title": "Copy work package 'Task1'" + }, + "pdf": { + "href": "/work_packages/37.pdf", + "type": "application/pdf", + "title": "Export as PDF" + }, + "atom": { + "href": "/work_packages/37.atom", + "type": "application/rss+xml", + "title": "Atom feed" + }, + "availableRelationCandidates": { + "href": "/api/v3/work_packages/37/available_relation_candidates", + "title": "Potential work packages to relate to" + }, + "customFields": { + "href": "/projects/demo-project/settings/custom_fields", + "type": "text/html", + "title": "Custom fields" + }, + "configureForm": { + "href": "/types/1/edit?tab=form_configuration", + "type": "text/html", + "title": "Configure form" + }, + "activities": { + "href": "/api/v3/work_packages/37/activities" + }, + "availableWatchers": { + "href": "/api/v3/work_packages/37/available_watchers" + }, + "relations": { + "href": "/api/v3/work_packages/37/relations" + }, + "revisions": { + "href": "/api/v3/work_packages/37/revisions" + }, + "watchers": { + "href": "/api/v3/work_packages/37/watchers" + }, + "addWatcher": { + "href": "/api/v3/work_packages/37/watchers", + "method": "post", + "payload": { + "user": { + "href": "/api/v3/users/{user_id}" + } + }, + "templated": true + }, + "removeWatcher": { + "href": "/api/v3/work_packages/37/watchers/{user_id}", + "method": "delete", + "templated": true + }, + "addRelation": { + "href": "/api/v3/work_packages/37/relations", + "method": "post", + "title": "Add relation" + }, + "addChild": { + "href": "/api/v3/projects/demo-project/work_packages", + "method": "post", + "title": "Add child of Task1" + }, + "changeParent": { + "href": "/api/v3/work_packages/37", + "method": "patch", + "title": "Change parent of Task1" + }, + "addComment": { + "href": "/api/v3/work_packages/37/activities", + "method": "post", + "title": "Add comment" + }, + "previewMarkup": { + "href": "/api/v3/render/markdown?context=/api/v3/work_packages/37", + "method": "post" + }, + "timeEntries": { + "href": "/api/v3/time_entries?filters=%5B%7B%22work_package_id%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2237%22%5D%7D%7D%5D", + "title": "Time entries" + }, + "ancestors": [], + "category": { + "href": null + }, + "type": { + "href": "/api/v3/types/1", + "title": "Task" + }, + "priority": { + "href": "/api/v3/priorities/8", + "title": "Normal" + }, + "project": { + "href": "/api/v3/projects/1", + "title": "Demo project" + }, + "status": { + "href": "/api/v3/statuses/1", + "title": "New" + }, + "author": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "responsible": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "assignee": { + "href": "/api/v3/users/4", + "title": "Nirved Mishra" + }, + "version": { + "href": null + }, + "parent": { + "href": null, + "title": null + }, + "customActions": [], + "meetings": { + "href": "/work_packages/37/tabs/meetings", + "title": "meetings" + }, + "github": { + "href": "/work_packages/37/tabs/github", + "title": "github" + }, + "github_pull_requests": { + "href": "/api/v3/work_packages/37/github_pull_requests", + "title": "GitHub pull requests" + }, + "gitlab": { + "href": "/work_packages/37/tabs/gitlab", + "title": "gitlab" + }, + "gitlab_merge_requests": { + "href": "/api/v3/work_packages/37/gitlab_merge_requests", + "title": "Gitlab merge requests" + }, + "gitlab_issues": { + "href": "/api/v3/work_packages/37/gitlab_issues", + "title": "Gitlab Issues" + }, + "convertBCF": { + "href": "/api/bcf/2.1/projects/demo-project/topics", + "title": "Convert to BCF", + "payload": { + "reference_links": [ + "/api/v3/work_packages/37" + ] + }, + "method": "post" + } + } + } + } diff --git a/zerver/webhooks/openproject/tests.py b/zerver/webhooks/openproject/tests.py new file mode 100644 index 0000000000..c06f13e263 --- /dev/null +++ b/zerver/webhooks/openproject/tests.py @@ -0,0 +1,102 @@ +from zerver.lib.test_classes import WebhookTestCase + + +class OpenProjectHookTests(WebhookTestCase): + CHANNEL_NAME = "OpenProjectUpdates" + URL_TEMPLATE = "/api/v1/external/openproject?api_key={api_key}&stream={stream}" + WEBHOOK_DIR_NAME = "openproject" + STREAM_NAME = "OpenProjectUpdates" + + def test_project_with_parent_created(self) -> None: + expected_topic = "AI Backend" + expected_message = ( + "Project **AI Backend** was created as a sub-project of **Demo project**." + ) + + self.check_webhook( + "project_created__with_parent", + expected_topic, + expected_message, + ) + + def test_project_without_parent_created(self) -> None: + expected_topic = "AI Backend" + expected_message = "Project **AI Backend** was created." + + self.check_webhook( + "project_created__without_parent", + expected_topic, + expected_message, + ) + + def test_project_updated(self) -> None: + expected_topic = "AI Backend" + expected_message = "Project **AI Backend** was updated." + self.check_webhook( + "project_updated", + expected_topic, + expected_message, + ) + + def test_work_package_created(self) -> None: + expected_topic = "Demo project" + expected_message = "**Task1** (work package **Task**) was created by **Nirved Mishra**." + self.check_webhook( + "work_package_created", + expected_topic, + expected_message, + ) + + def test_work_package_updated(self) -> None: + expected_topic = "Demo project" + expected_message = "**Task1** (work package **Task**) was updated by **Nirved Mishra**." + self.check_webhook( + "work_package_updated", + expected_topic, + expected_message, + ) + + def test_time_entry_with_workpackage_created(self) -> None: + expected_topic = "Project1" + expected_message = "**Nirved Mishra** logged **1 hour** on **kl**." + self.check_webhook( + "time_entry_created__with_workpackage", + expected_topic, + expected_message, + ) + + def test_time_entry_without_workpackage_created(self) -> None: + expected_topic = "Project1" + expected_message = "**Nirved Mishra** logged **1 hour** on **Project1**." + self.check_webhook( + "time_entry_created__without_workpackage", + expected_topic, + expected_message, + ) + + def test_time_entry_with_iso_hm(self) -> None: + expected_topic = "Project1" + expected_message = "**Nirved Mishra** logged **7 hours and 42 minutes** on **kl**." + self.check_webhook( + "time_entry_created__with_iso_hm", + expected_topic, + expected_message, + ) + + def test_time_entry_with_invalid_iso(self) -> None: + expected_topic = "Project1" + expected_message = "**Nirved Mishra** logged a time entry on **kl**." + self.check_webhook( + "time_entry_created__with_invalid_iso", + expected_topic, + expected_message, + ) + + def test_attachment_created(self) -> None: + expected_topic = "Project 2" + expected_message = "**Nirved Mishra** uploaded **a.out** in **task2**." + self.check_webhook( + "attachment_created", + expected_topic, + expected_message, + ) diff --git a/zerver/webhooks/openproject/view.py b/zerver/webhooks/openproject/view.py new file mode 100644 index 0000000000..fec202b475 --- /dev/null +++ b/zerver/webhooks/openproject/view.py @@ -0,0 +1,123 @@ +import regex as re +from django.http import HttpRequest, HttpResponse + +from zerver.decorator import webhook_view +from zerver.lib.exceptions import UnsupportedWebhookEventTypeError +from zerver.lib.response import json_success +from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint +from zerver.lib.validator import WildValue, check_string +from zerver.lib.webhooks.common import check_send_webhook_message +from zerver.models import UserProfile + +ALL_EVENT_TYPES: list[str] = [ + "project:created", + "project:updated", + "work_package:created", + "work_package:updated", + "time_entry:created", + "attachment:created", +] + +PROJECT_MESSAGE_TEMPLATE = "Project **{name}** was {action}{parent_project_message}." + +WORK_PACKAGE_MESSAGE_TEMPLATE = ( + "**{subject}** (work package **{type}**) was {action} by **{author}**." +) + +ATTACHMENT_MESSAGE_TEMPLATE = "**{author}** uploaded **{filename}** in **{container_name}**." + +TIME_ENTRY_MESSAGE_TEMPLATE = "**{user}** logged {duration_message}{parent_message}." + + +def format_duration(iso_duration: str) -> str: + duration = re.fullmatch( + r"P(?:(?P\d+)D)?(?:T(?:(?P\d+)H)?(?:(?P\d+)M)?(?:(?P\d+)S)?)?", + iso_duration, + ) + if duration is None: + raise ValueError(f"Invalid ISO 8601 duration format: {iso_duration}") + + time_units = ["days", "hours", "minutes", "seconds"] + formatted_duration = [ + f"{int(duration.group(unit))} {unit[:-1] if int(duration.group(unit)) == 1 else unit}" + for unit in time_units + if duration.group(unit) + ] + + if len(formatted_duration) > 1: + return ", ".join(formatted_duration[:-1]) + " and " + formatted_duration[-1] + return formatted_duration[0] + + +@webhook_view("OpenProject", all_event_types=ALL_EVENT_TYPES) +@typed_endpoint +def api_openproject_webhook( + request: HttpRequest, + user_profile: UserProfile, + *, + payload: JsonBodyPayload[WildValue], +) -> HttpResponse: + event_type: str = payload["action"].tame(check_string) + item, action = event_type.split(":") + action_data: WildValue = payload[item] + + if item == "project": + parent_project_message: str = "" + parent_project: str = ( + action_data.get("_embedded", {}).get("parent", {}).get("name", "").tame(check_string) + ) + if parent_project and action == "created": + parent_project_message = f" as a sub-project of **{parent_project}**" + message = PROJECT_MESSAGE_TEMPLATE.format( + name=action_data["name"].tame(check_string), + action=action, + parent_project_message=parent_project_message, + ) + topic = action_data["name"].tame(check_string) + elif item == "work_package": + message = WORK_PACKAGE_MESSAGE_TEMPLATE.format( + subject=action_data["subject"].tame(check_string), + type=action_data["_embedded"]["type"]["name"].tame(check_string), + action=action, + author=action_data["_embedded"]["author"]["name"].tame(check_string), + ) + topic = action_data["_embedded"]["project"]["name"].tame(check_string) + elif item == "attachment": + message = ATTACHMENT_MESSAGE_TEMPLATE.format( + filename=action_data["fileName"].tame(check_string), + author=action_data["_embedded"]["author"]["name"].tame(check_string), + container_name=action_data["_embedded"]["container"]["subject"].tame(check_string), + ) + topic = action_data["_embedded"]["container"]["_links"]["project"]["title"].tame( + check_string + ) + elif item == "time_entry": + parent_message: str = ( + f" on **{action_data['_embedded']['project']['name'].tame(check_string)}**" + ) + workpackage: str = ( + action_data.get("_embedded", {}) + .get("workPackage", {}) + .get("subject", "") + .tame(check_string) + ) + if workpackage: + parent_message = f" on **{workpackage}**" + + try: + formatted_duration = format_duration(action_data["hours"].tame(check_string)) + duration_message = f"**{formatted_duration}**" + except ValueError: + duration_message = "a time entry" + + message = TIME_ENTRY_MESSAGE_TEMPLATE.format( + duration_message=duration_message, + user=action_data["_embedded"]["user"]["name"].tame(check_string), + parent_message=parent_message, + ) + topic = action_data["_embedded"]["project"]["name"].tame(check_string) + else: + raise UnsupportedWebhookEventTypeError(event_type) + + check_send_webhook_message(request, user_profile, topic, message) + return json_success(request)