diff --git a/static/images/integrations/bot_avatars/openproject.png b/static/images/integrations/bot_avatars/openproject.png new file mode 100644 index 0000000000..a13d2b1c8d Binary files /dev/null and b/static/images/integrations/bot_avatars/openproject.png differ 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 0000000000..70d6bd70c4 Binary files /dev/null and b/static/images/integrations/openproject/001.png differ 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)